-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a coupled model for hierarchical model construction
- Loading branch information
Showing
4 changed files
with
499 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,290 @@ | ||
use std::f64::INFINITY; | ||
|
||
use serde::{Deserialize, Serialize}; | ||
|
||
use super::model_trait::{DevsModel, Reportable, ReportableModel, SerializableModel}; | ||
use super::{Model, ModelMessage, ModelRecord}; | ||
|
||
use crate::simulator::Services; | ||
use crate::utils::errors::SimulationError; | ||
|
||
use sim_derive::SerializableModel; | ||
|
||
#[derive(Clone, Deserialize, Serialize, SerializableModel)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct Coupled { | ||
ports_in: PortsIn, | ||
ports_out: PortsOut, | ||
components: Vec<Model>, | ||
external_input_couplings: Vec<ExternalInputCoupling>, | ||
external_output_couplings: Vec<ExternalOutputCoupling>, | ||
internal_couplings: Vec<InternalCoupling>, | ||
#[serde(default)] | ||
state: State, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
struct PortsIn { | ||
flow_paths: Vec<String>, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
struct PortsOut { | ||
flow_paths: Vec<String>, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct ExternalInputCoupling { | ||
#[serde(rename = "targetID")] | ||
pub target_id: String, | ||
pub source_port: String, | ||
pub target_port: String, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct ExternalOutputCoupling { | ||
#[serde(rename = "sourceID")] | ||
pub source_id: String, | ||
pub source_port: String, | ||
pub target_port: String, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct InternalCoupling { | ||
#[serde(rename = "sourceID")] | ||
pub source_id: String, | ||
#[serde(rename = "targetID")] | ||
pub target_id: String, | ||
pub source_port: String, | ||
pub target_port: String, | ||
} | ||
|
||
#[derive(Clone, Default, Debug, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
struct State { | ||
parked_messages: Vec<ParkedMessage>, | ||
records: Vec<ModelRecord>, | ||
} | ||
|
||
#[derive(Clone, Default, Debug, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
struct ParkedMessage { | ||
component_id: String, | ||
port: String, | ||
content: String, | ||
} | ||
|
||
impl Coupled { | ||
pub fn new( | ||
ports_in: Vec<String>, | ||
ports_out: Vec<String>, | ||
components: Vec<Model>, | ||
external_input_couplings: Vec<ExternalInputCoupling>, | ||
external_output_couplings: Vec<ExternalOutputCoupling>, | ||
internal_couplings: Vec<InternalCoupling>, | ||
) -> Self { | ||
Self { | ||
ports_in: PortsIn { | ||
flow_paths: ports_in, | ||
}, | ||
ports_out: PortsOut { | ||
flow_paths: ports_out, | ||
}, | ||
components, | ||
external_input_couplings, | ||
external_output_couplings, | ||
internal_couplings, | ||
state: State::default(), | ||
} | ||
} | ||
|
||
fn external_targets(&self, source_id: &str, source_port: &str) -> Vec<String> { | ||
// Vec<target_port> | ||
|
||
self.external_output_couplings | ||
.iter() | ||
.filter_map(|coupling| { | ||
if coupling.source_id == source_id && coupling.source_port == source_port { | ||
Some(coupling.target_port.to_string()) | ||
} else { | ||
None | ||
} | ||
}) | ||
.collect() | ||
} | ||
|
||
fn internal_targets(&self, source_id: &str, source_port: &str) -> Vec<(String, String)> { | ||
// Vec<(target_id, target_port)> | ||
|
||
self.internal_couplings | ||
.iter() | ||
.filter_map(|coupling| { | ||
if coupling.source_id == source_id && coupling.source_port == source_port { | ||
Some(( | ||
coupling.target_id.to_string(), | ||
coupling.target_port.to_string(), | ||
)) | ||
} else { | ||
None | ||
} | ||
}) | ||
.collect() | ||
} | ||
} | ||
|
||
impl DevsModel for Coupled { | ||
fn events_ext( | ||
&mut self, | ||
incoming_message: &ModelMessage, | ||
services: &mut Services, | ||
) -> Result<(), SimulationError> { | ||
let external_input_couplings = self.external_input_couplings.clone(); | ||
self.components | ||
.iter_mut() | ||
.filter_map(|component| { | ||
external_input_couplings | ||
.iter() | ||
.filter(|coupling| coupling.source_port == incoming_message.port_name) | ||
.map(|coupling| (&coupling.target_id, &coupling.target_port)) | ||
.find_map(|(target_id, target_port)| { | ||
if target_id == component.id() { | ||
Some(component.events_ext( | ||
&ModelMessage { | ||
port_name: target_port.to_string(), | ||
content: incoming_message.content.clone(), | ||
}, | ||
services, | ||
)) | ||
} else { | ||
None | ||
} | ||
}) | ||
}) | ||
.collect() | ||
} | ||
|
||
fn events_int( | ||
&mut self, | ||
services: &mut Services, | ||
) -> Result<Vec<ModelMessage>, SimulationError> { | ||
// Find the (internal message) events_ext relevant models (parked message id == component id) | ||
let ext_transitioning_component_triggers: Vec<(usize, String, String)> = (0..self | ||
.components | ||
.len()) | ||
.map(|component_index| -> Vec<(usize, String, String)> { | ||
self.state | ||
.parked_messages | ||
.iter() | ||
.filter_map(|parked_message| { | ||
if parked_message.component_id == self.components[component_index].id() { | ||
Some(( | ||
component_index, | ||
parked_message.port.to_string(), | ||
parked_message.content.to_string(), | ||
)) | ||
} else { | ||
None | ||
} | ||
}) | ||
.collect() | ||
}) | ||
.flatten() | ||
.collect(); | ||
ext_transitioning_component_triggers | ||
.iter() | ||
.map( | ||
|(component_index, message_port, message_content)| -> Result<(), SimulationError> { | ||
self.components[*component_index].events_ext( | ||
&ModelMessage { | ||
port_name: message_port.to_string(), | ||
content: message_content.to_string(), | ||
}, | ||
services, | ||
) | ||
}, | ||
) | ||
.collect::<Result<Vec<()>, SimulationError>>()?; | ||
self.state.parked_messages = Vec::new(); | ||
// Find the events_int relevant models (until_next_event == 0.0) | ||
// Run events_int for each model, and compile the internal and external messages | ||
// Store the internal messages in the Coupled model struct, and output the external messages | ||
let int_transitioning_component_indexes: Vec<usize> = (0..self.components.len()) | ||
.filter(|component_index| self.components[*component_index].until_next_event() == 0.0) | ||
.collect(); | ||
Ok(int_transitioning_component_indexes | ||
.iter() | ||
.map( | ||
|component_index| -> Result<Vec<ModelMessage>, SimulationError> { | ||
Ok(self.components[*component_index] | ||
.events_int(services)? | ||
.iter() | ||
.map(|outgoing_message| -> Vec<ModelMessage> { | ||
// For internal messages (those transmitted on internal couplings), store the messages | ||
// as Parked Messages, to be ingested by the target components on the next simulation step | ||
self.internal_targets( | ||
self.components[*component_index].id(), | ||
&outgoing_message.port_name, | ||
) | ||
.iter() | ||
.for_each(|(target_id, target_port)| { | ||
self.state.parked_messages.push(ParkedMessage { | ||
component_id: target_id.to_string(), | ||
port: target_port.to_string(), | ||
content: outgoing_message.content.clone(), | ||
}); | ||
}); | ||
// For external messages (those transmitted on external output couplings), prepare the | ||
// output as standard events_int output | ||
self.external_targets( | ||
self.components[*component_index].id(), | ||
&outgoing_message.port_name, | ||
) | ||
.iter() | ||
.map(|target_port| ModelMessage { | ||
port_name: target_port.to_string(), | ||
content: outgoing_message.content.clone(), | ||
}) | ||
.collect() | ||
}) | ||
.flatten() | ||
.collect()) | ||
}, | ||
) | ||
.flatten() | ||
.flatten() | ||
.collect()) | ||
} | ||
|
||
fn time_advance(&mut self, time_delta: f64) { | ||
self.components.iter_mut().for_each(|component| { | ||
component.time_advance(time_delta); | ||
}); | ||
} | ||
|
||
fn until_next_event(&self) -> f64 { | ||
self.components.iter().fold(INFINITY, |min, component| { | ||
f64::min(min, component.until_next_event()) | ||
}) | ||
} | ||
} | ||
|
||
impl Reportable for Coupled { | ||
fn status(&self) -> String { | ||
if self.state.parked_messages.is_empty() { | ||
format!["Processing {} messages", self.state.parked_messages.len()] | ||
} else { | ||
String::from("Processing no messages") | ||
} | ||
} | ||
|
||
fn records(&self) -> &Vec<ModelRecord> { | ||
&self.state.records | ||
} | ||
} | ||
|
||
impl ReportableModel for Coupled {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.