Expand description
§Rust Simulation Framework
This crate provides a flexible framework for simulating discrete-event systems (DES). It allows users to schedule, manage, and execute events over time, making it suitable for simulations of various systems such as queueing networks, resource allocation systems, and more.
The core components of the framework are:
Event
: A struct representing a single event in the simulation, holding its scheduled time, an action, and context.EventScheduler
: A struct that manages the execution of events, prioritizing those scheduled to run earlier.
§Key Features
- Event Scheduling: Schedule events at specific times or after delays.
- Event Logging: Keep a log of all events executed and their outcomes for later analysis.
- Flexible Execution: Run the scheduler until a certain condition is met, such as reaching a max time.
- Contextual Information: Attach metadata (context) to each event for richer event processing.
§Example: Scheduling an Event
Below is a simple example demonstrating how to create an event, schedule it in the EventScheduler
, and run the simulation.
use desru::{Event, EventScheduler};
fn main() {
let mut scheduler = EventScheduler::new();
let mut event = Event::new(0.0,
Some(Box::new(|scheduler| Some("Executed".to_string()))),
None);
scheduler.schedule(event);
scheduler.run_until_max_time(10.0);
}
§Example: SimPy’s Simple Car
The SimPy Python package provides a simple car example as a first example. You can see from the following state diagram that a car immediately parks, and then alternates between parking and driving at fixed intervals of time. The duration of the car being parked is 5 units of time, while the duration of the car driving is only 2 units of time.
--- title: Simple Car State --- stateDiagram-v2 [*] --> Park Park --> Drive: 5 Drive --> Park: 2
In this example we implement the same process, but using desru
.
use desru::{Event, EventScheduler};
fn car(scheduler: &mut EventScheduler) {
// Start by parking, which repeats in a loop
println!("Start parking at {}", scheduler.current_time);
let parking_duration = 5.0;
let trip_duration = 2.0;
// Schedule event to start driving after parking
scheduler.schedule(Event::new(
scheduler.current_time + parking_duration,
Some(Box::new(move |scheduler: &mut EventScheduler| {
println!("Start driving at {}", scheduler.current_time);
// Schedule event to start parking after driving
scheduler.schedule(Event::new(
scheduler.current_time + trip_duration,
Some(Box::new(move |scheduler: &mut EventScheduler| {
car(scheduler); // Recurse to repeat the cycle
None // No string context needed, returning None
})),
None,
));
None // No string context needed, returning None
})),
None,
));
}
fn main() {
// Initialize the event scheduler
let mut scheduler = EventScheduler::new();
// Start the car simulation
car(&mut scheduler);
// Run the scheduler for a max time of 15 units
scheduler.run_until_max_time(15.0);
}
You should expect to see this output:
Start parking at 0
Start driving at 5
Start parking at 7
Start driving at 12
Start parking at 14
The above implementation was intended to try to do what the SimPy example does: have multiple events scheduled within a single function. However, we can refactor the code using multiple functions that schedule each other. Further, for this simple example at least, we don’t need to keep defining the durations to the same constant values in every function call so we can also define those as global constants. I think it is easier to read this way:
use desru::{Event, EventScheduler};
const MAX_TIME: f64 = 15.0;
const PARK_DURATION: f64 = 5.0;
const DRIVE_DURATION: f64 = 2.0;
fn car(scheduler: &mut EventScheduler) {
park(scheduler);
}
fn park(scheduler: &mut EventScheduler) {
println!("Start parking at {}", scheduler.current_time);
scheduler.schedule(Event::new(
scheduler.current_time + PARK_DURATION,
Some(Box::new(move |scheduler: &mut EventScheduler| {
drive(scheduler);
None
})),
None,
));
}
fn drive(scheduler: &mut EventScheduler) {
println!("Start driving at {}", scheduler.current_time);
scheduler.schedule(Event::new(
scheduler.current_time + DRIVE_DURATION,
Some(Box::new(move |scheduler: &mut EventScheduler| {
park(scheduler); // Return to parking
None
})),
None,
));
}
fn main() {
// Initialize the event scheduler
let mut scheduler = EventScheduler::new();
// Start the car simulation
car(&mut scheduler);
// Run the scheduler for a max time
scheduler.run_until_max_time(MAX_TIME);
}
For those coming from a SimPy background, what the above implementations in desru
show us is
that it will sometimes be simpler to factorize out events into separate functions.
§Example: SimPy’s Objected Oriented Car (“Process Interaction”)
This example is an implementation of SimPy’s Waiting for a Process example. It it highly similar to the previous car example, however a class is used along with multiple methods.
While Rust does not have classes in the usual sense, it does support its own flavor of OOP. While Rust’s OOP does not have inheritance, for which Rust has some useful alternatives, we do not need it to implement an object-oriented implementation of SimPy’s example.
use desru::{Event, EventScheduler};
const CHARGE_DURATION: f64 = 5.0;
const TRIP_DURATION: f64 = 2.0;
struct Car<'a> {
scheduler: &'a mut EventScheduler,
}
impl<'a> Car<'a> {
fn new(scheduler: &'a mut EventScheduler) -> Self {
let mut car = Car { scheduler };
car.start();
car
}
fn start(&mut self) {
// Start the car process
self.scheduler.schedule(Event::new(
self.scheduler.current_time,
Some(Box::new(move |scheduler: &mut EventScheduler| {
let mut car_instance = Car { scheduler }; // Create a car instance with a mutable reference
car_instance.charge(); // Call the run method to start the car process
None
})),
None,
));
}
fn charge(&mut self) {
// Car running process
println!("Start parking and charging at {}", self.scheduler.current_time);
// Schedule the charge process
self.scheduler.schedule(Event::new(
self.scheduler.current_time + CHARGE_DURATION,
Some(Box::new(move |scheduler: &mut EventScheduler| {
let mut car_instance = Car { scheduler };
car_instance.drive(); // After charging, start driving
None
})),
None,
));
}
fn drive(&mut self) {
// Start driving process
println!("Start driving at {}", self.scheduler.current_time);
// Schedule the next run cycle (parking and charging) after the trip duration
self.scheduler.schedule(Event::new(
self.scheduler.current_time + TRIP_DURATION,
Some(Box::new(move |scheduler: &mut EventScheduler| {
let mut car_instance = Car { scheduler };
car_instance.charge(); // Repeat the cycle
None
})),
None,
));
}
}
fn main() {
// Initialize the event scheduler
let mut scheduler = EventScheduler::new();
// Create a car instance
let _car = Car::new(&mut scheduler);
// Run the scheduler for a max time of 15 units
scheduler.run_until_max_time(15.0);
}
§Core Structs
Event
: Defines the core event object used to represent scheduled actions.EventScheduler
: Manages the execution of events over simulated time.
§Customization
You can extend the framework by adding custom event types or adjusting how events are scheduled.
§Design Goals
This framework is designed to be:
- Simple to use: By providing straightforward methods to schedule and run events.
- Flexible: Allowing users to define custom event behaviors.
- Efficient: Using a priority queue to ensure events are executed in the correct order.
§Design Non-Goals
This framework is only for the very most core components for DES, and will not provide implementations of simulation tools.
§Future Directions
Planned features include:
- Advanced Scheduling Policies: Adding support for different event scheduling strategies.
- Performance Optimizations: Improving efficiency for larger simulations.
§Crate Overview
This crate provides essential components for event-driven simulations in Rust. Starting with events and a scheduler, and abstractions that provide weak coupling with state, this crate can be used to implement most conceivable discrete event simulations.
Structs§
- Event
- Represents an event in the simulation.
- Event
Scheduler - Manages and schedules events using a priority queue.