Oakfield API
Accurate, implementation-derived guide to scripting Oakfield simulations.
1. Overview
Oakfield exposes a composable operator calculus through Lua. A script creates a SimContext, adds fields and operators, assigns an integrator, then advances time. All APIs documented here are verified against the current source.
Key pillars:
• Fields: discrete storage (real or complex) over an N‑D grid
• Operators: registered transformations (native kernels or Lua callbacks)
• Integrators: advance context by dt (adaptive or fixed)
• Runtime services: async logger, profiler, visual mode selection
2. Indexing & Conventions
- Field indices, operator indices, and integrator references inside the engine are zero-based.
- Lua tables returned to user (e.g.
field:shape(),field:values()) are 1-based (Lua default). - Complex numbers are passed either as a plain number (real) or
{re, im}table. - Enum values and modes exposed as integers also have string helpers where provided.
3. Userdata & GC Semantics
| Type | GC Behavior |
|---|---|
| Context | Destroys context, releases managed integrators, frees native memory |
| Field | Detaches handle only; underlying field owned by context until context GC |
| Operator | Detaches handle only; operator lifetime owned by context |
| Integrator | Destroys integrator and frees workspace |
| Logger / Profiler | Destroys async logger/profiler and frees memory |
Detaching (field/operator) means subsequent method calls on that handle error (expected Field userdata).
4. Context API
Creation
sim.sim_create([config]) -> contextOptional config table keys include continuity override and runtime tuning. Boundary accepts neumann|dirichlet|periodic|reflective (default neumann).
Time & Timestep
sim.sim_set_timestep(ctx, dt)
sim.sim_get_timestep(ctx) -> number
sim.sim_get_time(ctx) -> number
sim.sim_get_step_index(ctx) -> integerVisual Mode
sim.sim_set_visual_mode(ctx, mode)
sim.sim_get_visual_mode(ctx) -> (int, string)Mode can be integer or one of: phase_portrait, waveform, phase, energy, frequency, polar.
5. Field API
Field Creation
sim.sim_add_field(ctx, shape_table[, options]) -> FieldParameters:
shape_table: 1+ positive integersoptions.type:double(default),float,complex_double,complex_floatoptions.fill: number or{re, im}options.initializer: function(i, coord) → value (1D only)options.origin,options.spacing: coordinate generation
Field Methods
field:rank() -> integer
field:shape() -> {dims...}
field:components() -> integer
field:values() -> { ... }Example: 1D Field with Sinusoidal Initialization
local field = sim.sim_add_field(ctx, {512}, {
initializer = function(i)
return math.sin(2 * math.pi * i / 512)
end
})Example: Complex Field
local complex_field = sim.sim_add_field(ctx, {512}, {
type = "complex_double",
initializer = function(i)
local x = i * 0.01
return {math.cos(x), math.sin(x)}
end,
spacing = 0.01
})6. Operator API
User-Defined Operators
sim.sim_add_operator(ctx, name, callback[, dependencies]) -> OperatorCallback receives context and returns integer SimResult code or boolean (true=OK, false=error).
local op = sim.sim_add_operator(ctx, 'tick_counter', function(context)
tick = tick + 1
return true -- or return 0 (SIM_RESULT_OK)
end)Stimulus Operators
sim.sim_add_stimulus_operator(ctx, field, config)Stimulus types: sine, standing_wave, chirp, gaussian_pulse, gaussian_travel, fbm, checkerboard, white_noise, random_fourier, spectral_lines, gabor
-- Sine wave stimulus
local stim = sim.sim_add_stimulus_operator(ctx, field, {
type = "sine",
amplitude = 0.5,
frequency = 2.0,
phase = 0.0
})Linear Dissipative Operators
sim.sim_add_linear_dissipative_operator(ctx, field, config)Config: viscosity, alpha (fractional order ∈ (0,2]), spacing
-- Classical diffusion
sim.sim_add_linear_dissipative_operator(ctx, field, {
viscosity = 2.5,
alpha = 1.0,
spacing = 0.1
})Stochastic Noise Operators
sim.sim_add_stochastic_noise_operator(ctx, field, config)Config: sigma (intensity), tau (correlation time), alpha (spectral exponent), seed, law (ito/stratonovich)
Analytic Warp Operators
sim.sim_add_analytic_warp_operator(ctx, field, config)Profiles: digamma, trigamma, power, tanh
sim.sim_add_analytic_warp_operator(ctx, field, {
profile = "tanh",
lambda = 0.5
})Mixer Operators
sim.sim_add_mixer_operator(ctx, lhs_field, rhs_field, output_field, config)Modes: linear, multiply, divide
-- Compute u²
local u2 = sim.sim_add_field(ctx, {N}, {fill = 0.0})
sim.sim_add_mixer_operator(ctx, u, u, u2, {
mode = "multiply"
})7. Integrator API
Creation
sim.sim_create_context_integrator(ctx, name[, config]) -> IntegratorAvailable integrators: euler, euler_deterministic, heun, rk4, rkf45, backward_euler, crank_nicolson, subordination
Example: Fixed-Step RK4
local integrator = sim.sim_create_context_integrator(ctx, "rk4", {
initial_dt = 0.01,
adaptive = false
})
sim.sim_set_integrator(ctx, integrator)Example: Adaptive RKF45
local integrator = sim.sim_create_context_integrator(ctx, "rkf45", {
initial_dt = 0.01,
adaptive = true,
min_dt = 1e-4,
max_dt = 0.1,
tolerance = 1e-6,
safety = 0.8
})8. Simulation Control
Basic Stepping
sim.sim_step(ctx) -> booleanPrepares plan, executes one step, updates time & index.
Example: Simulation Loop
for step = 1, 1000 do
local success = sim.sim_step(ctx)
if not success then
print("Simulation failed at step", step)
break
end
-- Periodic monitoring
if step % 100 == 0 then
local values = field:values()
-- Process values...
end
end9. Logging API
Creation
sim.sim_async_logger_create(ctx[, config]) -> LoggerMethods
logger:log(level_int, message) -> true
logger:pop() -> { timestamp_ns, thread_id, level, message } | nil
logger:clear()Log levels: LOG_LEVEL_TRACE, LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_WARN, LOG_LEVEL_ERROR, LOG_LEVEL_FATAL
10. Special Functions
Scalar / complex evaluations with automatic argument dispatch (number → real, {re,im} → complex):
sim.digamma(z) - Digamma function ψ(x) = d/dx ln Γ(x)sim.trigamma(z) - Trigamma function ψ'(x)sim.hyperexp(x, ...) - Hyperexponential functionsim.qhyperexp(phi, epsilon, n, q) - q-deformed hyperexponentialsim.qnumber(x, q) - q-numbersim.qzeta(s, q) - q-zeta functionsim.qdigamma(z, q) - q-digamma functionExample Usage
local psi = sim.digamma(1.25)
local psi_complex = sim.digamma({0.75, 0.5})
local phi = sim.hyperexp(0.5, 0.05, 32)
local qphi = sim.qhyperexp({0.4, 0.25}, {0.0, -0.1}, 24, 0.9)11. Complete Examples
Example 1: Basic Heat Equation
Simple diffusion with visualization hint.
local sim = require("libsimcore")
local N = 512
local dt = 0.01
local ctx = sim.sim_create()
sim.sim_set_timestep(ctx, dt)
sim.sim_set_visual_mode(ctx, "waveform")
-- Initial condition: Gaussian pulse
local field = sim.sim_add_field(ctx, {N}, {
initializer = function(i)
local x = (i - N/2) / N
return math.exp(-50 * x * x)
end
})
-- Diffusion operator
sim.sim_add_linear_dissipative_operator(ctx, field, {
viscosity = 1.0,
alpha = 1.0,
spacing = 1.0 / N
})
-- Fixed-step integration
local integrator = sim.sim_create_context_integrator(ctx, "rk4", {
initial_dt = dt,
adaptive = false
})
sim.sim_set_integrator(ctx, integrator)
-- Run simulation
for step = 1, 1000 do
sim.sim_step(ctx)
end
return ctxExample 2: Stochastic Differential Equation
Combines dissipation and noise.
local sim = require("libsimcore")
local N = 256
local dt = 0.005
local ctx = sim.sim_create()
sim.sim_set_timestep(ctx, dt)
-- Initialize with sine wave
local field = sim.sim_add_field(ctx, {N}, {
initializer = function(i)
return math.sin(2 * math.pi * i / N)
end
})
-- Fractional dissipation
sim.sim_add_linear_dissipative_operator(ctx, field, {
viscosity = 2.0,
alpha = 0.7,
spacing = 0.1
})
-- Additive noise
sim.sim_add_stochastic_noise_operator(ctx, field, {
sigma = 0.1,
tau = 0.05,
seed = 42
})
-- Adaptive integration
local integrator = sim.sim_create_context_integrator(ctx, "rkf45", {
initial_dt = dt,
adaptive = true,
min_dt = 1e-4,
max_dt = 0.05,
tolerance = 1e-5
})
sim.sim_set_integrator(ctx, integrator)
for step = 1, 500 do
sim.sim_step(ctx)
if step % 50 == 0 then
local metrics = sim.sim_step_metrics_latest(ctx)
if metrics then
sim.log(sim.LOG_LEVEL_INFO, "Step %d: dt=%.6f",
step, metrics.accepted_dt)
end
end
end
return ctxExample 3: Nonlinear PDE with Thermostat
Nonlinear dynamics with energy regulation.
local sim = require("libsimcore")
local N = 128
local dt = 0.01
local ctx = sim.sim_create()
sim.sim_set_timestep(ctx, dt)
-- Primary field
local u = sim.sim_add_field(ctx, {N}, {
initializer = function(i)
return 0.5 * math.sin(4 * math.pi * i / N)
end
})
-- Dissipation
sim.sim_add_linear_dissipative_operator(ctx, u, {
viscosity = 0.5,
alpha = 1.0,
spacing = 1.0 / N
})
-- Compute u^3 for cubic nonlinearity
local u2 = sim.sim_add_field(ctx, {N}, {fill = 0.0})
sim.sim_add_mixer_operator(ctx, u, u, u2, {mode = "multiply"})
local u3 = sim.sim_add_field(ctx, {N}, {fill = 0.0})
sim.sim_add_mixer_operator(ctx, u, u2, u3, {mode = "multiply"})
-- Add nonlinear term: -0.5*u + 0.2*u^3
local nl = sim.sim_add_field(ctx, {N}, {fill = 0.0})
sim.sim_add_mixer_operator(ctx, u, u3, nl, {
mode = "linear",
lhs_gain = -0.5,
rhs_gain = 0.2,
accumulate = true
})
-- Energy regulation
sim.sim_add_thermostat_operator(ctx, u, {
mode = sim.THERMOSTAT_SOFT_LAMBDA,
E_target = 1.0,
lambda_soft_gain = 0.15
})
-- Integrator
local integrator = sim.sim_create_context_integrator(ctx, "rk4", {
initial_dt = dt,
adaptive = false
})
sim.sim_set_integrator(ctx, integrator)
for step = 1, 1000 do
sim.sim_step(ctx)
end
return ctx12. Best Practices
1. Initialize Fields Carefully
Field initializer callbacks receive 0-based indices from C, but Lua arrays are 1-based. Be mindful of the indexing when mapping to coordinates.
-- ✓ Correct: Index i is already 0-based from C
local field = sim.sim_add_field(ctx, {N}, {
initializer = function(i)
local x = i / (N - 1) -- Map to [0, 1]
return math.sin(2 * math.pi * x)
end
})2. Use Appropriate Integrators
- Deterministic, smooth systems:
rkf45(adaptive) orrk4(fixed) - Simple testing / fixed step:
euler,rk4 - Implicit / stiff drift:
backward_euler,crank_nicolson - Fractional derivatives:
subordination - Stochastic systems:
euler,heun
3. Monitor Stability
Check field values periodically to detect numerical instabilities:
local values = field:values()
local max_abs = 0
for _, v in ipairs(values) do
max_abs = math.max(max_abs, math.abs(v))
end
if max_abs > 1e10 then
sim.log(sim.LOG_LEVEL_ERROR, "Instability detected!")
break
end4. Use Constants Over Strings
Prefer enum constants for better performance and type safety:
-- ✓ Better
profile = sim.ANALYTIC_WARP_PROFILE_TRIGAMMA
mode = sim.THERMOSTAT_SOFT_LAMBDA
-- ✗ Less efficient
profile = "trigamma"
mode = "soft_lambda"5. Handle Complex Fields Correctly
Complex fields return tables of {real, imag}:
local values = complex_field:values()
for i, v in ipairs(values) do
local re, im = v[1], v[2]
local magnitude = math.sqrt(re^2 + im^2)
local phase = math.atan2(im, re)
-- ...
endReady to start building?