pflow-rs
Rust port of go-pflow — Petri net modeling with ODE simulation and token model DSL.
Crates
| Crate | Description |
|---|---|
pflow-core |
Petri net types (Place, Transition, Arc), fluent Builder API, state map utilities |
pflow-solver |
ODE solvers (Tsit5, RK45, RK4, Euler, Heun, Midpoint, BS32), implicit methods, equilibrium detection |
pflow-tokenmodel |
Token model schema, snapshot, runtime execution, validation, content-addressed identity |
pflow-dsl |
S-expression DSL: lexer, parser, interpreter, builder, codegen |
pflow-macros |
schema! proc macro — compile-time DSL parsing with zero runtime overhead |
pflow |
Umbrella crate re-exporting all of the above |
Quick Start
use *;
// Build an SIR epidemic model
let = build
.sir
.with_rates;
// Solve to equilibrium
let state = net.set_state;
let prob = new;
let = find_equilibrium;
assert!;
// S + I + R = 1000 (conserved)
Petri Net Builder
use PetriNet;
let net = build
.place
.place
.transition
.arc
.arc
.done;
Chain helper for linear sequences:
let net = build
.chain
.done;
ODE Solver
Seven explicit Runge-Kutta methods plus implicit solvers for stiff systems:
use *;
let prob = new;
// Explicit (adaptive step size)
let sol = solve;
// Implicit (stiff systems)
let sol = implicit_euler;
let sol = trbdf2;
// Auto-detect stiffness
let sol = solve_implicit;
Solver presets:
| Preset | Use Case |
|---|---|
Options::default_opts() |
General purpose |
Options::fast() |
Game AI, interactive (~10x faster) |
Options::accurate() |
Research, publishing |
Options::game_ai() |
Move evaluation |
Options::epidemic() |
SIR/SEIR models |
Token Model DSL
Define token model schemas using an S-expression DSL. The schema! macro parses and validates the DSL at compile time — syntax errors become compiler errors, and the generated code constructs the Schema directly with zero runtime parsing.
use schema;
let s = schema!;
assert_eq!;
assert_eq!;
Invalid DSL is caught at compile time:
// This won't compile:
let s = schema!;
// error: DSL parse error: expected symbol "schema", got "bad"
DSL Syntax Reference
(schema <name>
(version <version>)
(states
(state <id> :kind token :initial <n>) ; token state with initial count
(state <id> :type <type> :exported) ; data state, exported
)
(actions
(action <id>) ; simple action
(action <id> :guard {<expr>}) ; guarded action
)
(arcs
(arc <source> -> <target>) ; simple arc
(arc <source> -> <target> :keys (<k1> <k2>)) ; arc with map keys
(arc <source> -> <target> :value <binding>) ; arc with value binding
)
(constraints
(constraint <id> {<expr>}) ; invariant constraint
)
)
Alternative: Fluent Builder
For dynamic schema construction, use the builder API directly:
use Builder;
let schema = new
.data.exported
.data
.action.guard
.flow.keys
.flow.keys
.constraint
.must_schema;
Runtime Parsing
For DSL strings loaded at runtime (e.g. from files):
use parse_schema;
let schema = parse_schema.unwrap;
Content-Addressed Identity
Schemas produce deterministic content identifiers (CIDs) via SHA-256. Insertion order doesn't matter — schemas with the same structure always produce the same hash.
use schema;
let s = schema!;
// Full CID (includes name/version)
let cid = s.cid; // "cid:a1b2c3..."
// Structural fingerprint (ignores name/version)
let idh = s.identity_hash; // "idh:d4e5f6..."
// Compare schemas
assert!; // same CID
assert!; // same structure
Runtime Execution
use ;
use Value;
let mut rt = new;
// Set initial balance
rt.snapshot.set_data_map_value;
// Execute a transfer
let mut bindings = new;
bindings.insert;
bindings.insert;
bindings.insert;
rt.execute_with_bindings.unwrap;
// alice: 750, bob: 250
State Utilities
use stateutil;
let state = /* ... */;
let updated = apply;
let total = sum;
let changes = diff;
let history = filter;
Code Generation
Generate Rust source from DSL definitions:
use generate_rust_from_dsl;
let code = generate_rust_from_dsl.unwrap;
// Outputs a Rust function that constructs the schema
Dependencies
Minimal external dependencies:
serde,serde_json— serializationsha2,hex— content-addressed identitythiserror— error types
License
MIT