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
| Value | Behavior |
|---|---|
Static (default) | Every static SCC is stamped #CIRC!. No live-edge machinery runs. Byte-for-byte compatible with prior releases. |
Runtime | Static 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.
| Value | Behavior |
|---|---|
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
| Knob | Excel default | Notes |
|---|---|---|
max_iterations | 100 | Total passes per cycle per recalc (pass 1 included). 1 = each member evaluates once per recalc (accumulator contract). 0 is a config error. |
max_change | 0.001 | Absolute per-member convergence threshold (` |
Validation rules
A CycleConfig is rejected at build time (engine construction, with_cycle, or the binding setter) when:
Iterateis combined withdetection: Static(iteration requiresRuntime).Iteratehasmax_iterations == 0.Iteratehas a negative or non-finitemax_change.
Selecting the iterate policy through a binding promotes detection to Runtime automatically, so you rarely set both by hand.
Surface map
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
| Capability | Rust | Python | JS/WASM |
|---|---|---|---|
| Set detection mode | ✅ | ✅ cycle_detection | ✅ cycleDetection (load only) |
| Set policy / iterate knobs | ✅ | ✅ cycle_policy, iterate_* | ✅ cyclePolicy, iterate* (load only) |
| Configure on programmatic (empty) workbook | ✅ | ✅ Workbook(config=...) | ❌ constructor takes no options |
Read CycleTelemetry | ✅ engine().last_cycle_telemetry() | ✅ wb.last_cycle_telemetry() | ❌ config-only today |
XLSX <calcPr> round-trip | ✅ | ✅ (via load/save) | ✅ (via fromXlsxBytesWithOptions) |
XLSX <calcPr> mapping
<calcPr> attribute | CycleConfig |
|---|---|
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.