asynchronix 0.2.4

[Asynchronix is now NeXosim] A high performance asychronous compute framework for system simulation.
Documentation
//! Example: power supply with parallel resistive loads.
//!
//! This example demonstrates in particular:
//!
//! * the use of requestor and replier ports,
//! * simulation monitoring with event slots.
//!
//! ```text
//!                                                     ┌────────┐
//!                                                     │        │
//!                                                ┌───▶│  Load  ├───▶ Power
//!                                                │    │        │
//!                                                │    └────────┘
//!//!                                                │    ┌────────┐
//!                                                │    │        │
//!                                                ├───▶│  Load  ├───▶ Power
//!                                                │    │        │
//!                                                │    └────────┘
//!//!                                                │    ┌────────┐
//!                       ┌──────────┐   voltage▶  │    │        │
//! Voltage setting ●────▶│          │◀────────────┴───▶│  Load  ├───▶ Power
//!                       │  Power   │  ◀current        │        │
//!                       │  supply  │                  └────────┘
//!                       │          ├───────────────────────────────▶ Total power
//!                       └──────────┘    
//! ```
use asynchronix::model::{Model, Output, Requestor};
use asynchronix::simulation::{Mailbox, SimInit};
use asynchronix::time::MonotonicTime;

/// Power supply.
pub struct PowerSupply {
    /// Electrical output [V → A] -- requestor port.
    pub pwr_out: Requestor<f64, f64>,
    /// Power consumption [W] -- output port.
    pub power: Output<f64>,
}

impl PowerSupply {
    /// Creates a power supply.
    fn new() -> Self {
        Self {
            pwr_out: Default::default(),
            power: Default::default(),
        }
    }

    /// Voltage setting [V] -- input port.
    pub async fn voltage_setting(&mut self, voltage: f64) {
        // Ignore negative values.
        if voltage < 0.0 {
            return;
        }

        // Sum all load currents.
        let mut total_current = 0.0;
        for current in self.pwr_out.send(voltage).await {
            total_current += current;
        }

        self.power.send(voltage * total_current).await;
    }
}

impl Model for PowerSupply {}

/// Power supply.
pub struct Load {
    /// Power consumption [W] -- output port.
    pub power: Output<f64>,

    /// Load conductance [S] -- internal state.
    conductance: f64,
}

impl Load {
    /// Creates a load with the specified resistance [Ω].
    fn new(resistance: f64) -> Self {
        assert!(resistance > 0.0);
        Self {
            power: Default::default(),
            conductance: 1.0 / resistance,
        }
    }

    /// Electrical input [V → A] -- replier port.
    ///
    /// This port receives the applied voltage and returns the load current.
    pub async fn pwr_in(&mut self, voltage: f64) -> f64 {
        let current = voltage * self.conductance;
        self.power.send(voltage * current).await;

        current
    }
}

impl Model for Load {}

fn main() {
    // ---------------
    // Bench assembly.
    // ---------------

    // Models.
    let r1 = 5.0;
    let r2 = 10.0;
    let r3 = 20.0;
    let mut psu = PowerSupply::new();
    let mut load1 = Load::new(r1);
    let mut load2 = Load::new(r2);
    let mut load3 = Load::new(r3);

    // Mailboxes.
    let psu_mbox = Mailbox::new();
    let load1_mbox = Mailbox::new();
    let load2_mbox = Mailbox::new();
    let load3_mbox = Mailbox::new();

    // Connections.
    psu.pwr_out.connect(Load::pwr_in, &load1_mbox);
    psu.pwr_out.connect(Load::pwr_in, &load2_mbox);
    psu.pwr_out.connect(Load::pwr_in, &load3_mbox);

    // Model handles for simulation.
    let mut psu_power = psu.power.connect_slot().0;
    let mut load1_power = load1.power.connect_slot().0;
    let mut load2_power = load2.power.connect_slot().0;
    let mut load3_power = load3.power.connect_slot().0;
    let psu_addr = psu_mbox.address();

    // Start time (arbitrary since models do not depend on absolute time).
    let t0 = MonotonicTime::EPOCH;

    // Assembly and initialization.
    let mut simu = SimInit::new()
        .add_model(psu, psu_mbox)
        .add_model(load1, load1_mbox)
        .add_model(load2, load2_mbox)
        .add_model(load3, load3_mbox)
        .init(t0);

    // ----------
    // Simulation.
    // ----------

    // Compare two electrical powers for equality [W].
    fn same_power(a: f64, b: f64) -> bool {
        // Use an absolute floating-point epsilon of 1 pW.
        (a - b).abs() < 1e-12
    }

    // Vary the supply voltage, check the load and power supply consumptions.
    for voltage in [10.0, 15.0, 20.0] {
        simu.send_event(PowerSupply::voltage_setting, voltage, &psu_addr);

        let v_square = voltage * voltage;
        assert!(same_power(load1_power.take().unwrap(), v_square / r1));
        assert!(same_power(load2_power.take().unwrap(), v_square / r2));
        assert!(same_power(load3_power.take().unwrap(), v_square / r3));
        assert!(same_power(
            psu_power.take().unwrap(),
            v_square * (1.0 / r1 + 1.0 / r2 + 1.0 / r3)
        ));
    }
}