1
0
forked from ROMEO/nexosim

Document observable states

This commit is contained in:
Jaŭhien Piatlicki 2024-11-08 14:04:34 +01:00
parent a533b3e6c1
commit 1a0dff0f6e
6 changed files with 333 additions and 3 deletions

View File

@ -135,6 +135,16 @@ jobs:
env: env:
MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-num-cpus=4 MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-num-cpus=4
- name: Run cargo miri example5 (single-threaded executor)
run: cargo miri run --example observables
env:
MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-num-cpus=1
- name: Run cargo miri example5 (multi-threaded executor)
run: cargo miri run --example observables
env:
MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-num-cpus=4
lints: lints:
name: Lints name: Lints
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -49,7 +49,8 @@ The [API] documentation is relatively exhaustive and includes a practical
overview which should provide all necessary information to get started. overview which should provide all necessary information to get started.
More fleshed out examples can also be found in the dedicated More fleshed out examples can also be found in the dedicated
[directory](asynchronix/examples). [simulator](asynchronix/examples) and [utilities](asynchronix-util/examples)
directories.
[API]: https://docs.rs/asynchronix [API]: https://docs.rs/asynchronix

View File

@ -1,3 +1,5 @@
# Utilities for model-building # Utilities for model-building
TODO: add documentation This crate contains utilities used for model and simulation bench development.

View File

@ -0,0 +1,310 @@
//! Example: processor with observable states.
//!
//! This example demonstrates in particular:
//!
//! * the use of observable states,
//! * state machine with delays.
//!
//! ```text
//! ┌───────────┐
//! Switch ON/OFF ●────►│ ├────► Mode
//! │ Processor │
//! Process data ●────►│ ├────► Value
//! │ │
//! │ ├────► House Keeping
//! └───────────┘
//! ```
use std::time::Duration;
use asynchronix::model::{Context, InitializedModel, Model};
use asynchronix::ports::{EventBuffer, Output};
use asynchronix::simulation::{AutoActionKey, Mailbox, SimInit, SimulationError};
use asynchronix::time::MonotonicTime;
use asynchronix_util::observables::{Observable, ObservableState, ObservableValue};
/// House keeping TM.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Hk {
pub voltage: f64,
pub current: f64,
}
impl Default for Hk {
fn default() -> Self {
Self {
voltage: 0.0,
current: 0.0,
}
}
}
/// Processor mode ID.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ModeId {
Off,
Idle,
Processing,
}
/// Processor state.
pub enum State {
Off,
Idle,
Processing(AutoActionKey),
}
impl Default for State {
fn default() -> Self {
State::Off
}
}
impl Observable<ModeId> for State {
fn observe(&self) -> ModeId {
match *self {
State::Off => ModeId::Off,
State::Idle => ModeId::Idle,
State::Processing(_) => ModeId::Processing,
}
}
}
/// Processor model.
pub struct Processor {
/// Mode output.
pub mode: Output<ModeId>,
/// Calculated value output.
pub value: Output<u16>,
/// HK output.
pub hk: Output<Hk>,
/// Internal state.
state: ObservableState<State, ModeId>,
/// Accumulator.
acc: ObservableValue<u16>,
/// Electrical data.
elc: ObservableValue<Hk>,
}
impl Processor {
/// Create a new processor.
pub fn new() -> Self {
let mode = Output::new();
let value = Output::new();
let hk = Output::new();
Self {
mode: mode.clone(),
value: value.clone(),
hk: hk.clone(),
state: ObservableState::new(mode),
acc: ObservableValue::new(value),
elc: ObservableValue::new(hk),
}
}
/// Switch processor ON/OFF.
pub async fn switch_power(&mut self, on: bool) {
if on {
self.state.set(State::Idle).await;
self.elc
.set(Hk {
voltage: 5.5,
current: 0.1,
})
.await;
self.acc.set(0).await;
} else {
self.state.set(State::Off).await;
self.elc.set(Hk::default()).await;
self.acc.set(0).await;
}
}
/// Process data for dt milliseconds.
pub async fn process(&mut self, dt: u64, context: &Context<Self>) {
if matches!(self.state.observe(), ModeId::Idle | ModeId::Processing) {
self.state
.set(State::Processing(
context
.scheduler
.schedule_keyed_event(
Duration::from_millis(dt),
Self::finish_processing,
(),
)
.unwrap()
.into_auto(),
))
.await;
self.elc.modify(|hk| hk.current = 1.0).await;
}
}
/// Finish processing.
async fn finish_processing(&mut self) {
self.state.set(State::Idle).await;
self.acc.modify(|a| *a += 1).await;
self.elc.modify(|hk| hk.current = 0.1).await;
}
}
impl Model for Processor {
/// Propagate all internal states.
async fn init(mut self, _: &Context<Self>) -> InitializedModel<Self> {
self.state.propagate().await;
self.acc.propagate().await;
self.elc.propagate().await;
self.into()
}
}
fn main() -> Result<(), SimulationError> {
// ---------------
// Bench assembly.
// ---------------
// Models.
let mut proc = Processor::new();
// Mailboxes.
let proc_mbox = Mailbox::new();
// Model handles for simulation.
let mut mode = EventBuffer::new();
let mut value = EventBuffer::new();
let mut hk = EventBuffer::new();
proc.mode.connect_sink(&mode);
proc.value.connect_sink(&value);
proc.hk.connect_sink(&hk);
let proc_addr = proc_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(proc, proc_mbox, "proc").init(t0)?;
// ----------
// Simulation.
// ----------
// Initial state.
expect(
&mut mode,
Some(ModeId::Off),
&mut value,
Some(0),
&mut hk,
0.0,
0.0,
);
// Switch processor on.
simu.process_event(Processor::switch_power, true, &proc_addr)?;
expect(
&mut mode,
Some(ModeId::Idle),
&mut value,
Some(0),
&mut hk,
5.5,
0.1,
);
// Trigger processing.
simu.process_event(Processor::process, 100, &proc_addr)?;
expect(
&mut mode,
Some(ModeId::Processing),
&mut value,
None,
&mut hk,
5.5,
1.0,
);
// All data processed.
simu.step_by(Duration::from_millis(101))?;
expect(
&mut mode,
Some(ModeId::Idle),
&mut value,
Some(1),
&mut hk,
5.5,
0.1,
);
// Trigger long processing.
simu.process_event(Processor::process, 100, &proc_addr)?;
expect(
&mut mode,
Some(ModeId::Processing),
&mut value,
None,
&mut hk,
5.5,
1.0,
);
// Trigger short processing, it cancels the previous one.
simu.process_event(Processor::process, 10, &proc_addr)?;
expect(
&mut mode,
Some(ModeId::Processing),
&mut value,
None,
&mut hk,
5.5,
1.0,
);
// Wait for short processing to finish, check results.
simu.step_by(Duration::from_millis(11))?;
expect(
&mut mode,
Some(ModeId::Idle),
&mut value,
Some(2),
&mut hk,
5.5,
0.1,
);
// Wait long enough, no state change as the long processing has been
// cancelled.
simu.step_by(Duration::from_millis(100))?;
assert_eq!(mode.next(), None);
assert_eq!(value.next(), None);
assert_eq!(hk.next(), None);
Ok(())
}
// Check observable state.
fn expect(
mode: &mut EventBuffer<ModeId>,
mode_ex: Option<ModeId>,
value: &mut EventBuffer<u16>,
value_ex: Option<u16>,
hk: &mut EventBuffer<Hk>,
voltage_ex: f64,
current_ex: f64,
) {
assert_eq!(mode.next(), mode_ex);
assert_eq!(value.next(), value_ex);
let hk_value = hk.next().unwrap();
assert!(same(hk_value.voltage, voltage_ex));
assert!(same(hk_value.current, current_ex));
}
// Compare two voltages or currents.
fn same(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-12
}

View File

@ -1 +1,2 @@
/// Observable states.
pub mod observables; pub mod observables;

View File

@ -1,3 +1,9 @@
//! Observable states.
//!
//! This module contains types used to implement states automatically propagated
//! to output on change.
//!
use std::ops::Deref; use std::ops::Deref;
use asynchronix::ports::Output; use asynchronix::ports::Output;