Flux Lens
On this page
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)
Note
Flux Lens is currently implemented for 1D fields only. If the selected field has rank ≠ 1,
lens.validisfalse.
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 🚀
- 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().
API 📚
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
| 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 units. |
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 buckets. |
absS_total |
double |
Total absolute work: . |
max_k |
double |
Nyquist wavenumber ( for a length- field). |
bucket_capacity |
integer |
Allocated capacity of the bucket buffers. |
bucket_count |
integer |
Number of populated buckets. |
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
| 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 📊
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_count
Band
local 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_capacity
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
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_period
Field Selection
ooc.flux_lens_set_field_index(ctx, index) -- 0-based integer index
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 units. |
flux_lens_set_use_dealias(ctx, enabled) |
boolean |
Toggle 2/3 dealiasing on the nonlinear product. |
Lock & Band Manipulation
ooc.flux_lens_set_locked(ctx, true) -- freeze band_lo/hi; marks still update
ooc.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| units
scale_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
ooc.flux_lens_set_band_low(ctx, k_low) -- set band_lo explicitly; auto-locks the lens
ooc.flux_lens_set_band_high(ctx, k_high) -- set band_hi explicitly; auto-locks the lens
Tip
flux_lens_set_band_lowandflux_lens_set_band_highautomatically callflux_lens_set_locked(ctx, true)if the lens is not already locked — preventing mark-based updates from overriding your explicit bounds on the next recompute.
Examples 🧪
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)
end
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
ooc.flux_lens_set_enabled(ctx, true)
ooc.flux_lens_set_band_low(ctx, 2.0) -- auto-locks
ooc.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
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 index
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])
end
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