Skip to content

Commit

Permalink
Merge pull request #5 from iscar-ucm/dev
Browse files Browse the repository at this point in the history
Ready for v0.3.0
  • Loading branch information
romancardenas authored Mar 8, 2023
2 parents db7ebc1 + 2c0dabb commit e0b9fca
Show file tree
Hide file tree
Showing 16 changed files with 212 additions and 203 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "xdevs"
version = "0.2.1"
version = "0.3.0"
authors = ["Román Cárdenas <[email protected]>"]
edition = "2021"
description = "An open source DEVS M&S framework."
Expand Down Expand Up @@ -40,3 +40,6 @@ name = "gpt_efp"
[profile.release]
lto = true
panic = "unwind"

[package.metadata.docs.rs]
features = ["par_all"]
30 changes: 21 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,26 @@

Version of the xDEVS simulator for Rust projects.
It allows you to build and simulate computational models following the DEVS formalism.
Its API is easy to use for DEVS practitioners.
Currently, their main features are speed and parallelism.
Its API is easy to use for DEVS practitioners. Currently, their main features are speed and parallelism.

## Blazingly fast 🚀

The Rust version of xDEVS is one of the fastests APIs currently available.
We will shortly publish some preliminary results to illustrate this.

## Unsafe but sound 🔐

We all love purely safe Rust crates. However, it is extremely difficult to provide a safe **AND**
fast implementation of DEVS event propagations. Thus, we decided to use `unsafe` Rust to model
DEVS ports and message propagation. In this way, we can provide the fastest DEVS implementation
we could think of. But don't worry! We have carefully studied the DEVS simulation algorithm
to come up with all the invariants that make our implementation safe even with `unsafe` code.
All the `unsafe` methods of ports come with proper documentation to let you know when it is safe
to use these `unsafe` methods!

**Spoiler alert:** if you don't try to *hack* the DEVS simulation workflow,
then you will always fufill the invariants to safely build your models.

## Fully configurable parallelism 🧶

We rely on the [`rayon`](https://github.com/rayon-rs/rayon) crate to provide parallelism for your simulations.
Expand All @@ -18,22 +30,22 @@ select where you want to take advantage of parallelism:

- `par_start`: it runs in parallel the start methods of your model before starting to simulate.
- `par_collection`: it executes the lambdas of your models in parallel.
- `par_eoc`: it propagates the EOCs in parallel (we do not recommend this feature, it is likely to be removed).
- `par_xic`: it propagates the EICs and ICs in parallel (we do not recommend this feature, it is likely to be removed).
- `par_eoc`: it propagates the EOCs in parallel (we **DO NOT** recommend this feature, it is likely to be removed).
- `par_xic`: it propagates the EICs and ICs in parallel (we **DO NOT** recommend this feature, it is likely to be removed).
- `par_transition`: it executes the deltas of your models in parallel (we **DO** recommend this feature).
- `par_stop`: it runs in parallel the stop methods of your model after the simulation.

### Useful combined features

We provide additional features to select handy combinations of features:

- `par_xxc`: alias for `par_eoc` and `par_xic` (we do not recommend this feature, it is likely to be removed).
- `par_xxc`: alias for `par_eoc` and `par_xic` (we **DO NOT** recommend this feature, it is likely to be removed).
- `par_sim_no_xcc`: alias for `par_collection` and `par_transition`.
- `par_sim`: alias for `par_xxc` and `par_sim_no_xcc` (we do not recommend this feature, it is likely to be removed).
- `par_sim`: alias for `par_xxc` and `par_sim_no_xcc` (we **DO NOT** recommend this feature, it is likely to be removed).
- `par_all_no_xcc`: alias for `par_start`, `par_sim_no_xcc`, and `par_stop` (**this is our favourite**).
- `par_all`: alias for `par_xxc` and `par_all_no_xcc` (we do not recommend this feature, it is likely to be removed).
- `par_all`: alias for `par_xxc` and `par_all_no_xcc` (we **DO NOT** recommend this feature, it is likely to be removed).

## Work in progress 👷‍♀️👷👷‍♂️

We are still working on this crate, and hope to add a plethora of cool features in the near future. Stay tuned!
If you want to contribute, feel free to open an issue on GitHub, we will reply ASAP.
We are still working on this crate, and hope to add a plethora of cool features in the near future.
Stay tuned! If you want to contribute, feel free to open an issue on GitHub, we will reply ASAP.
2 changes: 1 addition & 1 deletion examples/devstone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ fn main() {
let duration = start.elapsed();
println!("Simulator creation time: {duration:?}");
let start = Instant::now();
simulator.simulate_time(f64::INFINITY);
simulator.simulate(f64::INFINITY);
let duration = start.elapsed();
println!("Simulation time: {duration:?}");
}
23 changes: 15 additions & 8 deletions examples/gpt_efp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,17 @@ impl Atomic for Generator {
&mut self.component
}
fn lambda(&self) {
self.output.add_value(self.count);
// Safety: adding message on atomic model's output port at lambda
unsafe { self.output.add_value(self.count) };
}
fn delta_int(&mut self) {
self.count += 1;
self.sigma = self.period;
}
fn delta_ext(&mut self, e: f64) {
self.sigma -= e;
if !self.input.is_empty() {
// Safety: reading messages on atomic model's input port at delta_ext
if !unsafe { self.input.is_empty() } {
self.sigma = f64::INFINITY;
}
}
Expand Down Expand Up @@ -86,7 +88,8 @@ impl Atomic for Processor {

fn lambda(&self) {
if let Some(job) = self.job {
self.output.add_value((job, self.time));
// Safety: adding message on atomic model's output port at lambda
unsafe { self.output.add_value((job, self.time)) };
}
}

Expand All @@ -98,7 +101,8 @@ impl Atomic for Processor {
fn delta_ext(&mut self, e: f64) {
self.sigma -= e;
if self.job.is_none() {
self.job = Some(*self.input.get_values().first().unwrap());
// Safety: reading messages on atomic model's input port at delta_ext
self.job = Some(*unsafe { self.input.get_values() }.first().unwrap());
self.sigma = self.time;
}
}
Expand Down Expand Up @@ -141,7 +145,8 @@ impl Atomic for Transducer {
}

fn lambda(&self) {
self.output.add_value(true);
// Safety: adding message on atomic model's output port at lambda
unsafe { self.output.add_value(true) };
}

fn delta_int(&mut self) {
Expand All @@ -152,10 +157,12 @@ impl Atomic for Transducer {
fn delta_ext(&mut self, e: f64) {
self.sigma -= e;
let t = self.component.get_t_last() + e;
for job in self.input_g.get_values().iter() {
// Safety: reading messages on atomic model's input port at delta_ext
for job in unsafe { self.input_g.get_values() }.iter() {
println!("generator sent job {job} at time {t}");
}
for (job, time) in self.input_p.get_values().iter() {
// Safety: reading messages on atomic model's input port at delta_ext
for (job, time) in unsafe { self.input_p.get_values() }.iter() {
println!("processor processed job {job} after {time} seconds at time {t}");
}
}
Expand Down Expand Up @@ -238,5 +245,5 @@ fn main() {
_ => panic!("unknown model type. It must be either \"gpt\" or \"efp\""),
};
let mut simulator = RootCoordinator::new(coupled);
simulator.simulate_time(f64::INFINITY)
simulator.simulate(f64::INFINITY)
}
12 changes: 7 additions & 5 deletions src/devstone/atomic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ impl DEVStoneAtomic {
}

#[inline]
fn busy_sleep(duration: &Option<Duration>) {
fn sleep(duration: &Option<Duration>) {
if let Some(duration) = duration {
#[cfg(feature = "devstone_busy")]
{
Expand Down Expand Up @@ -116,21 +116,23 @@ impl Atomic for DEVStoneAtomic {

#[inline]
fn lambda(&self) {
self.output.add_value(self.state.n_events);
// Safety: adding message on atomic model's output port at lambda
unsafe { self.output.add_value(self.state.n_events) };
}

#[inline]
fn delta_int(&mut self) {
self.state.n_internals += 1;
self.sigma = f64::INFINITY;
Self::busy_sleep(&self.int_delay);
Self::sleep(&self.int_delay);
}

fn delta_ext(&mut self, _e: f64) {
self.state.n_externals += 1;
self.state.n_events += self.input.get_values().len();
// Safety: reading messages on atomic model's input port at delta_ext
self.state.n_events += unsafe { self.input.get_values() }.len();
self.sigma = 0.;
Self::busy_sleep(&self.ext_delay);
Self::sleep(&self.ext_delay);
}

#[inline]
Expand Down
2 changes: 1 addition & 1 deletion src/devstone/hi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ mod tests {
let probe = Arc::new(Mutex::new(TestProbe::default()));
let coupled = HI::create(width, depth, 0, 0, probe.clone());
let mut simulator = RootCoordinator::new(coupled);
simulator.simulate_time(f64::INFINITY);
simulator.simulate(f64::INFINITY);

let x = probe.lock().unwrap();
assert_eq!(expected_atomics(width, depth), x.n_atomics);
Expand Down
2 changes: 1 addition & 1 deletion src/devstone/ho.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ mod tests {
let probe = Arc::new(Mutex::new(TestProbe::default()));
let coupled = HO::create(width, depth, 0, 0, probe.clone());
let mut simulator = RootCoordinator::new(coupled);
simulator.simulate_time(f64::INFINITY);
simulator.simulate(f64::INFINITY);

let x = probe.lock().unwrap();
assert_eq!(expected_atomics(width, depth), x.n_atomics);
Expand Down
2 changes: 1 addition & 1 deletion src/devstone/homod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ mod tests {
let probe = Arc::new(Mutex::new(TestProbe::default()));
let coupled = HOmod::create(width, depth, 0, 0, probe.clone());
let mut simulator = RootCoordinator::new(coupled);
simulator.simulate_time(f64::INFINITY);
simulator.simulate(f64::INFINITY);

let x = probe.lock().unwrap();
assert_eq!(expected_atomics(width, depth), x.n_atomics);
Expand Down
2 changes: 1 addition & 1 deletion src/devstone/li.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ mod tests {
let probe = Arc::new(Mutex::new(TestProbe::default()));
let coupled = LI::create(width, depth, 0, 0, probe.clone());
let mut simulator = RootCoordinator::new(coupled);
simulator.simulate_time(f64::INFINITY);
simulator.simulate(f64::INFINITY);

let x = probe.lock().unwrap();
assert_eq!(expected_atomics(width, depth), x.n_atomics);
Expand Down
3 changes: 2 additions & 1 deletion src/devstone/seeder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ impl Atomic for DEVStoneSeeder {

#[inline]
fn lambda(&self) {
self.output.add_value(0);
// Safety: adding message on atomic model's output port at lambda
unsafe { self.output.add_value(0) };
}

#[inline]
Expand Down
12 changes: 2 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,10 @@ pub mod devstone;
pub mod modeling;
pub mod simulation;

#[cfg(not(feature = "par_any"))]
use std::rc::Rc;
#[cfg(feature = "par_any")]
use std::sync::Arc;

#[cfg(not(feature = "par_any"))]
type Shared<T> = Rc<T>;
#[cfg(feature = "par_any")]
type Shared<T> = Arc<T>;

/// Helper trait for avoiding verbose trait constraints.
#[cfg(not(feature = "par_any"))]
pub trait DynRef: 'static {}
/// Helper trait for avoiding verbose trait constraints.
#[cfg(feature = "par_any")]
pub trait DynRef: 'static + Sync + Send {}

Expand Down
13 changes: 5 additions & 8 deletions src/modeling/atomic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,22 @@ pub trait Atomic {
fn get_component_mut(&mut self) -> &mut Component;

/// Method for performing any operation before simulating. By default, it does nothing.
#[inline]
fn start(&mut self) {}

/// Method for performing any operation after simulating. By default, it does nothing.
#[inline]
fn stop(&mut self) {}

/// Output function of the atomic DEVS model.
///
/// # Safety
///
/// This is the only method where implementers can safely manipulate their [`super::OutPort`] structs.
/// Output function of the atomic DEVS model. This is the only method where
/// implementers can safely manipulate their [`super::OutPort`] structs.
fn lambda(&self);

/// Internal transition function of the atomic DEVS model.
fn delta_int(&mut self);

/// External transition function of the atomic DEVS model.
/// `e` corresponds to the elapsed time since the last state transition of the model.
///
/// # Safety
///
/// This is the only method where implementers can safely manipulate their [`super::InPort`] structs.
fn delta_ext(&mut self, e: f64);

Expand All @@ -38,6 +34,7 @@ pub trait Atomic {
/// Confluent transition function of the atomic DEVS model.
/// By default, it first triggers [`Atomic::delta_int`].
/// Then, it triggers [`Atomic::delta_ext`] with the elapsed time set to 0.
#[inline]
fn delta_conf(&mut self) {
self.delta_int();
self.delta_ext(0.);
Expand Down
Loading

0 comments on commit e0b9fca

Please sign in to comment.