Operator Calculus, Co.
Browse Studio documentation
On this page

Coordinate 📐

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.

Method Signature

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

Returns: Operator handle (userdata)

Mathematical Formulation

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=(x−cx−vxt)2+(y−cy−vyt)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/(N−1)unit∈[0,1]c/(N−1)−0.5centered∈[−0.5,0.5]2c/(N−1)−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=gain⋅cnorm+bias\text{out}_i = \text{gain} \cdot c_{\text{norm}} + \text{bias}

Parameters

Core Parameters:

Parameter Type Default Range Description
mode enum "index" index, coord Source mapping mode
normalize enum "none" see below Normalization applied to coordinate
gain double 1.0 unbounded Multiplicative gain after normalization
bias double 0.0 unbounded Additive bias after gain
time_offset double 0.0 unbounded Time offset for coordinate evaluation
accumulate boolean false — Add to output instead of overwriting
scale_by_dt boolean true — Scale accumulated writes by dt

Normalization options: none, unit, centered, signed

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

Parameter Type Default Range Description
coord_mode enum "axis" see below Coordinate mapping mode
coord_axis enum "x" x, y Axis for axis-mode
coord_combine enum "multiply" multiply, add Combine rule for separable mode
coord_angle double 0.0 radians Angle for angle-mode

Coordinate mode options: axis, angle, radial, separable

Spatial Parameters:

Parameter Type Default Range Description
origin_x double 0.0 unbounded X origin (mapped to index 0)
origin_y double 0.0 unbounded Y origin (mapped to index 0)
spacing_x double 1.0 >0 Grid spacing in X direction
spacing_y double 1.0 >0 Grid spacing in Y direction

Radial Mode Parameters:

Parameter Type Default Range Description
coord_center_x double 0.0 unbounded Radial center X position
coord_center_y double 0.0 unbounded Radial center Y position
coord_velocity_x double 0.0 unbounded Radial center X velocity (units/s)
coord_velocity_y double 0.0 unbounded Radial center Y velocity (units/s)

Boundary & Initial Conditions

  • 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

Stability & Convergence

  • 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

Performance Notes

  • 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

Examples

-- 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"
})

Phase Rotate 🔄

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.

Method Signature

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

Returns: Operator handle (userdata)

Mathematical Formulation

For each complex sample ziz_i:

zi←zi⋅eiωΔtz_i \leftarrow z_i \cdot e^{i \omega \Delta t}

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

Equivalent polar form:

∣zi∣eiϕi←∣zi∣ei(ϕ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}

Parameters

Parameter Type Default Range Description
phase_rate double 0.0 unbounded Angular rotation rate ω\omega in rad/s (required)

Boundary & Initial Conditions

  • 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

Stability & Convergence

  • 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

Performance Notes

  • 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

Examples

-- 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
})

Copy 📋

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.

Method Signature

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

Returns: Operator handle (userdata)

Mathematical Formulation

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}

Parameters

Parameter Type Default Description
accumulate boolean false Add to output instead of overwriting
scale_by_dt boolean true Scale accumulated writes by dt

Boundary & Initial Conditions

  • 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)

Stability & Convergence

  • Pure copy is unconditionally stable
  • When accumulating, ensure time integration is handled correctly to avoid unbounded growth

Performance Notes

  • 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

Examples

-- 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 ...

Scale ⚖️

add_scale_operator(ctx, input, output, opts)

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

Method Signature

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

Returns: Operator handle (userdata)

Mathematical Formulation

For each sample index ii:

outi={outi+s⋅inputi⋅Δtif accumulate and scale_by_dtouti+s⋅inputiif accumulate onlys⋅inputiotherwise (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.

Parameters

Parameter Type Default Description
scale double 1.0 Multiplicative scale factor
accumulate boolean false Add to output instead of overwriting
scale_by_dt boolean true Scale accumulated writes by dt

Boundary & Initial Conditions

  • No boundary handling required; operates elementwise
  • Input and output fields must have compatible shapes
  • Complex fields: scale applies to both real and imaginary parts

Stability & Convergence

  • 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

Performance Notes

  • Single multiplication per element; highly efficient
  • Can operate in-place when input == output
  • Consider fusing with other operators when possible to reduce memory bandwidth

Examples

-- 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 })

Zero Field 🗑️

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.

Method Signature

add_zero_field_operator(ctx, field) -> operator

Returns: Operator handle (userdata)

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

Mathematical Formulation

For each sample index ii:

fieldi=0\text{field}_i = 0

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

Parameters

None. The operator simply zeros the specified field.

Boundary & Initial Conditions

  • No boundary handling required; all elements are set to zero
  • Operates in-place on the target field

Stability & Convergence

  • Unconditionally stable; always produces zeros
  • Does not depend on timestep or field state

Performance Notes

  • 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)

Examples

-- 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 })