Formualizer Docs
Reference

Cycle Configuration Reference

The CycleConfig surface — detection modes, policies, iterative-calculation knobs, and how it maps across Rust, Python, and JS/WASM.

Cycle handling is configured through CycleConfig, nested under the engine's EvalConfig (and therefore reachable from WorkbookConfig.eval). It has two parts: a detection mode and a policy for live cycles.

CycleConfig {
  detection: CycleDetection,  // Static (default) | Runtime
  policy:    CyclePolicy,      // Error (default) | Iterate { max_iterations, max_change }
}

See the guides for the conceptual model:

CycleDetection

ValueBehavior
Static (default)Every static SCC is stamped #CIRC!. No live-edge machinery runs. Byte-for-byte compatible with prior releases.
RuntimeStatic SCCs are candidates; members evaluate with live-edge recording, and only live cycles get the policy verdict. Phantom (live-acyclic) SCCs produce ordinary values.

CyclePolicy

Applies to cycles confirmed live under Runtime detection.

ValueBehavior
Error (default)Live cycles produce #CIRC!.
Iterate { max_iterations, max_change }Excel-style iterative calculation. Live cycles run repeated passes until every member converges or the pass cap is reached. Requires Runtime detection.

Iterate knobs

KnobExcel defaultNotes
max_iterations100Total passes per cycle per recalc (pass 1 included). 1 = each member evaluates once per recalc (accumulator contract). 0 is a config error.
max_change0.001Absolute per-member convergence threshold (`

Validation rules

A CycleConfig is rejected at build time (engine construction, with_cycle, or the binding setter) when:

  • Iterate is combined with detection: Static (iteration requires Runtime).
  • Iterate has max_iterations == 0.
  • Iterate has a negative or non-finite max_change.

Selecting the iterate policy through a binding promotes detection to Runtime automatically, so you rarely set both by hand.

Surface map

Equivalent configuration in each binding
use formualizer_eval::engine::{CycleConfig, CycleDetection, CyclePolicy};// Runtime detection only (live cycles still #CIRC!)let cfg = CycleConfig { detection: CycleDetection::Runtime, policy: CyclePolicy::Error };// Excel-default iterative calculation (Runtime + 100 / 0.001)let cfg = CycleConfig::iterate_excel_defaults();// Explicit knobslet cfg = CycleConfig::iterate(50, 0.0001);// Applied via EvalConfig::with_cycle (panics on invalid combos)let eval = formualizer_eval::engine::EvalConfig::default().with_cycle(cfg);

Binding parity notes

CapabilityRustPythonJS/WASM
Set detection modecycle_detectioncycleDetection (load only)
Set policy / iterate knobscycle_policy, iterate_*cyclePolicy, iterate* (load only)
Configure on programmatic (empty) workbookWorkbook(config=...)❌ constructor takes no options
Read CycleTelemetryengine().last_cycle_telemetry()wb.last_cycle_telemetry()❌ config-only today
XLSX <calcPr> round-trip✅ (via load/save)✅ (via fromXlsxBytesWithOptions)

XLSX <calcPr> mapping

<calcPr> attributeCycleConfig
iterate="1"policy = Iterate, detection = Runtime
iterateCount="N"max_iterations = N (defaults to 100 if absent)
iterateDelta="D"max_change = D (defaults to 0.001 if absent)
iterate="0" / absent <calcPr>config left unchanged on load

On load, a file with iterate="1" wins over a configured Error policy (file-wins precedence). On save, an active Iterate policy writes the matching iterate/iterateCount/iterateDelta attributes.

On this page