Skip to main content

Crate odem_rs

Crate odem_rs 

Source
Expand description

§Object-based Discrete-Event Modeling for Rust

crates.io docs.rs License: MIT MSRV

Odem-rs is a discrete-event simulation library for Rust that uses async/await to model concurrent agents. It provides a deterministic, extensible framework for building Monte Carlo-style simulation models where agents interact through scheduled jobs.

§Key Features

  • Process-Based Simulation: Models simulation entities as asynchronous agents with isolated state and internally concurrent jobs.
  • Event-Driven Execution: Skips directly between significant events rather than executing at fixed time intervals.
  • Deterministic & Portable: Uses deterministic PRNGs and cooperative concurrency for reproducible results.
  • Flexible & Extensible: Provides library traits to tailor simulators and data structures for custom needs.
  • Safe & Sound: Validated with Miri to detect undefined behavior and enforce stacked borrow rules.
  • Integrated: Integrates seamlessly with tracing, uom, rand, and rayon.
  • Embedded-friendly: Can be used in bare-metal environments with a reduced feature-set.

Try the interactive simulation dashboard to experiment with example models in your browser.

§Getting Started

§Installation

Add odem-rs to your Cargo.toml:

[dependencies]
odem-rs = "0.4"
§Example: Barbershop Simulation

To get you started quickly, here is an example scenario featuring a simple barbershop model. Customers arrive at random intervals and request service from a single barber. Once a customer arrives, they occupy the barber for a random duration simulating the haircut, then release the barber for the next customer. The simulation will run for 12 hours.

use odem_rs::prelude::*;

#[derive(Config, Default)]
#[time(Time<f64>)]
struct Barbershop {
	joe: Facility,
	rng_stream: RngStream,
}

struct Customer(Time<f64>);
impl Behavior<Barbershop> for Customer {
	type Output = ();

	async fn actions(&self, sim: &Sim<Barbershop>) {
		let chair = sim.global().joe.seize().await;
		sim.advance(self.0).await;
		chair.release();
	}
}

#[odem_rs::main]
async fn main(sim: &Sim<Barbershop>) {
	sim.fork(async {
		let mut rng_a = sim.global().rng_stream.next_rng();
		let mut rng_s = sim.global().rng_stream.next_rng();

		loop {
			let arrival = minute::new(rng_a.random_range(12.0..24.0));
			let service = minute::new(rng_s.random_range(12.0..18.0));
			sim.advance(arrival).await;
			sim.spawn(Customer(service));
		}
	}).or(sim.advance(hour::new(12.0))).await;
}

More examples can be found in the examples directory.

§Execution Model

Odem-rs follows a discrete-event execution model, where simulation advances incrementally based on scheduled events. At any given model time, all continuations (representing agents and jobs) that are scheduled for execution are processed instantaneously in terms of model time before the clock is advanced. Thus, the passing of model time is explicit, allowing for deterministic execution. The execution order is determined by:

  • Model time: Monotonically increasing time preserves cause-and-effect relationships between processes.
  • Rank (between agents): Defines execution priority among different agents.
  • Precedence (between jobs): Ensures the correct sequencing of dependent jobs within one agent.
  • Insertion Order: Resolves ties between jobs with equal precedence by executing them in the order they were scheduled.

Once all tasks at a given model time have executed, the simulation clock jumps to the next scheduled event. This execution model is managed by an event calendar integrated into the async/await runtime executor, ensuring deterministic ordering while leveraging Rust’s native concurrency.

§Feature Flags

Odem-rs uses Cargo feature flags to control optional functionality. All flags listed as “default” are enabled when you add odem-rs as a dependency without further configuration. To start from a minimal base, set default-features = false and enable only what you need.

FeatureDefaultDescription
stdyesStandard library support (implies alloc).
allocyesHeap allocation for dynamic data structures.
tracingyesStructured instrumentation via the tracing crate.
debug-tracingnoDebug-level trace output for internal state machines.
uomyesType-safe SI quantities for model time.
rand_pcgyesPCG-family pseudo-random number generators.
rand_xoshironoXoShiRo-family pseudo-random number generators.
rand_distryesProbability distributions from the rand_distr crate.
rayonyesParallel iteration for independent simulation runs.
libmnoSoftware math functions for no_std environments.

See the API documentation for detailed descriptions of each feature flag.

§Re-exported Crates

Every external crate whose types appear in the API of odem-rs is re-exported from the crate root, in the version odem-rs was built against. Reach for them through odem-rs rather than depending on them separately: a second copy of any of these crates in a dependency graph is a distinct set of types, and mixing the two produces errors that name the same type twice, or - for tracing - a subscriber that silently receives nothing.

CrateVersionFeatureWhere it surfaces
allocator_api20.4alwaysthe allocator a configuration provides, Config::Alloc
intrusive_collections0.10alwaysthe link and adapters that fit continuations into a custom calendar
rand0.10alwaysthe generators an RngStream hands out
uom0.38uomthe quantities that model time is made of
tracing0.1tracingthe events the simulator emits
rayon1rayonthe parallel iterator over random number streams
typed_arena2allocthe backing store of a dynamic Pool
rand_pcg0.10rand_pcgthe PCG generator behind DefaultRng
rand_xoshiro0.8rand_xoshirothe generator behind DefaultRng when rand_pcg is off
rand_distr0.6rand_distrnone of its types, but its distributions must agree with rand on rand_core
use odem_rs::rand;        // always available
use odem_rs::uom;         // requires feature "uom"
use odem_rs::rayon;       // requires feature "rayon"
use odem_rs::rand_distr;  // requires feature "rand_distr"

The re-exports do not create that coupling, they make it usable: the signatures already pin the version, and re-exporting is what lets a model name what it is pinned to. It follows that a major-version bump of any of these crates is a breaking change for odem-rs, and reaches models through a new version of it.

§Contributors & Acknowledgments

Odem-rs is the result of doctoral research and has benefited from contributions by:

  • Lukas Markeffsky, with whom I enjoyed in-depth discussions on Rust’s type system and whose incredible insight and sharp mind helped a lot to find and resolve soundness issues in addition to improving the ergonomics of the library.
  • Paula Wiesner, with whom I performed early prototyping of agent-based approaches and who is still interested in the progress of this project, despite having successfully moved past academia. I appreciate the enthusiasm!

I welcome contributions, feedback, and feature requests! Open an issue or submit a PR.

§License

Odem-rs is licensed under MIT. See LICENSE for details.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in odem-rs by you, shall be licensed as MIT, without any additional terms or conditions.

§Crate Organization

Odem-rs is structured into multiple crates for modularity, which can be accessed from the root crate as follows:

Crate NameDescription
odem_rsEntry crate providing access to the core simulation framework.
odem_rs::coreDefines the foundational structures and traits for event-driven modeling.
odem_rs::syncProvides synchronization and communication primitives for agent-job interaction.
odem_rs::utilUtility crate with pools, PRNG streams, statistics, and physical quantities.

The batteries included are the executor with its event calendar, the concurrency primitives for the inside of an agent, the synchronization structures (channels and control variables), the PRNG generator streams and random variables, and the garbage-collected object pools. The library traits tailor the simulator and its data structures to a model, and the prelude gathers what most models need. The event calendar is an extension point of its own: the #[calendar(...)] attribute of derive(Config) selects an implementation of the Calendar trait, whose module documentation walks through writing one.

§Running a Model

A model is a configuration type carrying the state the processes share, and an async function over a simulation context. Start it with the #[odem_rs::main] attribute, as the barbershop above does: it writes the main function around the model and reports a deadlock.

Two more explicit entry points sit below it, and a model moves down the list when it outgrows a rung:

Entry pointConfigurationReturns
#[odem_rs::main]default or an expressionwhat the model returns
simulationdefaultthe configuration
Simulator::runanywhat the model returns

simulation is the one to reach for when a run is a replication among many, since it hands back the configuration the run wrote its results into; the RngStream documentation shows an experiment built that way.

§Feature Details

The feature table above lists what there is; this is what each flag does.

§Environment

  • std: Enables standard library support and implies alloc. Disable this for bare-metal or WebAssembly targets that do not provide a standard library.
  • alloc: Enables heap-allocated data structures such as dynamic channels, dynamic object pools, and per-type agent identifiers, and selects the global allocator as the default backing for processes created through Sim::spawn. Automatically activated by std, but can be enabled independently on targets that provide an allocator without the full standard library. Without it, spawned processes require a user-supplied allocator - such as a StaticHeap - configured through the #[alloc] attribute of derive(Config).
  • libm: Provides software implementations of math functions (e.g. sqrt) via the libm crate. Only needed in no_std environments where hardware floating-point or standard-library math is unavailable. When std is active, math functions from the standard library are used instead.

§Integrations

  • tracing: Integrates with the tracing ecosystem to provide structured, span-based instrumentation of agents and continuations. Useful for debugging simulation models and understanding execution flow.
  • debug-tracing: Emits additional debug-level trace events for finite-state machine transitions and channel operations. A tracing subscriber must be configured for these events to be visible.
  • uom: Enables the uom crate for modeling time with type-safe SI quantities. With this feature, model time can be expressed using units like hour::new(12.0) or second::new(0.5) rather than raw numeric values.

§Random Number Generators

The rand crate is always enabled. The following features control which PRNG backends are available:

  • rand_pcg: Provides PCG-family generators (Pcg32, Pcg64Dxsm, etc.) and sets the DefaultRng type alias to Pcg64Dxsm on every target, so that a model draws the same numbers everywhere.
  • rand_xoshiro: Provides XoShiRo-family generators (Xoshiro256PlusPlus, etc.) as an alternative backend, which sets DefaultRng to Xoshiro256PlusPlus when rand_pcg is off.
  • rand_distr: Re-exports the rand_distr crate with the probability distributions to sample from, in the version that matches the re-exported rand.

§Parallelism

  • rayon: Enables parallel iteration support via the rayon crate, allowing independent simulation replications to run across multiple threads.

Re-exports§

pub use odem_rs_core as core;
pub use odem_rs_sync as sync;
pub use odem_rs_util as util;
pub use uom;uom
pub use tracing;tracing
pub use rayon;rayon
pub use typed_arena;alloc
pub use rand_pcg;rand_pcg
pub use rand_xoshiro;rand_xoshiro
pub use rand_distr;rand_distr
pub use allocator_api2;
pub use intrusive_collections;
pub use rand;

Modules§

error
This module contains the definition of the combined error-type of the simulation library.
prelude
A module for re-exports of simulation-library concepts that are essential for most simulation models.

Attribute Macros§

main
Generates a synchronous main function that sets up a simulator, runs the provided asynchronous simulation logic, and handles the results.