@@ -395,22 +395,6 @@ z-rotated
395395; ; **Frequency** is just **how many times the point goes around the circle during one period**.
396396; ; That's it. No mysterious wave physics—just counting rotations.
397397
398- ; ; ### What k=0 Means: The DC Component
399-
400- ; ; **k=0** means **no rotation**—a point that doesn't move around the circle. It stays at the same
401- ; ; angle all the time. This represents the **constant/average value** of the signal.
402- ; ;
403- ; ; For our temperature data, DFT[0] tells us the sum of all temperatures:
404-
405- (dfn/sum temperatures)
406-
407- ; ; The **average temperature** is this sum divided by N:
408-
409- (dfn/mean temperatures)
410-
411- ; ; The name "DC" comes from electrical engineering ("direct current"—electricity that doesn't alternate),
412- ; ; but the idea is simple: it's the baseline level around which the signal oscillates.
413-
414398; ; ### What k=N/2 Means: The Nyquist Frequency
415399
416400; ; **k=N/2** (for our 8-sample signal, k=4) is the **fastest oscillation detectable**. It rotates
@@ -517,22 +501,26 @@ z-rotated
517501
518502; ; **Reading this spectrum:**
519503; ; - k=0: The constant component (average temperature, also called DC for "direct current" from electrical engineering)
520- ; ; - k=1: Dominant —one cycle per period (the daily pattern)
504+ ; ; - k=1: Largest oscillating component —one cycle per period (the daily pattern)
521505; ; - k=2, k=3: Smaller contributions (higher harmonics—multiples of the base frequency)
506+ ; ;
507+ ; ; **Why is k=1 the dominant oscillation?** The k=0 component (DC) is large because it represents
508+ ; ; the sum of all temperatures, but it's not an oscillation—it's just the baseline. Among the
509+ ; ; oscillating components (k≥1), k=1 has by far the largest magnitude, revealing a strong daily cycle.
522510
523511; ; The DFT has revealed that our temperatures are dominated by a **daily cycle** (k=1),
524512; ; with some higher-frequency variation.
525513
526514; ; ## Computational Note: Naive DFT vs FFT
527515
528516; ; The DFT formula we've described—computing inner products for each frequency—requires
529- ; ; **$O(N^2)$ operations** ("order N-squared" — roughly N×N multiplications, since for each
530- ; ; of N frequencies, we sum over N samples) .
517+ ; ; **[ $O(N^2)$](https://en.wikipedia.org/wiki/Big_O_notation) operations**: roughly N×N multiplications, since for each
518+ ; ; of N frequencies, we sum over N samples.
531519; ;
532520; ; For 1024 samples, that's ~1 million multiplications (1024 × 1024).
533521; ;
534522; ; The **Fast Fourier Transform (FFT)** computes the exact same result using clever
535- ; ; algebraic factorization, reducing complexity to **$O(N \log N)$** ("order N log N") —only
523+ ; ; algebraic factorization, reducing complexity to **$O(N \log N)$**—only
536524; ; ~10,000 operations for 1024 samples, a 100× speedup!
537525; ;
538526; ; The FFT is one of the most important algorithms in computing. It doesn't change
@@ -546,15 +534,32 @@ z-rotated
546534
547535; ; Let's build signals with known frequency content and verify the DFT finds them.
548536
537+ ; ; Helper function to extract magnitudes from DFT results:
538+
539+ (defn extract-magnitudes [spectrum n]
540+ (mapv (fn [k]
541+ (let [real (nth spectrum (* 2 k))
542+ imag (nth spectrum (inc (* 2 k)))]
543+ {:frequency k
544+ :magnitude (Math/sqrt (+ (* real real) (* imag imag)))}))
545+ (range n)))
546+
547+ ; ; For these examples, create a 1-second signal sampled at 100 Hz:
548+
549+ (def sample-rate 100.0 )
550+ (def duration 1.0 )
551+ (def n-samples (int (* sample-rate duration)))
552+ (def sample-times (dfn// (range n-samples) sample-rate))
553+
549554; ; ### Example 1: Sum of Two Pure Frequencies
550555
551556; ; Create a signal with exactly two frequency components: 5 Hz and 15 Hz
552557
553558(def signal-two-freqs
554- (dfn/+ (dfn/sin (dfn/* 2.0 Math/PI 5.0 time ))
555- (dfn/* 0.5 (dfn/sin (dfn/* 2.0 Math/PI 15.0 time )))))
559+ (dfn/+ (dfn/sin (dfn/* 2.0 Math/PI 5.0 sample-times ))
560+ (dfn/* 0.5 (dfn/sin (dfn/* 2.0 Math/PI 15.0 sample-times )))))
556561
557- (-> (tc/dataset {:time (vec time ) :value (vec signal-two-freqs)})
562+ (-> (tc/dataset {:time (vec sample-times ) :value (vec signal-two-freqs)})
558563 (plotly/base {:=x :time
559564 :=y :value
560565 :=x-title " Time (seconds)"
@@ -592,7 +597,7 @@ z-rotated
592597(def noise (dfn/* 0.3 (dfn/- (repeatedly n-samples rand) 0.5 )))
593598(def signal-noisy (dfn/+ signal-two-freqs noise))
594599
595- (-> (tc/dataset {:time (vec time ) :value (vec signal-noisy)})
600+ (-> (tc/dataset {:time (vec sample-times ) :value (vec signal-noisy)})
596601 (plotly/base {:=x :time
597602 :=y :value
598603 :=x-title " Time (seconds)"
@@ -628,7 +633,7 @@ z-rotated
628633
629634; ; Create a signal at 7.3 Hz (doesn't align with DFT bins)
630635
631- (def signal-non-integer (dfn/sin (dfn/* 2.0 Math/PI 7.3 time )))
636+ (def signal-non-integer (dfn/sin (dfn/* 2.0 Math/PI 7.3 sample-times )))
632637(def spectrum-non-integer (t/forward-1d dft-transformer signal-non-integer))
633638(def mags-non-integer (extract-magnitudes spectrum-non-integer 20 ))
634639
@@ -819,24 +824,19 @@ inner-product-diff-freq
819824
820825; ; ### Demonstration: A Pure Sine Wave
821826
822- ; ; Let's create a pure sine wave at exactly 10 Hz, sampled at 100 Hz for 1 second (100 samples):
823-
824- (def sample-rate 100.0 )
825- (def duration 1.0 )
826- (def n-samples (int (* sample-rate duration)))
827- (def time (dfn// (range n-samples) sample-rate))
827+ ; ; Let's create a pure sine wave at exactly 10 Hz (using our 1-second, 100 Hz sample rate from earlier):
828828
829829; ; Case 1: Frequency that fits perfectly (10 Hz - exactly 10 cycles in 1 second)
830- (def sine-perfect (dfn/sin (dfn/* 2.0 Math/PI 10.0 time )))
830+ (def sine-perfect (dfn/sin (dfn/* 2.0 Math/PI 10.0 sample-times )))
831831
832832; ; Case 2: Frequency that doesn't fit (10.5 Hz - creates discontinuity)
833- (def sine-leaky (dfn/sin (dfn/* 2.0 Math/PI 10.5 time )))
833+ (def sine-leaky (dfn/sin (dfn/* 2.0 Math/PI 10.5 sample-times )))
834834
835835; ; Visualize the signals at the boundaries
836836
837837(-> (tc/concat
838- (tc/dataset {:time (vec time ) :value (vec sine-perfect) :signal " 10 Hz (perfect fit)" })
839- (tc/dataset {:time (vec time ) :value (vec sine-leaky) :signal " 10.5 Hz (discontinuous)" }))
838+ (tc/dataset {:time (vec sample-times ) :value (vec sine-perfect) :signal " 10 Hz (perfect fit)" })
839+ (tc/dataset {:time (vec sample-times ) :value (vec sine-leaky) :signal " 10.5 Hz (discontinuous)" }))
840840 (plotly/base {:=x :time
841841 :=y :value
842842 :=color :signal
@@ -858,14 +858,6 @@ inner-product-diff-freq
858858(def spectrum-perfect (t/forward-1d dft-transformer sine-perfect))
859859(def spectrum-leaky (t/forward-1d dft-transformer sine-leaky))
860860
861- (defn extract-magnitudes [spectrum n]
862- (mapv (fn [k]
863- (let [real (nth spectrum (* 2 k))
864- imag (nth spectrum (inc (* 2 k)))]
865- {:frequency k
866- :magnitude (Math/sqrt (+ (* real real) (* imag imag)))}))
867- (range n)))
868-
869861(def mags-perfect (extract-magnitudes spectrum-perfect 30 ))
870862(def mags-leaky (extract-magnitudes spectrum-leaky 30 ))
871863
0 commit comments