Skip to content

Commit c3e69b7

Browse files
committed
transforms wip
1 parent 8d3b192 commit c3e69b7

1 file changed

Lines changed: 35 additions & 43 deletions

File tree

src/dsp/fourier_from_finite_sequences.clj

Lines changed: 35 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)