Skip to content
Oakfield Operator Calculus Function Reference Site

Field Creation

  • add_field(ctx, shape[, options]) allocates a field and returns a Field handle (userdata) pinned to the context lifetime.
  • Shape: Lua array of positive integers, one per dimension. Axis 0 is the slowest (outermost loop); axis rank-1 is the fastest (innermost).
  • Element type: "double" (real, 8 bytes/element) or "complex_double" (interleaved re/im, 16 bytes/element).
  • Seeding: fill sets every element at allocation time. initializer (Lua function) runs per-element after fill and supports 1D and 2D fields.
  • Representation metadata: domain, value_kind, or representation = { ... } tag the field as physical vs spectral and real vs complex-valued without changing the underlying storage type.
  • Coordinates: origin and spacing define the physical coordinate grid passed to the initializer. They are consumed during initialization only and are not stored on the field.

add_field(ctx, shape[, options]) -> field

Returns: Field handle (userdata) — pass it to operators, step functions, and field queries.

OptionTypeDefaultDescription
typestring "double""complex_double" stores interleaved {re, im} pairs. Unrecognized strings fall back to "double".
filldouble or {re, im}0Seeds every element before the initializer runs. For complex fields, supply a {re, im} table; a bare number is ignored for complex storage.
initializerfunction nonePer-element Lua callback called after fill. Supported for 1D and 2D fields only. See signatures below.
origindouble 0.0Physical coordinate of element index 0 along every axis (isotropic — same for all axes).
spacingdouble 1.0Step size between adjacent elements in physical coordinates (isotropic).
domainstring or enumderivedRepresentation domain. Canonical values are "physical" and "spectral"; "spatial" and "frequency" are accepted aliases.
value_kindstring or enumderivedScalar interpretation metadata. Canonical values are "real", "complex", and "complex_real_constraint"; aliases like "scalar", "hermitian", and "conjugate_symmetric" are accepted.
representationtable noneNested { domain = ..., value_kind = ... } override. Parsed after the top-level domain / value_kind keys and takes precedence when both are present.

The initializer function is called once per element. Physical coordinates are derived from origin and spacing. Return a number for real fields or a {re, im} table for complex fields.

ArgumentDescription
i0-based element index (0 to shape[1] - 1)
xPhysical coordinate: origin + i * spacing
-- Complex travelling wave
local wave = ooc.add_field(ctx, {1024}, {
type = "complex_double",
origin = -math.pi,
spacing = 2 * math.pi / 1024,
initializer = function(i, x)
return { math.cos(3 * x), math.sin(3 * x) }
end
})
ArgumentDescription
ix0-based index along axis 0 (outer / row dimension, 0 to shape[1] - 1)
iy0-based index along axis 1 (inner / column dimension, 0 to shape[2] - 1)
xPhysical coordinate along axis 0: origin + ix * spacing
yPhysical coordinate along axis 1: origin + iy * spacing
-- Gaussian blob centred at (0, 0)
local pattern = ooc.add_field(ctx, {256, 256}, {
origin = -1.0,
spacing = 2.0 / 256,
initializer = function(ix, iy, x, y)
return math.exp(-8 * (x * x + y * y))
end
})

The iteration order for 2D fields is: outer loop over ix (axis 0), inner loop over iy (axis 1), matching the row-major element layout returned by field:values().


local ctx = ooc.create()
local density = ooc.add_field(ctx, {256, 256}, { fill = 0.0 })
ooc.log("rank=%d elements=%d", density:rank(), #density:values())
local wave = ooc.add_field(ctx, {1024}, {
type = "complex_double",
origin = -math.pi,
spacing = 2 * math.pi / 1024,
initializer = function(i, x)
return { math.cos(3 * x), math.sin(3 * x) }
end
})
local v = wave:values()
ooc.log("wave[0] = %.3f + %.3fi", v[1][1], v[1][2])
local blob = ooc.add_field(ctx, {256, 256}, {
origin = -1.0,
spacing = 2.0 / 256,
initializer = function(ix, iy, x, y)
return math.exp(-8 * (x * x + y * y))
end
})
local phase = ooc.add_field(ctx, {512, 512}, {
type = "complex_double",
origin = -math.pi,
spacing = 2 * math.pi / 512,
initializer = function(ix, iy, x, y)
local theta = math.atan(y, x)
return { math.cos(theta), math.sin(theta) }
end
})

Use representation tags when a field should be treated as a spectral buffer or as Hermitian-constrained complex data:

local spectrum = ooc.add_field(ctx, {256}, {
type = "complex_double",
representation = {
domain = "spectral",
value_kind = "complex_real_constraint",
},
fill = {0.0, 0.0}
})
ooc.log("domain=%s value_kind=%s", spectrum:domain(), spectrum:value_kind())

Seeding Without Initializer — fill + set_values

Section titled “Seeding Without Initializer — fill + set_values”

For rank ≥ 3 or when the seed data comes from outside Lua, allocate with fill and then write via field:set_values():

local vol = ooc.add_field(ctx, {64, 64, 64}, { fill = 0.0 })
-- Build a flat table of 64^3 values, then write:
local data = {}
for i = 1, 64 * 64 * 64 do data[i] = math.random() end
vol:set_values(data)

  1. Choose type up front — element size is fixed at allocation and determines byte layout for all operators attached to that field.
  2. Prefer fill for constant seeds; use initializer for per-element geometry or waveform shapes (1D and 2D only).
  3. For large fields or complex seeding logic, add_stimulus_operator (e.g. stimulus_rd_seed) is more efficient than a Lua initializer.
  4. Capture the returned Field handle — it is needed to attach operators and to call :rank(), :shape(), :values().
  5. Fields are stored contiguously in row-major order; shape[1] is the outermost dimension and shape[rank] is the innermost (fastest-varying).