Logging & Profiling
📋 Basic Logging
Section titled “📋 Basic Logging”log([ctx], [level], fmt, ...)- If the first argument is a context, it is ignored for logging but allowed for convenience.
levelis optional; defaults toINFO. Usesim.LOG_LEVEL_TRACE|DEBUG|INFO|WARN|ERROR|FATAL.- Remaining arguments follow Lua
string.formatsemantics; the message is formatted before printing.
Examples:
sim.log("Hello from Lua")sim.log(sim.LOG_LEVEL_DEBUG, "value=%.3f", 1.234)sim.log(ctx, sim.LOG_LEVEL_WARN, "step %d exceeded threshold", step)Log Levels
Section titled “Log Levels”| Name | Level | Description |
|---|---|---|
| SIM_LOG_LEVEL_TRACE | 0 | Very detailed debug information |
| SIM_LOG_LEVEL_DEBUG | 1 | General debug information |
| SIM_LOG_LEVEL_INFO | 2 | Informational messages |
| SIM_LOG_LEVEL_WARN | 3 | Warning conditions |
| SIM_LOG_LEVEL_ERROR | 4 | Error conditions |
| SIM_LOG_LEVEL_FATAL | 5 | Fatal conditions |
💾 Async Logging
Section titled “💾 Async Logging”sim_async_logger_create([capacity]) -> loggerAllocates an in-memory async ring-buffer logger (default capacity 256, floored to at least 64) and returns a Logger userdata. It does not touch the filesystem; records are queued for a consumer to read—safe for multi-threaded or asynchronous pipelines. Pass 0 or omit the argument to use the default capacity.
Logger methods:
logger:log(level, message): enqueue a record.logger:pop(): returns the next record table{timestamp_ns, thread_id, level, message}ornilif empty.logger:clear(): drop all queued records.
Example:
local logger = sim.sim_async_logger_create(0)
logger:log(sim.LOG_LEVEL_INFO, "Async Logger test...")logger:log(sim.LOG_LEVEL_TRACE, string.format("N = %d, dt = %f", N, dt))
while true do local rec = logger:pop() if not rec then break end print(ctx, string.format("[%d] %s", rec.level, rec.message))end💡 Tips
Section titled “💡 Tips”logprepends level tags when not in GUI mode; in GUI mode it emits raw text.- Formatting errors raise a Lua error with the reason (
log("%s %d", 1)will fail). - Use
LOG_LEVEL_*constants exposed byluaopen_libsimcorefor clarity. - When integrating with
sim_on_step, keep log volume low to avoid slowing the simulation.
📊 Profiling & Metrics
Section titled “📊 Profiling & Metrics”These helpers surface runtime data to Lua. Set enable_profiling = true in your config passed to sim_run to collect profiler counters. Adaptive timestep heuristics can be toggled with enable_timestep_heuristics (defaults to true).
Context Metrics
Section titled “Context Metrics”sim_context_metrics(ctx) -> tableReturns summary metadata:
local metrics = sim.sim_context_metrics(ctx)
-- fields: field_count, operator_count, plan_operator_count, plan_valid,-- backend ("CPU"|"CUDA"|"Metal"), step_index, time_seconds, dt, total_bytes
sim.log("Backend: %s, Field Count=%d, Operator Count=%d, dt=%.4f, Plan Valid=%s", metrics.backend, metrics.field_count, metrics.operator_count, metrics.dt, metrics.plan_valid and "True" or "False")Field Stats
Section titled “Field Stats”sim_field_stats(ctx_or_field, [field_index]) -> table|nilComputes per-field statistics (mean, RMS, spectral entropy, phase coherence, continuity counts, etc.). You can pass either a field handle or an index. Returns nil if unavailable.
Available statistics:
| Statistic | Description |
|---|---|
| mean_re | Mean of real parts |
| mean_im | Mean of imaginary parts |
| mean_abs | Mean of magnitudes |
| rms | Root mean square of magnitudes |
| var_re | Variance of real parts |
| var_im | Variance of imaginary parts |
| var_abs | Variance of magnitudes |
| max_abs | Maximum magnitude |
| phase_coherence | Phase coherence metric |
| circularity | Circularity metric |
| spectral_entropy | Spectral entropy |
| spectral_bandwidth | Spectral bandwidth |
| phase_coherence_weighted | Weighted phase coherence |
| phase_coherence_ema | EMA of phase coherence |
| phase_coherence_k0 | Phase coherence k0 |
| phase_sample_count | Number of phase samples |
| phase_lock_state | Phase lock state |
| phase_regime | Phase regime |
| count | Number of samples |
-- Create a fieldlocal field = sim.sim_add_field(ctx, {N}, { type = "complex_double", fill = {0.0, 0.0}})
-- Local time step counterlocal t = 0
-- Pointer to stats of field index 0local stats = sim.sim_field_stats(ctx, 0)
-- Create a stats operatorlocal stats_op = sim.sim_add_operator(ctx, "stats_op", function(context) t = t + 1
-- Print results every 1000 steps if t % 1000 == 0 then sim.log("-----------[ Field Stats ]----------") sim.log("%-24s %.10f", "Mean Real", stats.mean_re) sim.log("%-24s %.10f", "Mean Imag", stats.mean_im) sim.log("%-24s %.10f", "Var Real", stats.var_re) sim.log("%-24s %.10f", "Var Imag", stats.var_im) sim.log("%-24s %.10f", "Var Abs", stats.var_abs) sim.log("%-24s %.10f", "RMS", stats.rms) sim.log("%-24s %.10f", "Max Abs", stats.max_abs) sim.log("%-24s %.10f", "Phase Coherence", stats.phase_coherence) sim.log("%-24s %.10f", "Phase Coherence Weighted", stats.phase_coherence_weighted) sim.log("%-24s %.10f", "Phase Coherence EMA", stats.phase_coherence_ema) sim.log("%-24s %.10f", "Phase Coherence k0", stats.phase_coherence_k0) sim.log("%-24s %.10f", "Phase Regime", stats.phase_regime) sim.log("%-24s %.10f", "Circularity", stats.circularity) sim.log("%-24s %.10f", "Spectral Entropy", stats.spectral_entropy) sim.log("%-24s %.10f", "Spectral Bandwidth", stats.spectral_bandwidth) end
return true -- or return 0 (SIM_RESULT_OK)end)Example output:
-----------[ Field Stats ]----------Mean Real 0.0004454985Mean Imag -0.0003130782Var Real 0.0365573493Var Imag 0.0622247355Var Abs 0.0198729688RMS 0.3142966455Max Abs 0.4487944696Phase Coherence 0.0232840599Phase Coherence Weighted 0.0019383776Phase Coherence EMA 0.0019383776Phase Coherence k0 0.8311182350Phase Regime 3.0000000000Circularity 0.2664554384Spectral Entropy 0.2546378467Spectral Bandwidth 3.7603059001Profiler Snapshot
Section titled “Profiler Snapshot”sim_profiler_snapshot(ctx) -> table|nilReturns a snapshot of the last profiled frame with fields:
frame_start_nsframe_end_nstotal_nsaverage_operator_ns
local t = 0local snap_op = sim.sim_add_operator(ctx, "snap_op", function(context) if t % 1000 == 0 then
sim.log("-------[ Profiler Snapshot ]--------") local snap = sim.sim_profiler_snapshot(ctx)
sim.log("Frame=%g ns, Average Op=%g ns", snap.total_ns, snap.average_operator_ns) end return trueend)Operator Profiler
Section titled “Operator Profiler”sim_operator_profiler(ctx) -> entries, total_msPer-operator timing and RMS deltas for the last profiled frame. Returns a list of entries with fields:
nameinclusive_msrms_delta
local profiler_op = sim.sim_add_operator(ctx, "profiler_op", function(context) if t % 1000 == 0 then sim.log("-------[ Operator Profiler ]--------") local entries, total_ms = sim.sim_operator_profiler(ctx)
if entries then for _, e in ipairs(entries) do sim.log("%-24s %.5f ms", e.name, e.inclusive_ms) end sim.log("%-24s %.5f ms", "total", total_ms) end end return trueend)Example output:
-------[ Operator Profiler ]--------mixer_53#0 0.00029 msstimulus_sine_54#0 0.00000 msstimulus_sine_55#0 0.00000 msmixer_56#0 0.00000 msprofiler_op 0.00000 mstotal 0.00029 msManual Profiler
Section titled “Manual Profiler”sim_profiler_new(operator_count) -> profilerCreates a standalone profiler userdata you can drive manually (distinct from the scheduler profiler). Methods:
profiler:begin_frame()profiler:end_frame()profiler:record_operator(index, duration_ns)profiler:snapshot() -> {frame_start_ns, frame_end_ns, total_ns, average_operator_ns}
Example:
local p = sim.sim_profiler_new(3)
p:begin_frame()-- ... measure work ...p:record_operator(1, 250000) -- 0-based indexp:end_frame()
local snap = p:snapshot()
print("manual frame ns", snap.total_ns)⚙️ Configuration Flags
Section titled “⚙️ Configuration Flags”Pass these in the table to sim.sim_run(ctx, config_table):
enable_profiling = trueto collect scheduler profiler data forsim_profiler_snapshot/sim_operator_profiler.enable_logging = trueto turn on the async logger backend.enable_timestep_heuristics = falseto disable adaptive dt decisions (andsim_timestep_decisionwill reportavailable = false).
sim.sim_run(ctx, { enable_profiling = true, enable_logging = true, enable_timestep_heuristics = true,})