Operator Calculus, Co.
Browse Studio documentation
On this page

1D Complex Waveform with Initializer

A 1024-element complex field seeded with a unit-magnitude sinusoid at angular frequency 3:

local ctx = ooc.create()
ooc.set_timestep(ctx, 0.01)

local wave = ooc.add_field(ctx, {1024}, {
    type    = "complex_double",
    origin  = -math.pi,
    spacing = 2 * math.pi / 1024,
    initializer = function(i, x)
        return { math.cos(3 * x), math.sin(3 * x) }
    end
})

local v = wave:values()
ooc.log("wave rank=%d  first=%0.3f+%0.3fi", wave:rank(), v[1][1], v[1][2])

Spectral Representation Metadata

Tag a complex field as spectral and Hermitian-constrained when it stores the Fourier image of a real physical signal:

local ctx = ooc.create()

local spectrum = ooc.add_field(ctx, {256}, {
    type = "complex_double",
    representation = {
        domain = "spectral",
        value_kind = "complex_real_constraint",
    },
    fill = {0.0, 0.0}
})

ooc.log("domain=%s  value_kind=%s  components=%d",
    spectrum:domain(), spectrum:value_kind(), spectrum:components())

If you provide both top-level domain / value_kind and representation = { ... }, the nested representation table wins.


2D Real Field — Radial Gaussian

A 256 × 256 real field with a Gaussian blob centred at the physical origin. The initializer receives row index ix, column index iy, and their physical coordinates x, y:

local ctx = ooc.create()

local blob = ooc.add_field(ctx, {256, 256}, {
    origin  = -1.0,
    spacing = 2.0 / 256,
    initializer = function(ix, iy, x, y)
        return math.exp(-8 * (x * x + y * y))
    end
})

-- Read center value: row 128 (0-based 127), col 128 (0-based 127)
local vals  = blob:values()
local shape = blob:shape()
local cols  = shape[2]
local center = vals[(127 * cols) + 128]   -- 1-based: row*cols + col
ooc.log("center = %.4f", center)

2D Complex Field — Spiral Phase

Initialize a 512 × 512 complex field with a spiral phase pattern using math.atan:

local ctx = ooc.create()

local spiral = ooc.add_field(ctx, {512, 512}, {
    type    = "complex_double",
    origin  = -math.pi,
    spacing = 2 * math.pi / 512,
    initializer = function(ix, iy, x, y)
        local theta = math.atan(y, x)
        return { math.cos(theta), math.sin(theta) }
    end
})

Reading Parameters and Checking Layout Mid-Run

Add a field, then inspect its storage metadata, representation tags, and cached stats:

local ctx = ooc.create()
local field = ooc.add_field(ctx, {64}, {
    type = "complex_double",
    representation = {
        domain = "spectral",
        value_kind = "complex",
    },
    fill = {1.0, 0.0}
})

local info = ooc.field_info(ctx, 0)
ooc.log("format=%s  bytes=%d  elements=%d", info.format, info.byte_size, info.element_count)
ooc.log("domain=%s  value_kind=%s", field:domain(), field:value_kind())

local snapshot = field:values()
ooc.log("first element = %s", tostring(snapshot[1]))

local stats = ooc.field_stats(ctx, field)
ooc.log("rms=%.4f  max=%.4f", stats.rms, stats.max_abs)
stats:refresh()

local counts = ooc.field_continuity_counts(ctx, 0)
ooc.log("continuity dirty=%d stable=%d", counts.dirty, counts.stable)

After grabbing snapshots, feed them into Lua visualizations or write them to disk; the Field handle remains valid as long as the context is alive.


Writing Values Back with Set Values

field:set_values(table) replaces the field buffer from a Lua table. The table must have at least as many entries as the field has elements.

Real field — sine wave:

local ctx = ooc.create()
local f = ooc.add_field(ctx, {256}, { fill = 0.0 })
local data = {}
for i = 1, 256 do
    data[i] = math.sin(2 * math.pi * (i - 1) / 256)
end
f:set_values(data)

Complex field — unit phasors:

local ctx = ooc.create()
local g = ooc.add_field(ctx, {512}, { type = "complex_double" })
local data = {}
for i = 1, 512 do
    local x = 2 * math.pi * (i - 1) / 512
    data[i] = { math.cos(x), math.sin(x) }
end
g:set_values(data)

Higher-rank seeding (rank ≥ 3) — initializers are not supported for rank ≥ 3, so use set_values:

local ctx = ooc.create()
local vol = ooc.add_field(ctx, {32, 32, 32}, { fill = 0.0 })
local data = {}
for i = 1, 32 * 32 * 32 do data[i] = math.random() end
vol:set_values(data)

One-Shot Stats with Field Stats Compute

field_stats_compute returns an immediate stats table without needing a step context:

local ctx = ooc.create()
local field = ooc.add_field(ctx, {128}, {
    type = "complex_double",
    initializer = function(i, x)
        local phase = 2 * math.pi * i / 128
        return { math.cos(phase), math.sin(phase) }
    end
})

local s = ooc.field_stats_compute(field)
ooc.log("mean=%.4f  rms=%.4f  max=%.4f  coherence=%.4f",
    s.mean, s.rms, s.max_abs, s.phase_coherence)

Custom Stats with the Accumulator API

Process a subset of elements (e.g. every other row) and compute stats manually:

local ctx = ooc.create()
local field = ooc.add_field(ctx, {32, 32}, {
    initializer = function(ix, iy, x, y)
        return math.sin(0.2 * ix) * math.cos(0.15 * iy)
    end
})

local shape  = field:shape()
local cols   = shape[2]
local vals   = field:values()
local acc    = ooc.field_stats_accumulator_begin()

-- Accumulate only the even rows
for row = 1, shape[1], 2 do
    for col = 1, cols do
        local v = vals[(row - 1) * cols + col]
        ooc.field_stats_accumulate_real(acc, v)
    end
end

local result = ooc.field_stats_accumulator_finish(acc)
ooc.log("even-row rms=%.4f  count=%d", result.rms, result.count)

Reuse an accumulator across frames to avoid repeated allocation:

local ctx = ooc.create()
local field = ooc.add_field(ctx, {64}, {
    initializer = function(i, x)
        return math.sin(2 * math.pi * i / 64)
    end
})

local acc = ooc.field_stats_accumulator_begin()

for step = 1, 100 do
    ooc.field_stats_accumulator_begin(acc)   -- reset in-place
    for _, v in ipairs(field:values()) do
        ooc.field_stats_accumulate_real(acc, v)
    end
    local s = ooc.field_stats_accumulator_finish(acc)
    ooc.log("step %d  rms=%.4f", step, s.rms)
end

Full Spectral + Phase Pipeline

Compute spectral entropy and phase-coherence metrics for a complex field:

local ctx = ooc.create()
local field = ooc.add_field(ctx, {256}, {
    type = "complex_double",
    initializer = function(i, x)
        local phase = 2 * math.pi * i / 256
        return { math.cos(phase), math.sin(phase) }
    end
})

-- Step 1: base stats
local s = ooc.field_stats_compute(field)

-- Step 2: spectral stats — updates s.spectral_entropy, s.spectral_bandwidth, etc.
local ok, dom_k = ooc.field_stats_compute_spectral_view(field, s)

-- Step 3: phase metrics — updates s.phase_coherence, s.phase_coherence_ema, etc.
if ok then
    ooc.field_stats_compute_phase_metrics(field, s, nil, dom_k)
end

-- Step 4: hysteresis lock state (requires ctx + field index)
ooc.field_stats_update_phase_lock(ctx, 0, s)

ooc.log("entropy=%.3f  coherence=%.3f  ema=%.3f  lock=%d",
    s.spectral_entropy, s.phase_coherence, s.phase_coherence_ema, s.phase_lock_state)

Tuning Phase-Lock Hysteresis

Adjust the global phase config before starting a run to set lock/unlock thresholds and EMA smoothing:

ooc.field_stats_set_phase_config({
    lock_on            = 0.85,
    lock_off           = 0.70,
    smoothing_constant = 0.05,
    weighted           = true,
    deramp_enabled     = false,
})

local cfg = ooc.field_stats_get_phase_config()
ooc.log("lock_on=%.2f  lock_off=%.2f  alpha=%.3f",
    cfg.lock_on, cfg.lock_off, cfg.smoothing_constant)

Iterating All Fields in a Context

local ctx = ooc.create()
ooc.add_field(ctx, {32}, { fill = 0.0 })
ooc.add_field(ctx, {32}, {
    type = "complex_double",
    representation = {
        domain = "spectral",
        value_kind = "complex",
    },
    fill = {1.0, 0.0}
})

for i = 0, ooc.field_count(ctx) - 1 do
    local info  = ooc.field_info(ctx, i)
    local stats = ooc.field_stats(ctx, i)
    ooc.log("[%d] %s  rms=%.4f  max=%.4f",
        i, info.format, stats.rms, stats.max_abs)
end