Skip to content
Oakfield Operator Calculus Function Reference Site

Custom Operators

Custom operators let you extend the simulation with your own Lua callbacks. They execute alongside built-in operators in the scheduler and have full access to fields, parameters, and simulation state.


  1. Registerooc.add_operator(ctx, name, fn) installs your callback and returns an operator handle.
  2. Capture index — Store the operator index (ooc.operator_count(ctx) - 1) if you need to read parameters inside the callback.
  3. Expose params (optional)ooc.operator_set_params(ctx, op, params) publishes typed parameters for UI and introspection.
  4. Modify at runtime — Use ooc.operator_param_set() to change parameters of any operator (including built-in ones).

local op = ooc.add_operator(ctx, name, callback) --> operator_handle
ParameterTypeDescription
ctxcontext Simulation context
namestring Display name for the operator
callbackfunction Function called each simulation step; receives context, returns true/false or a SimResult code

The callback should return:

  • true or 0 — Success (SIM_RESULT_OK)
  • false or error code — Halt simulation

The operator index is needed to read parameters inside callbacks. Capture it immediately after registration:

local my_op_idx
local my_op = ooc.add_operator(ctx, "My Operator", function(c)
-- use my_op_idx here to read params
local value = ooc.operator_param(c, my_op_idx, "gain")
return true
end)
my_op_idx = ooc.operator_count(ctx) - 1

Expose typed parameters for UIs and introspection with operator_set_params:

ooc.operator_set_params(ctx, op, {
{ name = "gain", type = "float", default = 1.0, min = 0.0, max = 10.0 },
{ name = "field", type = "field", default = 0 },
{ name = "mode", type = "enum", options = {"fast", "accurate"}, default = "fast" },
})
TypeRequired KeysOptional Keys
floatnamedefault, min, max, description
integernamedefault, min, max, description
booleannamedefault, description
enumname, optionsdefault, description
fieldnamedefault, description
-- Set a float/integer/boolean parameter
ooc.operator_param_set(ctx, operator_index, "param_name", value)
-- Set an enum parameter
ooc.operator_param_enum_set(ctx, operator_index, "param_name", "option_string")
-- Set a field parameter
ooc.operator_param_field_set(ctx, operator_index, "param_name", field_handle)

Dynamically modulate parameters of other operators over time. This pattern is useful for exploring parameter spaces or creating time-varying effects.

local function build_context()
local N = 768
local dt = 0.025
local ctx = ooc.create()
ooc.set_timestep(ctx, dt)
local field = ooc.add_field(ctx, {N}, {
type = "complex_double",
fill = {0.0, 0.0}
})
-- Stimulus operator at index 0
ooc.add_stimulus_operator(ctx, field, {
type = "stimulus_sine",
amplitude = 1.0,
wavenumber = 1,
omega = -0.5,
scale_by_dt = false
})
-- Custom sweep operator
local t = 0
local sweep_op = ooc.add_operator(ctx, "Parameter Sweep", function(c)
t = t + 1
-- Slowly oscillate the wavenumber of the stimulus (operator index 0)
local sweep = math.sin(t * 0.00001) * 2.0
ooc.operator_param_set(ctx, 0, "wavenumber", sweep)
return true
end)
return ctx
end
return build_context()

Key points:

  • The sweep operator modifies parameter wavenumber on operator index 0 (the stimulus)
  • Uses a simple counter t to track simulation steps
  • operator_param_set works on any operator, not just custom ones

Custom operators can query the profiler for performance monitoring:

local profiler_op = ooc.add_operator(ctx, "Profiler", function(c)
if t % 1000 == 0 then
local entries, total_ms = ooc.operator_profiler(ctx)
if entries then
for _, e in ipairs(entries) do
ooc.log("%-20s %.3f ms", e.name, e.inclusive_ms)
end
ooc.log("Total: %.3f ms", total_ms)
end
end
return true
end)

  • Performance: Keep Lua operators lightweight. Heavy per-element loops bottleneck CPU execution. For numerical work, use built-in operators that run on the GPU.

  • Write masks: The scheduler does not infer write masks for Lua operators. Avoid side effects outside the fields you explicitly manage.

  • Field access: Use field:values() to get a Lua table of values, and field:set_values(v) to write back. These copy data between Lua and the simulation.

  • Logging: Use ooc.log(format, ...) for printf-style debug output. Avoid excessive logging in tight loops.

  • Return values: Always return true (or 0) on success. Returning false or an error code halts the simulation.

  • Operator ordering: Operators execute in registration order. If your custom operator depends on another operator’s output, register it after.