|
301 | 301 | ;; Why would a transform of real data produce complex output? This seems unnecessarily |
302 | 302 | ;; complicated. Let's build intuition for why complex numbers are not just mathematical |
303 | 303 | ;; elegance—they're computational necessity. |
| 304 | +;; |
| 305 | +;; **Note on dtype-next operations**: In the examples below, you'll see operations like `dfn/*` |
| 306 | +;; and `dfn//`. These are element-wise operations from dtype-next: |
| 307 | +;; - `dfn/*` multiplies arrays element-by-element: `[1 2 3] * 2 → [2 4 6]` |
| 308 | +;; - `dfn//` divides arrays element-by-element: `[2 4 6] / 2 → [1 2 3]` |
| 309 | +;; - `dfn/+`, `dfn/-` work similarly for addition and subtraction |
| 310 | +;; - `dfn/cos`, `dfn/sin` apply trigonometric functions to each element |
| 311 | +;; |
| 312 | +;; We'll explore these operations more deeply as we use them throughout the tutorial. |
304 | 313 |
|
305 | 314 | ;; ### The Problem: Sines and Cosines Are Both Needed |
306 | 315 |
|
|
508 | 517 | ;; |
509 | 518 | ;; This same pattern works for DCT, DST, DHT, and wavelets—just change the transform type! |
510 | 519 |
|
| 520 | +;; ### Performance: JTransforms and Parallelism |
| 521 | +;; |
| 522 | +;; Under the hood, fastmath uses [JTransforms](https://github.com/wendykierp/JTransforms), |
| 523 | +;; a high-performance Java library that automatically parallelizes large transforms. |
| 524 | +;; |
| 525 | +;; **Key performance features**: |
| 526 | +;; - **Automatic parallelization**: For arrays larger than ~8K elements, JTransforms splits |
| 527 | +;; work across available CPU cores using Java's `ForkJoinPool` |
| 528 | +;; - **SIMD optimizations**: Uses Java's auto-vectorization where possible |
| 529 | +;; - **In-place transforms**: Can operate directly on input arrays (fastmath wraps this safely) |
| 530 | +;; - **Algorithm selection**: Chooses optimal FFT algorithm (split-radix, mixed-radix, or |
| 531 | +;; Bluestein) based on array size |
| 532 | +;; |
| 533 | +;; **What this means for you**: You don't need to think about parallelism—just pass large |
| 534 | +;; arrays and JTransforms automatically uses all available cores. For smaller signals (<8K), |
| 535 | +;; the overhead of parallelization exceeds the benefit, so it runs single-threaded. |
| 536 | +;; |
| 537 | +;; **Performance tip**: Reuse transformer objects! The constructor pre-computes lookup tables |
| 538 | +;; (twiddle factors), so creating a transformer once and reusing it is much faster than |
| 539 | +;; creating a new one for each transform. |
| 540 | + |
511 | 541 | ;; ### Understanding the DFT: Signal Decomposition |
512 | 542 | ;; |
513 | 543 | ;; The [Discrete Fourier Transform (DFT)](https://en.wikipedia.org/wiki/Discrete_Fourier_transform) |
|
971 | 1001 | ;; |
972 | 1002 | ;; Signal = a₁·ψ(t-1) + a₂·ψ(2t-1) + a₃·ψ(2t-2) + ... |
973 | 1003 | ;; |
| 1004 | +;; **⚠️ Important**: Most wavelet transforms (Haar, Daubechies, Symlets) require **power-of-2 |
| 1005 | +;; array sizes** (64, 128, 256, 512, etc.). This is because wavelets work by recursively |
| 1006 | +;; splitting signals in half. If your signal isn't a power of 2, you'll need to pad it: |
| 1007 | +;; |
| 1008 | +;; ```clojure |
| 1009 | +;; ;; Pad to next power of 2 |
| 1010 | +;; (let [n (dtype/ecount signal) |
| 1011 | +;; target-n (int (Math/pow 2 (Math/ceil (/ (Math/log n) (Math/log 2)))))] |
| 1012 | +;; ;; ... pad signal to target-n ...) |
| 1013 | +;; ``` |
| 1014 | +;; |
| 1015 | +;; We'll show the padding pattern in our examples below. |
| 1016 | +;; |
974 | 1017 | ;; Where ψ is a localized wavelet function at different scales and positions. |
975 | 1018 | ;; |
976 | 1019 | ;; See also: [Wavelet Transform](https://en.wikipedia.org/wiki/Wavelet_transform) |
|
1551 | 1594 | {:category "Array access" :functions "dtype/ecount, dtype/get-value, dtype/sub-buffer"} |
1552 | 1595 | {:category "Search" :functions "argops/argmax - find index of maximum"}]) |
1553 | 1596 |
|
1554 | | -;; ### Common Patterns |
| 1597 | +;; ### Common dtype-next Patterns - Quick Reference |
1555 | 1598 |
|
1556 | | -;; Throughout signal processing, certain computational patterns appear repeatedly. |
1557 | | -;; Root mean square error combines difference, squaring, and averaging to measure |
1558 | | -;; signal similarity: |
1559 | | -;; |
1560 | | -;; (Math/sqrt (dfn/mean (dfn/sq (dfn/- signal1 signal2)))) |
1561 | | -;; |
1562 | | -;; Signal energy sums the squares of all values: |
1563 | | -;; |
1564 | | -;; (dfn/sum (dfn/sq signal)) |
1565 | | -;; |
1566 | | -;; Normalization centers and scales a signal to zero mean and unit variance: |
1567 | | -;; |
1568 | | -;; (dfn// (dfn/- signal (dfn/mean signal)) (dfn/standard-deviation signal)) |
1569 | | -;; |
1570 | | -;; Signal-to-noise ratio ([SNR](https://en.wikipedia.org/wiki/Signal-to-noise_ratio)) |
1571 | | -;; in decibels compares signal power to noise power: |
1572 | | -;; |
1573 | | -;; (* 20 (Math/log10 (/ (dfn/mean (dfn/sq signal)) |
1574 | | -;; (dfn/mean (dfn/sq noise))))) |
| 1599 | +;; Throughout the tutorial, you've seen these dtype-next patterns repeatedly. Here's a |
| 1600 | +;; quick reference with working examples you can use in your own code. |
| 1601 | + |
| 1602 | +;; **Root Mean Square Error (RMSE)** - measures how different two signals are: |
| 1603 | +(defn compute-rmse [signal1 signal2] |
| 1604 | + (Math/sqrt (dfn/mean (dfn/sq (dfn/- signal1 signal2))))) |
| 1605 | + |
| 1606 | +;; Example: Compare original and compressed |
| 1607 | +(let [original (range 100) |
| 1608 | + noisy (dfn/+ original 0.1)] |
| 1609 | + (compute-rmse original noisy)) |
| 1610 | + |
| 1611 | +;; **Signal Energy** - total power in a signal: |
| 1612 | +(defn signal-energy [signal] |
| 1613 | + (dfn/sum (dfn/sq signal))) |
| 1614 | + |
| 1615 | +;; Example: Energy before and after filtering |
| 1616 | +(signal-energy (:signal (:pure-sine signals))) |
| 1617 | + |
| 1618 | +;; **Normalization** - zero mean, unit variance: |
| 1619 | +(defn normalize [signal] |
| 1620 | + (dfn// (dfn/- signal (dfn/mean signal)) |
| 1621 | + (dfn/standard-deviation signal))) |
| 1622 | + |
| 1623 | +;; Example: Normalize before processing |
| 1624 | +(let [normalized (normalize (:signal (:two-tones signals)))] |
| 1625 | + {:mean (dfn/mean normalized) |
| 1626 | + :std (dfn/standard-deviation normalized)}) |
| 1627 | + |
| 1628 | +;; **Signal-to-Noise Ratio ([SNR](https://en.wikipedia.org/wiki/Signal-to-noise_ratio)) in dB**: |
| 1629 | +(defn snr-db [signal noise] |
| 1630 | + (* 20 (Math/log10 (/ (dfn/mean (dfn/sq signal)) |
| 1631 | + (dfn/mean (dfn/sq noise)))))) |
| 1632 | + |
| 1633 | +;; Example: Measure compression quality |
| 1634 | +;; (let [signal (:signal (:smooth signals)) |
| 1635 | +;; noise (dfn/- signal compressed-signal)] |
| 1636 | +;; (snr-db signal noise)) |
1575 | 1637 |
|
1576 | 1638 | ;; ### Transform Selection Flowchart |
1577 | 1639 |
|
|
0 commit comments