Flux Lens
What It Is 🧠
Section titled “What It Is 🧠”The Flux Lens is a context-level diagnostic that computes a spectral energy-transfer spectrum from a 1D field and uses it to maintain a tapered bandpass reconstruction in both spectral and physical space.
It is designed to answer:
- Where is the dominant spectral energy transfer? (
kc) - How concentrated is that transfer? (
k50,k90) - What does the corresponding band-limited signal look like? (
band_phys,band_spec)
Mathematical Formulation 📐
Section titled “Mathematical Formulation 📐”The Flux Lens uses a skew-Burgers spectral flux to estimate inter-scale energy transfer.
Given a real or complex field with spectral coefficients :
Step 1 — Nonlinear term. Form the squared field and FFT it to get . Compute the skew-Burgers nonlinear term:
Step 2 — Mode-by-mode energy exchange rate. The work done by mode :
where denotes the complex conjugate.
Step 3 — Cumulative flux. Sort modes by and accumulate:
is the net energy flux from scales larger than to scales smaller than . Peaks and sign changes identify dominant energy cascades.
Marks derived from :
| Mark | Definition |
|---|---|
kc | Wavenumber of peak flux magnitude: |
k50 | Smallest such that |
k90 | Smallest such that |
Dealiasing. When use_dealias = true, all modes above the 2/3 rule cutoff are zeroed before the nonlinear product:
Quick Start 🚀
Section titled “Quick Start 🚀”- Enable the lens with
flux_lens_set_enabled(ctx, true). - Optionally select a field with
flux_lens_set_field_index. - Read scalars from
ooc.flux_lens(ctx)and arrays fromlens:buckets()/lens:band().
Create Proxy
Section titled “Create Proxy”local lens = ooc.flux_lens(ctx)Returns a step-cached snapshot proxy. Fields are read lazily; the proxy refreshes automatically at step boundaries. Call lens:refresh() to force an immediate update.
Snapshot Fields
Section titled “Snapshot Fields”| Field | Type | Description |
|---|---|---|
enabled | boolean | Lens is enabled; disabled lenses free their FFT buffers. |
locked | boolean | true when band_lo/band_hi are frozen and will not track marks automatically. |
force_update | boolean | A refresh was explicitly requested for the next recompute. |
band_ready | boolean | band() arrays are populated and valid. |
valid | boolean | Convenience mirror of marks.valid; true when the field is 1D and the computation succeeded. |
field_index | integer | 0-based index of the field driving the lens. |
use_dealias | boolean | Whether 2/3 dealiasing is applied before the nonlinear product. |
smoothing | double | Current EMA smoothing factor for band tracking. |
min_bandwidth | double | Minimum enforced band_hi - band_lo. |
update_period | integer | Minimum accepted steps between recomputes (0 = every step). |
last_update_step | integer | Step index of the most recent recompute. |
band_lo, band_hi | double | Current bandpass bounds in $ |
target_band_lo, target_band_hi | double | Target bounds derived from marks (before EMA smoothing). |
band_max_component | double | Largest component value in the current band snapshot. |
band_max_magnitude | double | Largest complex magnitude in the current band snapshot. |
kc | double | Flux-peak wavenumber. |
k50 | double | 50 % cumulative-flux wavenumber. |
k90 | double | 90 % cumulative-flux wavenumber. |
pi_at_kc | double | value at . |
pi_min, pi_max | double | Flux extremes across all $ |
absS_total | double | Total absolute work: $\sum_k |
max_k | double | Nyquist wavenumber ( for a length- field). |
bucket_capacity | integer | Allocated capacity of the bucket buffers. |
bucket_count | integer | Number of populated $ |
band_capacity | integer | Allocated length of the band() arrays. |
marks | table | Nested mark snapshot {valid, kc, k50, k90, pi_at_kc, total_abs_work, pi_min, pi_max, kmax}. |
Proxy Methods
Section titled “Proxy Methods”| Method | Returns | Description |
|---|---|---|
lens:refresh() | boolean | Force an immediate snapshot update; returns true when a fresh snapshot could be read. |
lens:buckets() | table | Per-bucket arrays {k, S, absS, pi} (1-based, length = bucket_count). |
lens:band() | table | {spec, phys} complex-pair arrays of the band-limited signal (length = band_capacity). |
Array Layouts 📊
Section titled “Array Layouts 📊”Buckets
Section titled “Buckets”local b = lens:buckets()-- b.k[i] : wavenumber |k| of bucket i-- b.S[i] : energy exchange rate S(k)-- b.absS[i] : |S(k)|-- b.pi[i] : cumulative flux Π(k)-- All arrays have the same length: lens.bucket_countlocal band = lens:band()-- band.spec[i] : spectral-domain complex sample {re, im}-- band.phys[i] : physical-domain complex sample {re, im}-- Both arrays have length: lens.band_capacityControl Functions 🧰
Section titled “Control Functions 🧰”All control functions take ctx as the first argument and return true on success, false if the lens is not initialised.
Enable / Disable
Section titled “Enable / Disable”ooc.flux_lens_set_enabled(ctx, true) -- enable (allocates FFT buffers on first use)ooc.flux_lens_set_enabled(ctx, false) -- disable (frees internal buffers)ooc.flux_lens_force_refresh(ctx) -- trigger immediate recompute regardless of update_periodField Selection
Section titled “Field Selection”ooc.flux_lens_set_field_index(ctx, index) -- 0-based integer indexUpdate Rate & Smoothing
Section titled “Update Rate & Smoothing”| Function | Parameter | Description |
|---|---|---|
flux_lens_set_update_period(ctx, steps) | integer ≥ 0 | Minimum accepted steps between recomputes; 0 = every step. |
flux_lens_set_smoothing(ctx, alpha) | double ∈ [0, 1] | EMA factor for band motion: 0 = instant snap, 1 = frozen. |
flux_lens_set_min_bandwidth(ctx, width) | double | Enforce a minimum band_hi − band_lo in $ |
flux_lens_set_use_dealias(ctx, enabled) | boolean | Toggle 2/3 dealiasing on the nonlinear product. |
Lock & Band Manipulation
Section titled “Lock & Band Manipulation”ooc.flux_lens_set_locked(ctx, true) -- freeze band_lo/hi; marks still updateooc.flux_lens_set_locked(ctx, false) -- allow band to track marks automatically
ooc.flux_lens_scale_width(ctx, scale) -- multiply (band_hi − band_lo) by scale (>1 wider, <1 narrower)ooc.flux_lens_shift_center(ctx, shift) -- translate band center by shift in |k| unitsscale_width and shift_center operate on the current band_lo/hi when the lens has a valid snapshot, otherwise on target_band_lo/hi.
Direct Band Bounds
Section titled “Direct Band Bounds”ooc.flux_lens_set_band_low(ctx, k_low) -- set band_lo explicitly; auto-locks the lensooc.flux_lens_set_band_high(ctx, k_high) -- set band_hi explicitly; auto-locks the lensExamples 🧪
Section titled “Examples 🧪”Minimal enable + marks readout
Section titled “Minimal enable + marks readout”ooc.flux_lens_set_enabled(ctx, true)ooc.flux_lens_set_update_period(ctx, 30)
local lens = ooc.flux_lens(ctx)ooc.step(ctx)
lens:refresh()if lens.valid then ooc.log("kc=%.3f k50=%.3f k90=%.3f pi_max=%.4f", lens.kc, lens.k50, lens.k90, lens.pi_max)endSlow refresh + lock band at current marks
Section titled “Slow refresh + lock band at current marks”ooc.flux_lens_set_enabled(ctx, true)ooc.flux_lens_set_update_period(ctx, 200)
for i = 1, 500 do ooc.step(ctx) end
ooc.flux_lens_force_refresh(ctx)local lens = ooc.flux_lens(ctx)lens:refresh()
ooc.flux_lens_set_locked(ctx, true)ooc.log("band locked at [%.2f, %.2f]", lens.band_lo, lens.band_hi)Explicit band bounds with direct setters
Section titled “Explicit band bounds with direct setters”ooc.flux_lens_set_enabled(ctx, true)ooc.flux_lens_set_band_low(ctx, 2.0) -- auto-locksooc.flux_lens_set_band_high(ctx, 8.0) -- already locked; updates hi bound only
local lens = ooc.flux_lens(ctx)lens:refresh()ooc.log("band=[%.1f, %.1f] locked=%s", lens.band_lo, lens.band_hi, tostring(lens.locked))Select which field drives the lens
Section titled “Select which field drives the lens”local a = ooc.add_field(ctx, {512}, { type = "complex_double", fill = {0, 0} })local b = ooc.add_field(ctx, {512}, { type = "complex_double", fill = {0, 0} })
ooc.flux_lens_set_enabled(ctx, true)ooc.flux_lens_set_field_index(ctx, 1) -- 0-based field indexBucket export for analysis
Section titled “Bucket export for analysis”ooc.flux_lens_force_refresh(ctx)local lens = ooc.flux_lens(ctx)lens:refresh()
local b = lens:buckets()for i = 1, #b.k do ooc.log("k=%.3f S=%+.4f Pi=%.4f", b.k[i], b.S[i], b.pi[i])endBand-limited physical signal
Section titled “Band-limited physical signal”ooc.flux_lens_set_enabled(ctx, true)ooc.flux_lens_set_smoothing(ctx, 0.05)ooc.flux_lens_set_min_bandwidth(ctx, 1.0)
for i = 1, 100 do ooc.step(ctx) end
local lens = ooc.flux_lens(ctx)lens:refresh()if lens.band_ready then local band = lens:band() local mid = math.floor(lens.band_capacity / 2) + 1 ooc.log("band phys centre: %.4f + %.4fi", band.phys[mid][1], band.phys[mid][2])end