Skip to main content

Crate odem_rs

Crate odem_rs 

Source
Expand description

Odem-rs is an Object-based Discrete-Event Modeling library for Rust, enabling Monte Carlo–style simulation models via async/await. It provides a flexible, extensible framework where concurrent agents represent simulation actors.

§Overview

  • Process-Based: Models simulation entities as asynchronous agents with isolated state and behavior, while allowing internal concurrency via jobs.
  • Discrete-Event: Schedules and executes events at distinct points in model time, skipping from event to event rather than at a fixed rate.
  • Deterministic: Results of simulation runs are fully portable due to the use of deterministic pseudo-random-number generators (PRNG) in combination with cooperative concurrency. This also enables the use of !Send closures while still allowing independent simulation runs to be parallelized safely.
  • Adaptable: Exposes library traits to tailor simulators and data structures to specific simulation models.
  • Sound: Validated with Miri to detect undefined behavior and tests compliance with stacked borrow rules.
  • Batteries Included: Provides essential components for simulation:
  • Ecosystem: Integrates seamlessly with tracing, uom, and rand for logging, unit-of-measure modeling, and random number generation.
  • no_std/no_alloc: Supports execution in bare-metal environments even without a memory allocator, albeit with fewer dynamic-collection-based features.

§Execution Principle

The core of odem-rs is its event-driven execution model, which ensures that simulation models evolve incrementally in a structured manner. 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): Determines execution priority among 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 active continuations have been executed at a given model time, the simulation time advances to the next scheduled event, skipping idle periods where no actions occur.

This execution model is managed by an event calendar, which is a part of the async/await runtime executor. The executor schedules and processes continuations in deterministic order as described above. This allows odem-rs to seamlessly integrate with Rust’s async runtime model, leveraging native concurrency while maintaining strict event sequencing.

§Introductory 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::*, sync::facility::Facility};
use core::pin::pin;
use rand::RngExt;

#[derive(Config, Default)]
struct Barbershop {
    barber: Facility,
    rng_stream: RngStream,
}

struct Customer(f64);

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

    async fn actions(&self, sim: &Sim<Barbershop>) {
        // Acquire the barber
        let chair = sim.global().barber.seize().await;
        // Simulate the haircut duration
        sim.advance(self.0).await;
        // Release the barber for the next customer
        chair.release();
    }
}

#[odem_rs::main]
async fn main(sim: &Sim<Barbershop>) {
    // Process responsible for generating customers
    sim.fork(async {
        let mut rng_a = sim.global().rng_stream.rng();
        let mut rng_s = sim.global().rng_stream.rng();
        let pool = pin!(Pool::dynamic().with(Agent::new));

        loop {
            // Wait a random time before creating the next customer
            let arrival = rng_a.random_range(12.0..24.0);
            let service = rng_s.random_range(12.0..18.0);
            sim.advance(arrival).await;
            sim.activate(pool.alloc(Customer(service)));
        }
    })
    // Run the barbershop for 12 hours
    .or(sim.advance(12.0 * 60.0))
    .await;
}

More examples can be found in the examples directory.

§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.

§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.
rayonyesParallel iteration for independent simulation runs.
libmnoSoftware math functions for no_std environments.

§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. Automatically activated by std, but can be enabled independently on targets that provide an allocator without the full standard library.
  • 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 a PCG variant matching the target’s pointer width.
  • rand_xoshiro: Provides XoShiRo-family generators (Xoshiro256PlusPlus, etc.) as an alternative backend.

§Parallelism

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

§Re-exported Crates

Odem-rs re-exports its ecosystem dependencies so that simulation models can use version-compatible crates without adding them as separate dependencies. Each re-export is available as a module under odem_rs::, e.g. odem_rs::rand or odem_rs::uom.

CrateVersionFeature
rand0.10always
intrusive_collections0.10always
uom0.38uom
tracing0.1tracing
rayon1rayon
typed_arena2alloc
rand_pcg0.10rand_pcg
rand_xoshiro0.8rand_xoshiro

This means you can depend on odem-rs alone and use its re-exports:

use odem_rs::rand;           // rand 0.10
use odem_rs::uom;            // uom 0.38   (requires feature "uom")
use odem_rs::rayon;          // rayon 1    (requires feature "rayon")

If you need additional items from these crates (e.g. rand_distr), add them to your Cargo.toml with a compatible version to avoid duplication:

[dependencies]
odem-rs = "0.3"
rand_distr = "0.6"  # compatible with rand 0.10

§Contributors & Acknowledgments

Inspired by the Scandinavian approach to discrete-event simulation as exemplified - among others - through Simula, GPSS, SLX, ODEMx, odem-rs refines these concepts in a Rust-centric framework.

Odem-rs is part of ongoing 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!

Explore the modules and traits to uncover the library’s full potential.

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 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.