Sensor akselerometer yang dipasang di dek jembatan pejalan kaki merekam getaran sebagai deretan angka: satu nilai tiap milidetik, ribuan nilai per detik. Rekaman semacam ini sifatnya riil — tiap sampel adalah bilangan nyata hasil pengukuran, bukan bilangan kompleks. Ketika rekaman itu ditransformasi dengan np.fft.fft, setengah spektrum yang dihasilkan sebenarnya duplikat: seperti kita buktikan pada fftfreq dan Spektrum Frekuensi, magnitude bin ke-k identik dengan magnitude bin ke-N−k.

Identitas mag[k] = mag[N−k] itu bukan keanehan, melainkan konsekuensi langsung dari sinyal yang riil. NumPy menyediakan pasangan fungsi yang memanfaatkannya: np.fft.rfft menghitung hanya setengah spektrum pertama, dan np.fft.irfft menyusun kembali sinyal riil dari setengah data itu. Fondasi bilangan kompleksnya sudah kita pelajari di Bilangan Kompleks, dan mekanisme fft/ifft-nya di Dari DFT ke FFT.

Yang kita kerjakan pada chapter ini:

  • Menghitung setengah spektrum dengan np.fft.rfft dan membandingkannya dengan np.fft.fft
  • Menyusun ulang sinyal riil dari setengah spektrum dengan np.fft.irfft
  • Menghitung penghematan memori dari N elemen menjadi N/2 + 1 elemen complex128

Setengah Spektrum dengan np.fft.rfft #

Kita memakai sinyal getaran jembatan yang wajar: komponen utama 3 Hz beramplitudo 1 dan harmonik keduanya 6 Hz beramplitudo 0.5, direkam satu detik penuh dengan laju sampling 1.000 Hz, ditambah derau sensor ber-seed agar skenarionya terasa nyata tanpa kehilangan determinisme. Kali ini kita bandingkan panjang output kedua fungsi dan buktikan bahwa rfft memang setengah pertama fft.

python
import numpy as np

fs = 1000
t = np.arange(1000) / fs                  # sensor getaran jembatan, 1 detik
getaran = np.sin(2 * np.pi * 3 * t) + 0.5 * np.sin(2 * np.pi * 6 * t)
rng = np.random.default_rng(42)
getaran += rng.normal(0, 0.05, t.size)    # derau pengukuran sensor
spec_fft = np.fft.fft(getaran)
spec_rfft = np.fft.rfft(getaran)
print("len(fft) :", spec_fft.size)
print("len(rfft):", spec_rfft.size)
print("rfft == fft[:501]:", np.allclose(spec_fft[:501], spec_rfft))
mag = np.abs(spec_rfft)
print("mag rfft[3] =", mag[3].round(1), "| mag rfft[6] =", mag[6].round(1))
print("cermin fft[997] =", np.abs(spec_fft[997]).round(1))

Output yang Dihasilkan:

text
len(fft) : 1000
len(rfft): 501
rfft == fft[:501]: True
mag rfft[3] = 500.2 | mag rfft[6] = 248.9
cermin fft[997] = 500.2

Mari kita bedah baris demi baris:

  1. Baris 3–5: laju sampling 1.000 Hz dan satu detik rekaman; dua komponen getaran sesuai skenario — 3 Hz sebagai fundamental dan 6 Hz sebagai harmoniknya.
  2. Baris 6–7: derau rng.normal(0, 0.05, ...) dari generator ber-seed 42 meniru ketidaksempurnaan sensor; eksekusi berapa pun menghasilkan derau yang sama.
  3. Baris 9 (spec_rfft = np.fft.rfft(getaran)): rfft mengembalikan 501 koefisien, bukan 1.000. Rumusnya N/2 + 1 untuk N genap.
  4. Baris 12: np.allclose membandingkan fft[:501] dengan rfft elemen demi elemen; hasil True menegaskan bahwa rfft benar-benar setengah pertama spektrum penuh.
  5. Baris 14–15: puncak 3 Hz dan 6 Hz terbaca di bin 3 dan bin 6 dengan magnitude ~500 dan ~249 — skala (N/2) × amplitudo yang sama seperti pada fftfreq dan Spektrum Frekuensi. Bin 997 = 1000 − 3 adalah cermin bin 3: nilainya identik, dan bagian cermin semacam itulah yang tidak disimpan rfft.

Bilangan 501 itu layak dicermati. Selain bin frekuensi positif 1 sampai 499, rfft menyertakan bin DC (indeks 0) dan — untuk N genap — bin Nyquist (indeks 500) di ujung array. Dua bin ekstra itu tidak punya pasangan cermin, sehingga totalnya 499 + 2 = 501, bukan sekadar 500.

Menyusun Kembali Sinyal dengan np.fft.irfft #

Setengah spektrum memuat informasi lengkap untuk sinyal riil, sehingga rekonstruksi tidak membutuhkan sisi cermin. np.fft.irfft menerima array setengah spektrum, menyusun cermin konjugatnya secara internal, lalu menjalankan invers. Bedanya dengan np.fft.ifft pada Dari DFT ke FFT: hasilnya langsung float64, bukan complex128, karena sinyal riil memang tidak punya bagian imajiner.

python
import numpy as np

fs = 1000
t = np.arange(1000) / fs
getaran = np.sin(2 * np.pi * 3 * t) + 0.5 * np.sin(2 * np.pi * 6 * t)
rng = np.random.default_rng(42)
getaran += rng.normal(0, 0.05, t.size)
spektrum = np.fft.rfft(getaran)
hasil = np.fft.irfft(spektrum, n=t.size)
print("dtype hasil :", hasil.dtype)
print("asli  [:4]  :", getaran[:4].round(4))
print("hasil [:4]  :", hasil[:4].round(4))
print("selisih maks:", f"{np.abs(hasil - getaran).max():.2e}")

Output yang Dihasilkan:

text
dtype hasil : float64
asli  [:4]  : [ 0.0152 -0.0143  0.1129  0.16  ]
hasil [:4]  : [ 0.0152 -0.0143  0.1129  0.16  ]
selisih maks: 8.88e-16

Uraian baris-per-barisnya:

  1. Baris 8 (spektrum = np.fft.rfft(getaran)): 501 koefisien setengah spektrum, dihitung ulang agar blok ini berdiri sendiri.
  2. Baris 9 (hasil = np.fft.irfft(spektrum, n=t.size)): parameter n menyatakan panjang sinyal asli yang diinginkan, di sini 1.000 sampel.
  3. Baris 10: dtype hasil float64; kita tidak perlu memanggil np.real seperti pada alur ifft.
  4. Baris 11–12: empat sampel pertama cocok nilai demi nilai hingga pembulatan 4 desimal — rekonstruksi berhasil dari setengah data.
  5. Baris 13: selisih terbesar 8.88e-16 murni residu pembulatan float64; pada skala pengukuran sensor, praktis nol.

Berapa Memori yang Dihemat? #

Setiap elemen complex128 menampung dua float64 — 16 byte per elemen. Dengan rfft, jumlah elemen spektrum turun dari N menjadi N/2 + 1. Kita ukur langsung lewat atribut nbytes pada spektrum sinyal bersih:

python
import numpy as np

n = 1000
t = np.arange(n) / n
getaran = np.sin(2 * np.pi * 3 * t)
spec_fft = np.fft.fft(getaran)
spec_rfft = np.fft.rfft(getaran)
print("elemen fft :", spec_fft.size, "->", spec_fft.nbytes, "byte")
print("elemen rfft:", spec_rfft.size, "->", spec_rfft.nbytes, "byte")
print("hemat      :", spec_fft.nbytes - spec_rfft.nbytes, "byte")

Output yang Dihasilkan:

text
elemen fft : 1000 -> 16000 byte
elemen rfft: 501 -> 8016 byte
hemat      : 7984 byte

Penjelasannya sederhana: 1.000 elemen complex128 menempati 16.000 byte, sedangkan 501 elemen hanya 8.016 byte — hampir separuh. Skalanya makin terasa untuk rekaman panjang:

Panjang sinyal NSpektrum fftSpektrum rfft
1.00016.000 byte8.016 byte
1.000.00016,0 MB≈ 8,0 MB
100.000.0001,6 GB≈ 0,8 GB

Penghematan tidak berhenti di memori: transformasi real FFT juga menjalankan lebih sedikit operasi daripada complex FFT berukuran penuh, sehingga perhitungannya lebih ringan. Untuk rekaman jutaan sampel yang dianalisis berulang-ulang, selisih ini menentukan apakah data muat di RAM.

Dengan rfft dan irfft, seluruh alur analisis sinyal riil — membaca spektrum, menyimpannya, dan mengembalikannya — bisa dikerjakan dengan setengah data. Babak berikutnya memakai keunggulan ini untuk pekerjaan yang lebih aktif: menyaring derau langsung di domain frekuensi pada Filtering Frekuensi dan ifft.