Releases: richardposner/RuleMonkey
RuleMonkey 3.2.0
Added
-
Parameter sweeps:
parameter_scanandbifurcate.
RuleMonkeySimulatorgains two methods —parameter_scan(ScanSpec, seed)andbifurcate(ScanSpec, seed)— the RuleMonkey equivalents of
BioNetGen'sparameter_scanandbifurcateactions. A sweep runs the
model at each value of one parameter (an explicit value list, or a
linear / geometricmin/max/n_pointsrange) and records the
endpoint observable and global-function values, matching BNG's
extraction of the last.gdatrow per run.parameter_scanwith
reset_conc=falseandbifurcatecarry molecular state over between
points;bifurcateruns the forward and backward sweeps as one
continuous trajectory so a bistable model surfaces hysteresis. New
ScanSpec/ScanResult/BifurcateResulttypes intypes.hpp. A
newrm_scancommand-line tool exposes both modes and writes the
result in tab-separated.scanformat on stdout (function columns
gated behind--print-functions, mirroring
#7; see
docs/scan_format.md). Closes
#8. SSA
trajectories and existing output are unchanged; the header-only ABI
change means consumers must rebuild against the new headers. -
Global-function values in the public API.
rulemonkey::Result
now carriesfunction_namesandfunction_dataalongside
observable_names/observable_data, populated at every output time
point;function_datais column-major (function_data[fn_idx][t_idx])
and parallel toobservable_data.RuleMonkeySimulatorgains
function_names()(XML declaration order, captured at construction)
andget_function_values()(live-session readback, mirroring
get_observable_values()). These expose the BNGLbegin functions
entries — the derived quantities models commonly use as their
measured/fitted outputs (e.g.Clusters() = monomer + dimer + …) —
which the engine already evaluates internally for rate laws. Only
global (non-local) functions are surfaced; local functions evaluate
per-molecule and have no single global value, sofunction_namesmay
be shorter than the model's fullbegin functionsblock. The API
surface is unconditional. Closes
#7. SSA
trajectories and observable output are unchanged; the header-only ABI
change means consumers must rebuild against the new headers. -
rm_driver --print-functions. A new opt-in flag that appends the
model's global-function values as trailing.gdatcolumns (after the
observables). Off by default, mirroring BNGL'sprint_functions=>1:
the default.gdatstays observables-only and byte-identical to what
earlier RM versions emitted. The flag governs onlyrm_driver's text
output — the in-processResultAPI exposes the values regardless. -
tests/cpp/function_values_test.cpp— regression test for the new
function surface:function_names()declaration order, the
column-major shape ofResult::function_data, per-sample algebraic
consistency with the observables each function derives from (covering
nested function-of-function settle order), liveget_function_values()
readback againstget_observable_values(), the no-session throw, and
the empty-not-absent function surface of a model with no functions. -
Cooperative cancellation hook on
run()/simulate()/step_to().
Each of the three public entry points now accepts an optional
rulemonkey::CancelCallback(astd::function<bool()>) that the SSA
event loop polls roughly every 1024 events; returningfalseraises
rulemonkey::Cancelled(astd::runtime_errorsubclass) at a safe
between-event point. Empty callbacks disable polling and pay no
per-event overhead. This unblocks the BNGsimtimeoutkwarg for the
RuleMonkey backend (closes
#3); the prior
workaround of wrapping each evaluation in a subprocess can now go
away. Source-compatible — existing callers see only the defaulted
parameter — but mangled-name ABI changes, so consumers must rebuild
against the new headers. -
tests/cpp/cancellation_test.cpp— regression test for the four
behavioral contracts the new hook adds: pre-cancelled callback throws
on entry,Cancelledinheritsstd::runtime_error, mid-session
simulate()cancellation leaves the session live with
current_time()strictly inside the requested window and is
recoverable viadestroy_session()+ re-initialize(), and an
always-true callback produces a bit-identical trajectory to the
no-callback path. -
Species enumeration, canonical complex labeling, and
.species
output. RuleMonkey can now enumerate the distinct chemical species
in the live pool by graph isomorphism. A new DIY canonical-labeling
core (cpp/rulemonkey/canonical.{hpp,cpp}— 1-WL color refinement
plus individualization–refinement for symmetric residue such as rings
and homo-oligomers; no nauty/bliss, preserving the cleanroom property)
assigns each complex a canonical normalized-BNGL label.
RuleMonkeySimulatorgainsenumerate_species()(returnsSpeciesRow
records — a new type intypes.hpp),write_species_file(path)
(BNG-format.speciesoutput, live species only, NFsim-ssparity —
seedocs/species_format.md),
species_count(canonical_species), andtotal_complex_count(). A new
rm_driver --species <path>flag writes the.speciesfile from the
command line. A cached-incremental labeling mode (per-complex cached
label with dirty-bit invalidation in the structural mutators) is built
and validated by a Debug/ASan-build invariant — cached label equals a
from-scratch recompute, gated by theRULEMONKEY_CANONICAL_CACHE_SELFCHECK
compile definition — awaiting its downstream consumer. Closes
#9 §2. New
ctest casescanonical_test,species_enumeration_test. Header-only
ABI change — consumers must rebuild against the new headers. -
Session API: live expression evaluation and pattern-keyed species
methods. On an active session,RuleMonkeySimulatorgains
evaluate_expression(expr, extra)— compiles and evaluates an
arbitrary BNGL expression against the live session (parameters,
observables, global functions, andtime()/t; an optionalextra
map shadows those names on clash) — and four pattern-keyed species
methods,get_species_count/add_species/remove_species/
set_species_count, each taking a BNGL species-pattern string. A new
runtime BNGL species-pattern parser (cpp/rulemonkey/pattern_parser.{hpp,cpp})
backs the latter four: it accepts exact, fully-specified, connected
species (every component listed, stateful components with a concrete
~state, numeric bonds) and rejects partial patterns (!+/!?/
omitted components).get_species_countcanonicalizes the parsed
species and reuses thespecies_countlookup above;
add_/remove_/set_resync all rule propensities after the
structural change. Closes
#9 §1 (and,
with §2 above and §3 — which needed no work — issue #9 in full). New
ctest casesevaluate_expression_test,pattern_parser_test,
species_methods_test. Header-only ABI change — consumers must
rebuild against the new headers.
Changed
-
Expression evaluator: hand-rolled parser replaced with ExprTk. The
BNGL rate-law / function / parameter math evaluator (expr_eval) is now
ExprTk, via the
bngsim::ExprTkEvaluatorwrapper RuleMonkey shares with its BNGsim
integration host. All four expression consumers — global functions,
rate-law ASTs, the simulator parameter cascade, and local functions —
moved at once; the hand-rolled recursive-descent parser andAstNode
tree-walker are gone. Expression evaluation is ~16–30% faster per call
on function-rate models (no effect on mass-action); SSA trajectories
are bit-identical to 3.1.x. Closes
#6. No public
API or header change. Build note: ExprTk is vendored under
third_party/and compiled only in a standalone build — a CMake gate
(if(TARGET bngsim::expression)) links the host's copy inside a BNGsim
build instead.scripts/vendor_exprtk.py --checkguards the vendored
copy against drift from its pinned BNGsim commit. -
CMake vendoring defaults. The minimum CMake version is now 3.20.
RULEMONKEY_BUILD_TESTSandRULEMONKEY_BUILD_CLIdefault to
PROJECT_IS_TOP_LEVEL,RULEMONKEY_WARNINGS_AS_ERRORSdefaults off
when RuleMonkey is added as a subdirectory, and tests no longer depend
onCMAKE_SOURCE_DIR. -
Local-function rate laws: redundant per-molecule observable
re-evaluation eliminated. On models with local-function rate laws,
evaluate_local_raterecomputed each rule's local observables from
scratch (count_embeddings_*) for every affected molecule on every
event — up to ~75% of wall time on local-function-heavy models.
evaluate_observable_onnow routes trackedMolecules-type
observables through the per-moleculeobs_mol_contribtable that the
species-observable incremental machinery already maintains and
refreshes before the propensity recompute each event: per-molecule
scope becomes a table read, complex-wide scope a sum over the complex
— no embedding counts. A from-scratch recompute remains as a
bounds-checked fallback, and a Debug/ASan-build invariant (gated by
theRULEMONKEY_LOCAL_OBS_SELFCHECKcompile definition) cross-checks
the fast path against it. Wall-time reductions:isingspin_localfcn
71%,ANx20%,AN16%,t39%. Cl...
RuleMonkey 3.1.2
[3.1.2] — 2026-05-02
Added
-
docs/internals.md— engine-internals reading guide for
contributors about to modifycpp/rulemonkey/engine.cpp. Covers
the SSA event loop, the three pattern-matching layers
(count_embeddings_single,count_multi_mol_fast,
count_2mol_1bond_fc), complex tracking on bind/unbind, propensity
computation andincremental_update, the 2-mol/1-bond fast-path
specialization,fire_rule's OpType switch, and the five
select_reactantspaths. Cites engine.cpp line ranges as anchors. -
"Adding a new profile" recipe in
engine_profile.hpp. Five
mechanical steps to wire a gate, struct, member, increment site,
and report function for a new hot path. Existing per-profile gate
comments and field-level documentation were already strong; the
missing piece was a contributor recipe. -
tests/cpp/error_paths_test.cpp— pins down that the
documented public-API error surfaces throwstd::runtime_error
(notstd::exception, not silent failure) for: missing XML file,
malformed XML, unknownset_paramname, and the four mutators
that reject calls while a session is active (set_param,
clear_param_overrides,set_molecule_limit,
set_block_same_complex_binding). Previously these paths
existed insimulator.cppbut were only exercised indirectly by
the corpus parity tests. -
harness/perf_diff.py— diffs per-model wall-time between two
feature_coverage_report.mdfiles. Sorts by absoluteΔ%;
flags ±15% asSLOWER/FASTER; marksNEW/GONEfor
models present on only one side. Companion.github/workflows/perf-diff.yml
runs the full feature_coverage benchmark on both PR base and HEAD
on the same runner (controls hardware variance) and uploads the
diff as an artifact. Not a hard gate — shared GitHub runners are
noisy enough that single-model deltas of 30%+ come from
neighbour-VM contention rather than real regressions.
Changed
-
-Werroris on by default for the in-tree build, gated by
RULEMONKEY_WARNINGS_AS_ERRORS=ON. Default ON so a stray warning
shows up on the developer's machine before it lands in CI.
Downstream consumers building RM as a subdirectory or against an
installed package can opt out with
-DRULEMONKEY_WARNINGS_AS_ERRORS=OFFif their toolchain flags
things ours does not. Verified clean against AppleClang 17;
CI exercises Linux clang and gcc. -
CI
asanjob is now a Linux + macOS matrix. Same code, same
compiler family (clang), but different stdlib (libstdc++ vs
libc++) and different sanitizer-runtime image — exactly the
divergence that hides UB on one platform and reveals it on the
other. The CI step sets
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1and
ASAN_OPTIONS=detect_leaks=0to keep diagnostic output uniform
across platforms.