odem-rs-core 0.1.0

Core components of the ODEM-rs simulation framework
//! This module is about preparing the simulation library for the execution of
//! a simulation model, defining traits and a default configuration for
//! specifying various data types and constants.
//!
//! Our library abstracts from concrete types and initial values for model time,
//! priority and shared data, which requires the user to specify these
//! properties before any simulation model may be executed. In order to make
//! this as painless as possible, we employ the builder-pattern to construct a
//! configuration that is automatically passed down to the various constructors
//! for the model elements.
//!
//! ## `Config` Trait
//!
//! The [`Config`] trait is used to configure the simulation model and includes
//! the following associated types:
//!
//! - `Time`: Type for the model time, required to be cloneable, partially
//!   ordered, and with a debug representation.
//! - `Rank`: Type used to prioritize pucks scheduled at the same model time,
//!   required to be cloneable and totally ordered.
//! - `Data`: User-defined globally shared data type, intended for statistical
//!   aggregators, shared random number generators, or any data accessible from
//!   anywhere in the simulation model.
//! - `Plan`: Type of the continuation calendar, implementing the `Scheduler`
//!   trait for this configuration.
//!
//! Additionally, the `Config` trait includes methods to retrieve default values
//! for simulation start time, default rank for agents, and a reference to
//! globally shared data.
//!
//! ## `Time` Trait
//!
//! The [`Time`] trait encapsulates traits needed for the model-time type in a
//! configuration, including being `Unpin`, `PartialOrd`, `Copy`, and `Debug`.
//! It provides a default implementation for displaying time in a human-readable
//! format.
//!
//! ## `Rank` Trait
//!
//! The [`Rank`] trait encapsulates traits needed for the rank-type in a
//! configuration, including being `Unpin`, `Ord`, `Copy`, and `Debug`.
//! A blanket implementation is provided for all types meeting those criteria.
//!
//! ## Default Configuration
//!
//! The [`DefaultConfig`] struct defines the simulation configuration used by
//! default. It uses `f64` for the model time with an initial value of `0.0`, an
//! empty tuple for rank, and no additional data.

use core::fmt;

use crate::calendar::{DefaultPlan, Scheduler};

#[doc(inline)]
pub use odem_rs_meta::Config;

/* ************************* Configuration Traits *************************** */

/// Trait used to configure the various data types and constants used in a
/// simulation model.
pub trait Config: 'static {
	/// The type used for the model time.
	type Time: Time;
	/// The type used to prioritize pucks that are scheduled at the same
	/// model time.
	type Rank: Rank;
	/// User-defined, globally shared data type.
	///
	/// It is intended to be used for injecting statistical aggregators and
	/// shared random number generators but can be used whenever you would
	/// like to access some data from anywhere in the simulation model.
	/// Only one copy of this data exists during a simulation-run.
	type Data: 'static;
	/// The type of the continuation calendar which has to implement the
	/// `Scheduler` trait for this configuration.
	/// 
	/// # Note
	/// 
	/// The `Scheduler`-trait is not yet part of the public API due to
	/// instability. The only valid choice at this point is [`DefaultPlan`].
	type Plan: Scheduler<Config = Self>;

	/// Returns the start or default time of the simulation.
	fn default_time(&self) -> Self::Time;

	/// Returns the default rank for agents in the simulation.
	fn default_rank(&self) -> Self::Rank;

	/// Returns a reference to the globally shared data during a simulation run.
	fn global_data(&self) -> &Self::Data;
}

/// Helper trait that encapsulates all the traits needed for the model-time
/// type of [configuration](Config).
pub trait Time: Unpin + PartialOrd + Copy + fmt::Debug + 'static {
	/// Formats the time in human-readable format.
	///
	/// Uses the debug implementation by default but can be overridden with
	/// a more suitable representation.
	fn format(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		fmt::Debug::fmt(&self, f)
	}

	/// Displays the time in human-readable format using the [`format`]-method.
	///
	/// This method cannot be overridden, since [`DisplayTime`] cannot be
	/// constructed outside of this crate. Override [`format`] instead.
	///
	/// [`format`]: Self::format
	fn display(self) -> DisplayTime<Self> {
		DisplayTime(self)
	}
}

/// Helper trait that encapsulates all the traits needed for the rank-type
/// of a [configuration](Config).
pub trait Rank: Default + Unpin + Ord + Copy + fmt::Debug + 'static {}

// blanket-implementation for all the right types
impl<R> Rank for R where R: Default + Unpin + Ord + Copy + fmt::Debug + 'static {}

/* **************************************************** Default Configuration */

/// Defines the simulation [configuration] that is used by default.
///
/// The configuration uses `f64` for the [Time] with an initial value of `0.0`,
/// the empty tuple for the [Rank] and no additional data.
///
/// [configuration]: Config
#[derive(Default, Copy, Clone)]
pub struct DefaultConfig;

impl Config for DefaultConfig {
	type Time = f64;
	type Rank = ();
	type Data = Self;
	type Plan = DefaultPlan<Self>;

	fn default_time(&self) -> Self::Time {
		0.0
	}

	fn default_rank(&self) -> Self::Rank {}

	fn global_data(&self) -> &Self::Data {
		self
	}
}

/* ****************************************************** Built-In Time Types */

/// Implements [`Display`] by referring to [`Time::format`].
///
/// [`Display`]: fmt::Display
pub struct DisplayTime<T>(T);

impl<T> DisplayTime<T> {
	/// Provides access to the wrapped [`Time`].
	pub fn get(&self) -> &T {
		&self.0
	}

	/// Extracts the inner [`Time`].
	pub fn into_inner(self) -> T {
		self.0
	}
}

impl<T: Time> fmt::Debug for DisplayTime<T> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		self.0.format(f)
	}
}

impl<T: Time> fmt::Display for DisplayTime<T> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		self.0.format(f)
	}
}

// Provide a generic configuration for a configuration triplet.
impl<T: Time, R: Rank, D: 'static> Config for (T, R, D) {
	type Time = T;
	type Rank = R;
	type Data = D;
	type Plan = DefaultPlan<Self>;

	fn default_time(&self) -> Self::Time {
		self.0
	}

	fn default_rank(&self) -> Self::Rank {
		self.1
	}

	fn global_data(&self) -> &Self::Data {
		&self.2
	}
}

macro_rules! impl_primitive_time {
	($($T:ty),* $(,)?) => {$(
		impl Time for $T {
			fn format(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
				use fmt::Display;
				if f.alternate() {
					use crate::erased::ClockTime;
					Display::fmt(&ClockTime::seconds(*self as isize), f)
				} else {
					Display::fmt(self, f)
				}
			}
		}
	)*};
}

impl_primitive_time!(
	i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64
);

impl Time for () {}

/* ****************************************************** Optional Time Types */

// support si time quantities as model time
#[cfg(feature = "uom")]
mod uom {
	use super::Time;
	use core::fmt;
	use uom::{
		Conversion,
		fmt::DisplayStyle,
		num_traits::{AsPrimitive, Num},
		si::{Units, time},
	};

	impl<U, V> Time for time::Time<U, V>
	where
		U: Units<V> + ?Sized + 'static,
		V: Conversion<V>
			+ Num
			+ PartialOrd
			+ PartialEq
			+ AsPrimitive<isize>
			+ fmt::Debug
			+ fmt::Display
			+ Unpin
			+ 'static,
		time::second: Conversion<V, T = V::T>,
	{
		fn format(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
			use fmt::Display;

			if f.alternate() {
				// switch to a wall-clock-format for the alternative format
				use crate::erased::ClockTime;
				ClockTime::seconds(self.get::<time::second>().as_()).fmt(f)
			} else {
				// use the abbreviated format by default
				self.into_format_args(time::second, DisplayStyle::Abbreviation)
					.fmt(f)
			}
		}
	}
}