Operator Calculus, Co.
Browse Studio documentation
On this page

Quick Reference ๐Ÿ“Œ

  • Obtain a handle: add_field at creation, or get_field(ctx, index) to look up an existing field.
  • Inspect: :rank(), :shape(), :components(), :domain(), :value_kind(), and :values() snapshot the field into Lua.
  • Write back: :set_values(table) replaces the field buffer from a Lua table.
  • Statistics: field_stats(ctx, field) for a lazy step-cached proxy; field_stats_compute(field) for an immediate one-shot result; the accumulator API for manual rolling stats.
  • Spectral & phase: field_stats_compute_spectral_view and field_stats_compute_phase_metrics compute frequency-domain and phase-coherence metrics.
  • Phase lock: field_stats_update_phase_lock and the field_stats_{get,set}_phase_config functions control hysteresis-based phase-lock detection.

Instance Methods ๐Ÿ”ง

Field handles expose the following methods. __tostring returns Field(<ptr>) or Field(nil) for debug printing; garbage-collection of the handle does not touch the underlying context storage.

field:rank()

Returns the number of dimensions (tensor rank) as an integer.

local r = field:rank()   -- e.g. 2 for a {256, 256} field

field:shape()

Returns a Lua array of extents, one per axis, in declaration order (axis 1 = outermost, axis rank = innermost / fastest-varying).

local s = field:shape()  -- e.g. {256, 512}
ooc.log("%d rows ร— %d cols", s[1], s[2])

field:components()

Returns 1 for real ("double") fields and 2 for complex ("complex_double") fields.

field:domain()

Returns the fieldโ€™s representation-domain tag as a string, typically "physical" or "spectral".

ooc.log("domain=%s", field:domain())

field:value_kind()

Returns the fieldโ€™s value-kind tag as a string. Canonical outputs are "real", "complex", "complex_real_constraint", and "unknown".

ooc.log("value_kind=%s", field:value_kind())

field:values()

Materializes the entire field buffer into a Lua table (a copy). Mutating the returned table does not affect the field.

  • Real fields โ†’ flat array of numbers, row-major (axis rank varies fastest).
  • Complex fields โ†’ array of {re, im} two-element tables.
  • Raises a Lua error if the element format is unsupported.
-- Real snapshot
for i, v in ipairs(field:values()) do
    ooc.log("[%d] = %.4f", i, v)
end

-- Complex snapshot
for i, z in ipairs(field:values()) do
    ooc.log("[%d] = %.3f + %.3fi", i, z[1], z[2])
end

Tip

values() copies the full buffer into Lua, which is convenient for small fields or diagnostics. For large fields, prefer operating in-place with operators or exporting data via C-side hooks.

field:set_values(table)

Writes values from a Lua table back into the field buffer. The table must contain at least as many entries as the field has elements; excess entries are ignored.

  • Real fields โ†’ elements must be numbers.
  • Complex fields โ†’ each element may be:
    • A bare number โ†’ stored as {re = value, im = 0}.
    • A {re, im} two-element table.
  • Returns true on success; raises a Lua error if the table is too short or an element type is wrong.
-- Zero out a real field
local zeros = {}
local n = field:shape()[1] * field:shape()[2]
for i = 1, n do zeros[i] = 0.0 end
field:set_values(zeros)

-- Write complex values
local vals = {}
for i = 1, 1024 do
    local x = (i - 1) * 2 * math.pi / 1024
    vals[i] = { math.cos(x), math.sin(x) }
end
field:set_values(vals)

Context Functions ๐Ÿ“‚

field_count(ctx)

Returns the number of fields currently allocated in the context.

get_field(ctx, index)

Returns a Field handle for the given 0-based index. Raises a Lua error if the index is out of range.

local f = ooc.get_field(ctx, 0)   -- first field

field_info(ctx, index)

Returns a table describing the field at the given 0-based index, or nil if the index is missing.

Key Type Description
index integer Field index (mirrors the argument)
rank integer Number of dimensions
element_count integer Total number of elements
shape table Array of per-axis extents
format string "real_double" or "complex_double"
format_value integer Numeric enum value of format
components integer 1 (real) or 2 (complex)
element_size integer Bytes per element (8 or 16)
byte_size integer Total buffer size in bytes
local info = ooc.field_info(ctx, 0)
ooc.log("format=%s bytes=%d", info.format, info.byte_size)

Tip

field_info reports storage format such as "real_double" or "complex_double". For representation metadata like physical vs spectral or Hermitian-constrained complex values, use field:domain() and field:value_kind().


Field Statistics ๐Ÿ“Š

field_stats(ctx, index_or_field) โ€” Lazy Proxy

Returns a stats proxy whose values are cached per simulation step. On first access within a step the proxy recomputes stats; subsequent reads within the same step return the cached values.

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

Call stats:refresh() to force a recompute regardless of the step cache. It returns true when the refresh succeeds and false if the snapshot could not be updated. Use pairs(stats) to iterate a full snapshot table.

Stats Fields

Field Type Description
mean_re double Mean of real components
mean_im double Mean of imaginary components
mean_abs double Mean of absolute values |z|
rms double Root mean square of real components
var_re double Variance of real components
var_im double Variance of imaginary components
var_abs double Variance of |z|
max_abs double Maximum absolute value across all elements
phase_coherence double Kuramoto order parameter (0โ€“1); 1 = fully coherent
circularity double Complex circularity measure
spectral_entropy double Shannon entropy of the spectral power distribution
spectral_bandwidth double RMS spectral bandwidth
phase_coherence_weighted double Amplitude-weighted phase coherence
phase_coherence_ema double Exponential moving average of phase coherence
phase_coherence_k0 double Phase coherence at the dominant wavenumber
phase_sample_count integer Number of samples contributing to phase estimates
phase_lock_state integer Hysteresis lock flag (0 = unlocked, 1 = locked)
phase_regime integer Encoded phase-regime classification
count integer Total element count included in stats
continuity_dirty_ops integer Number of operators that wrote to the field (dirty) last step
continuity_stable_ops integer Number of operators that left the field unchanged last step

field_stats_compute(field) โ€” One-Shot

Immediately computes stats for the given Field handle and returns a plain table (not a proxy). No context or step index is required.

The returned table contains all fields listed above plus mean (the mean of real components, equivalent to mean_re for real fields).

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

Use this when you need an immediate snapshot outside of a step loop, or when you want to compute stats for a temporarily constructed field.


Stats Accumulator API

The accumulator API lets you compute stats incrementally over arbitrary sequences of values โ€” useful for streaming data from external sources or computing stats over a subset of elements.

field_stats_accumulator_begin([acc])

Creates a new accumulator userdata, or resets an existing one if acc is passed. Returns the accumulator handle.

local acc = ooc.field_stats_accumulator_begin()
-- Or reuse an existing accumulator:
ooc.field_stats_accumulator_begin(acc)

field_stats_accumulate_real(acc, value)

Feeds a single real sample into the accumulator.

ooc.field_stats_accumulate_real(acc, 0.42)

field_stats_accumulate_complex(acc, re, im)

Feeds a single complex sample into the accumulator.

ooc.field_stats_accumulate_complex(acc, re, im)

field_stats_accumulator_finish(acc)

Finalizes the accumulation and returns a stats table with the computed values.

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

Full accumulator example:

local acc = ooc.field_stats_accumulator_begin()
for _, v in ipairs(my_field:values()) do
    ooc.field_stats_accumulate_real(acc, v)
end
local s = ooc.field_stats_accumulator_finish(acc)
ooc.log("custom rms=%.4f max=%.4f", s.rms, s.max_abs)

Spectral & Phase Statistics

These functions require a pre-computed base stats table (from the proxy or one-shot compute) and update it with additional spectral or phase-coherence metrics.

field_stats_compute_spectral_view(field, stats_table)

Computes spectral statistics for field and updates stats_table in-place with spectral_entropy, spectral_bandwidth, and related fields.

Returns: ok (boolean), dominant_k (integer) โ€” the index of the dominant wavenumber bin. ok is false if the field is not complex or the FFT failed.

local stats = ooc.field_stats_compute(field)
local ok, dom_k = ooc.field_stats_compute_spectral_view(field, stats)
if ok then
    ooc.log("spectral_entropy=%.4f dominant_k=%d", stats.spectral_entropy, dom_k)
end

field_stats_compute_phase_metrics(field, stats_table[, config_table[, dominant_k]])

Computes phase-coherence metrics and updates stats_table in-place with phase_coherence, phase_coherence_weighted, phase_coherence_ema, phase_coherence_k0, and phase_sample_count.

Argument Required Description
field yes Field handle or field view
stats_table yes Existing stats table (updated in-place)
config_table no Phase config overrides for this call only (see Phase Configuration)
dominant_k no Dominant wavenumber from field_stats_compute_spectral_view; defaults to 0
local stats = ooc.field_stats_compute(field)
local ok, dom_k = ooc.field_stats_compute_spectral_view(field, stats)
ooc.field_stats_compute_phase_metrics(field, stats, nil, dom_k)
ooc.log("phase_coherence=%.4f ema=%.4f", stats.phase_coherence, stats.phase_coherence_ema)

field_stats_update_phase_lock(ctx, index, stats_table)

Updates the hysteresis-based phase_lock_state field in stats_table for the field at index (0-based), using the persistent lock state stored in the context. Updates stats_table in-place.

ooc.field_stats_update_phase_lock(ctx, 0, stats)
ooc.log("lock=%d regime=%d", stats.phase_lock_state, stats.phase_regime)

Phase Configuration

The phase coherence system uses a global configuration that controls EMA smoothing, hysteresis thresholds, and amplitude weighting.

field_stats_get_phase_config()

Returns the current global phase config as a table.

field_stats_set_phase_config(config_table)

Merges the provided table into the global phase config. Unknown keys are ignored.

Key Type Description
abs_threshold double Absolute amplitude threshold for phase inclusion
rel_threshold double Amplitude threshold relative to max_abs
weighted boolean Weight phase samples by amplitude
lock_on double Coherence threshold to enter locked state
lock_off double Coherence threshold to exit locked state (hysteresis)
smoothing_constant double EMA alpha for phase_coherence_ema (also accepted as smoothing_seconds)
deramp_enabled boolean Enable phase-ramp detrending before coherence computation (also accepted as deramp)
ooc.field_stats_set_phase_config({
    lock_on  = 0.85,
    lock_off = 0.70,
    smoothing_constant = 0.05,
    weighted = true,
})

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

Field Views & Continuity ๐Ÿ“

field_view_from_field(field)

Creates a lightweight field view from a Field handle. Field views are accepted by field_stats_compute_spectral_view and field_stats_compute_phase_metrics in place of a full Field handle. Useful when you want to pass a view into a function that expects either type.

local view = ooc.field_view_from_field(field)
local ok, dom_k = ooc.field_stats_compute_spectral_view(view, stats)

The returned table contains data (lightuserdata), count, type, and type_name. Treat it as an opaque bridge object tied to the lifetime of the original field.

field_continuity_counts(ctx, index)

Returns a table { index, dirty, stable } for the field at the given 0-based index, reflecting how many operators wrote to (dirty) or left unchanged (stable) the field during the most recent simulation step.

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

Usage Examples ๐Ÿ“–

Inspect a Field After Creation

local ctx = ooc.create()
local f = ooc.add_field(ctx, {8, 8}, { type = "complex_double", fill = {1, 0} })

ooc.log("rank=%d shape=%s components=%d",
    f:rank(),
    table.concat(f:shape(), "ร—"),
    f:components())

Read Values and Compute Stats

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

-- Force refresh if you need current-step values immediately:
stats:refresh()

-- Iterate all fields in a snapshot:
for key, val in pairs(stats) do
    ooc.log("%s = %s", key, tostring(val))
end

Full Spectral + Phase Pipeline

ooc.step(ctx)

local s = ooc.field_stats_compute(field)
local ok, dom_k = ooc.field_stats_compute_spectral_view(field, s)
if ok then
    ooc.field_stats_compute_phase_metrics(field, s, nil, dom_k)
    ooc.field_stats_update_phase_lock(ctx, 0, s)
    ooc.log("entropy=%.3f coherence=%.3f lock=%d", s.spectral_entropy, s.phase_coherence, s.phase_lock_state)
end