Skip to content
Oakfield Operator Calculus Function Reference Site

Utility Operators

add_coordinate_operator(ctx, field, opts)

Generate index or coordinate-mapped values into a field. Creates spatial basis functions, gradients, positional encodings, or time-varying coordinate masks.

add_coordinate_operator(ctx, field, [options]) -> operator

Returns: Operator handle (userdata)

Index Mode:

For a field with NN elements, the raw coordinate at index ii is simply ii.

Coordinate Modes:

  • axis: c=xjc = x_j or c=yjc = y_j depending on coord_axis
  • angle: c=xcos(θ)+ysin(θ)c = x \cos(\theta) + y \sin(\theta) where θ\theta is coord_angle
  • radial: c=(xcxvxt)2+(ycyvyt)2c = \sqrt{(x - c_x - v_x t)^2 + (y - c_y - v_y t)^2} with optional moving center
  • separable: c=f(x)g(y)c = f(x) \circ g(y) where \circ is multiply or add

Normalization:

cnorm={cnonec/(N1)unit[0,1]c/(N1)0.5centered[0.5,0.5]2c/(N1)1signed[1,1]c_{\text{norm}} = \begin{cases} c & \text{none} \\ c / (N-1) & \text{unit} \in [0, 1] \\ c / (N-1) - 0.5 & \text{centered} \in [-0.5, 0.5] \\ 2c / (N-1) - 1 & \text{signed} \in [-1, 1] \end{cases}

Final output:

outi=gaincnorm+bias\text{out}_i = \text{gain} \cdot c_{\text{norm}} + \text{bias}

Core Parameters:

ParameterTypeDefaultRangeDescription
modeenum "index"index, coordSource mapping mode
normalizeenum "none"see belowNormalization applied to coordinate
gaindouble 1.0unboundedMultiplicative gain after normalization
biasdouble 0.0unboundedAdditive bias after gain
time_offsetdouble 0.0unboundedTime offset for coordinate evaluation
accumulateboolean falseAdd to output instead of overwriting
scale_by_dtboolean trueScale accumulated writes by dt

Normalization options: none, unit, centered, signed

Coordinate Mode Parameters (when mode = "coord"):

ParameterTypeDefaultRangeDescription
coord_modeenum "axis"see belowCoordinate mapping mode
coord_axisenum "x"x, yAxis for axis-mode
coord_combineenum "multiply"multiply, addCombine rule for separable mode
coord_angledouble 0.0radiansAngle for angle-mode

Coordinate mode options: axis, angle, radial, separable

Spatial Parameters:

ParameterTypeDefaultRangeDescription
origin_xdouble 0.0unboundedX origin (mapped to index 0)
origin_ydouble 0.0unboundedY origin (mapped to index 0)
spacing_xdouble 1.0>0Grid spacing in X direction
spacing_ydouble 1.0>0Grid spacing in Y direction

Radial Mode Parameters:

ParameterTypeDefaultRangeDescription
coord_center_xdouble 0.0unboundedRadial center X position
coord_center_ydouble 0.0unboundedRadial center Y position
coord_velocity_xdouble 0.0unboundedRadial center X velocity (units/s)
coord_velocity_ydouble 0.0unboundedRadial center Y velocity (units/s)
  • Operates elementwise; no boundary handling required
  • Output field is overwritten (or accumulated into) regardless of initial state
  • For 2D fields, assumes row-major layout with spacing_x and spacing_y
  • Unconditionally stable (pure assignment operation)
  • Time-varying radial coordinates depend on simulation time; ensure time_offset aligns with your integration scheme
  • No temporal accumulation issues when accumulate = false
  • Lightweight pointwise operation; O(N) complexity
  • Radial mode requires square root per element
  • Moving radial center (nonzero velocity) uses simulation time
  • Consider precomputing static coordinates once rather than every timestep
-- Linear index ramp normalized to [0, 1]
ooc.add_coordinate_operator(ctx, field, {
mode = "index",
normalize = "unit"
})
-- Signed index ramp [-1, 1]
ooc.add_coordinate_operator(ctx, field, {
mode = "index",
normalize = "signed",
gain = 2.0,
bias = 0.5
})
-- X-axis coordinate with custom spacing
ooc.add_coordinate_operator(ctx, field, {
mode = "coord",
coord_mode = "axis",
coord_axis = "x",
spacing_x = 0.1,
origin_x = -5.0,
normalize = "none"
})
-- Radial distance from center of 256x256 field
ooc.add_coordinate_operator(ctx, field, {
mode = "coord",
coord_mode = "radial",
coord_center_x = 128,
coord_center_y = 128,
normalize = "signed"
})
-- Moving radial center for tracking applications
ooc.add_coordinate_operator(ctx, field, {
mode = "coord",
coord_mode = "radial",
coord_center_x = 64,
coord_center_y = 64,
coord_velocity_x = 10.0, -- moves right at 10 units/s
coord_velocity_y = 0.0
})
-- Angled gradient at 45 degrees
ooc.add_coordinate_operator(ctx, field, {
mode = "coord",
coord_mode = "angle",
coord_angle = math.pi / 4,
gain = 2.0,
bias = -1.0
})
-- Separable X*Y coordinate product
ooc.add_coordinate_operator(ctx, field, {
mode = "coord",
coord_mode = "separable",
coord_combine = "multiply",
normalize = "signed"
})

add_phase_rotate_operator(ctx, field, opts)

Apply a uniform phase rotation to all field samples. Complex fields use the full complex rotation, while real fields use the projected cosine factor. This makes the operator useful for frequency shifting, demodulation, and rotating reference frames.

add_phase_rotate_operator(ctx, field, [options]) -> operator

Returns: Operator handle (userdata)

For each complex sample ziz_i:

zizieiωΔtz_i \leftarrow z_i \cdot e^{i \omega \Delta t}

where ω\omega is the phase_rate in radians per second.

Equivalent polar form:

zieiϕiziei(ϕi+ωΔt)|z_i| e^{i\phi_i} \leftarrow |z_i| e^{i(\phi_i + \omega \Delta t)}

The magnitude is preserved; only the phase advances by ωΔt\omega \Delta t per timestep.

For real-valued fields or real-constrained spectra, the operator applies the projected real multiplier

ψiψicos(ωΔt)\psi_i \leftarrow \psi_i \cos(\omega \Delta t)

instead of the full complex exponential.

Cartesian implementation:

(Re(z)Im(z))(cos(ωΔt)sin(ωΔt)sin(ωΔt)cos(ωΔt))(Re(z)Im(z))\begin{pmatrix} \text{Re}(z) \\ \text{Im}(z) \end{pmatrix} \leftarrow \begin{pmatrix} \cos(\omega \Delta t) & -\sin(\omega \Delta t) \\ \sin(\omega \Delta t) & \cos(\omega \Delta t) \end{pmatrix} \begin{pmatrix} \text{Re}(z) \\ \text{Im}(z) \end{pmatrix}
ParameterTypeDefaultRangeDescription
phase_ratedouble 0.0unboundedAngular rotation rate ω\omega in rad/s (required)
  • Operates elementwise; no boundary handling required
  • Complex fields rotate in the complex plane; real fields receive the projected cosine factor
  • Initial phase determines starting point; rotation is relative
  • Complex fields: Rotation is unitary and magnitude-preserving
  • Real fields: The projected cosine factor can attenuate or sign-flip samples, so the operation is no longer norm-preserving
  • Phase accumulates linearly with time: ϕ(t)=ϕ0+ωt\phi(t) = \phi_0 + \omega t
  • For very large ωΔt|\omega \Delta t|, consider reducing timestep to avoid phase aliasing
  • Requires one sin/cos evaluation per timestep (not per element)
  • Complex fields use the full 2x2 rotation; real fields use only the cosine projection
  • Very efficient; rotation matrix is computed once per step
  • No memory allocation beyond the field itself
-- Rotate at 1 Hz (2π rad/s)
ooc.add_phase_rotate_operator(ctx, carrier, {
phase_rate = 2 * math.pi
})
-- Negative rotation (clockwise in complex plane)
ooc.add_phase_rotate_operator(ctx, signal, {
phase_rate = -1.0
})
-- Demodulation: shift carrier frequency to baseband
local carrier_freq = 440.0 -- Hz
ooc.add_phase_rotate_operator(ctx, modulated_signal, {
phase_rate = -2 * math.pi * carrier_freq
})
-- Slow phase drift for visualization
ooc.add_phase_rotate_operator(ctx, wave, {
phase_rate = 0.1
})
-- Rotating reference frame for oscillator analysis
local omega_0 = 100.0 -- natural frequency
ooc.add_phase_rotate_operator(ctx, oscillator_state, {
phase_rate = -omega_0 -- move to co-rotating frame
})

add_copy_operator(ctx, input, output, opts)

Copy input field samples to an output field, with optional accumulation. Fundamental building block for field routing, buffering, and multi-stage pipelines.

add_copy_operator(ctx, input, output, [options]) -> operator

Returns: Operator handle (userdata)

For each sample index ii:

outi={outi+inputiΔtif accumulate and scale_by_dtouti+inputiif accumulate onlyinputiotherwise (overwrite)\text{out}_i = \begin{cases} \text{out}_i + \text{input}_i \cdot \Delta t & \text{if accumulate and scale\_by\_dt} \\ \text{out}_i + \text{input}_i & \text{if accumulate only} \\ \text{input}_i & \text{otherwise (overwrite)} \end{cases}
ParameterTypeDefaultDescription
accumulateboolean falseAdd to output instead of overwriting
scale_by_dtboolean trueScale accumulated writes by dt
  • No boundary handling required; operates elementwise
  • Input and output fields must have compatible shapes and element counts
  • Complex fields are copied component-wise (both real and imaginary parts)
  • Pure copy is unconditionally stable
  • When accumulating, ensure time integration is handled correctly to avoid unbounded growth
  • Minimal computational overhead; essentially a memory copy
  • When accumulate = false, may be optimized to a direct memcpy
  • Useful for double-buffering or creating field snapshots before destructive operations
-- Simple field copy
ooc.add_copy_operator(ctx, source, destination)
-- Accumulate input into running sum (scaled by dt)
ooc.add_copy_operator(ctx, flux, integral, {
accumulate = true,
scale_by_dt = true
})
-- Accumulate without dt scaling (raw summation)
ooc.add_copy_operator(ctx, sample, buffer, {
accumulate = true,
scale_by_dt = false
})
-- Double-buffering pattern
local u_prev = ooc.add_field(ctx, {N}, { fill = 0.0 })
ooc.add_copy_operator(ctx, u, u_prev) -- snapshot before update
-- ... operators that modify u ...

add_scale_operator(ctx, input, output, opts)

Scale input field samples by a constant factor. Essential for applying gain, normalization, or unit conversion.

add_scale_operator(ctx, input, output, [options]) -> operator

Returns: Operator handle (userdata)

For each sample index ii:

outi={outi+sinputiΔtif accumulate and scale_by_dtouti+sinputiif accumulate onlysinputiotherwise (overwrite)\text{out}_i = \begin{cases} \text{out}_i + s \cdot \text{input}_i \cdot \Delta t & \text{if accumulate and scale\_by\_dt} \\ \text{out}_i + s \cdot \text{input}_i & \text{if accumulate only} \\ s \cdot \text{input}_i & \text{otherwise (overwrite)} \end{cases}

where ss is the scale parameter.

ParameterTypeDefaultDescription
scaledouble 1.0Multiplicative scale factor
accumulateboolean falseAdd to output instead of overwriting
scale_by_dtboolean trueScale accumulated writes by dt
  • No boundary handling required; operates elementwise
  • Input and output fields must have compatible shapes
  • Complex fields: scale applies to both real and imaginary parts
  • Unconditionally stable for any finite scale value
  • scale > 1.0 amplifies; scale < 1.0 attenuates
  • scale = 0.0 zeros the output (prefer add_zero_field_operator for clarity)
  • Negative scales invert the sign of the field
  • Single multiplication per element; highly efficient
  • Can operate in-place when input == output
  • Consider fusing with other operators when possible to reduce memory bandwidth
-- Apply gain of 0.5
ooc.add_scale_operator(ctx, signal, attenuated, {
scale = 0.5
})
-- Invert field sign
ooc.add_scale_operator(ctx, u, u_neg, {
scale = -1.0
})
-- In-place scaling
ooc.add_scale_operator(ctx, field, field, {
scale = 0.99 -- gentle decay
})
-- Accumulate scaled values (e.g., for weighted averaging)
ooc.add_scale_operator(ctx, sample, weighted_sum, {
scale = weight,
accumulate = true
})
-- Diffusion coefficient application
local D = 0.01
ooc.add_laplacian_operator(ctx, u, lap_u)
ooc.add_scale_operator(ctx, lap_u, du_dt, { scale = D })

add_zero_field_operator(ctx, field)

Overwrite all samples in a field with zeros. Commonly used to reset accumulators, clear buffers, or initialize state at the start of each timestep.

add_zero_field_operator(ctx, field) -> operator

Returns: Operator handle (userdata)

Note: This operator takes no options table—just the context and field.

For each sample index ii:

fieldi=0\text{field}_i = 0

For complex fields, both real and imaginary components are set to zero.

None. The operator simply zeros the specified field.

  • No boundary handling required; all elements are set to zero
  • Operates in-place on the target field
  • Unconditionally stable; always produces zeros
  • Does not depend on timestep or field state
  • Highly optimized; typically compiled to a fast memory-set operation
  • Does not use time (uses_time = false) or timestep (uses_dt = false)
  • Preserves real subspace (complex fields become real zero + imaginary zero)
-- Zero a field at each step (useful for accumulators)
local accumulator = ooc.add_field(ctx, {N}, { fill = 0.0 })
ooc.add_zero_field_operator(ctx, accumulator)
-- ... operators that accumulate into the field ...
-- Reset state before stimulus injection
ooc.add_zero_field_operator(ctx, state)
ooc.add_stimulus_operator(ctx, state, {
type = "stimulus_sine",
amplitude = 1.0,
wavenumber = 0.5,
omega = 0.1
})
-- Clear a buffer in a ping-pong scheme
ooc.add_zero_field_operator(ctx, buffer_b)
ooc.add_copy_operator(ctx, buffer_a, buffer_b, { accumulate = true })