xdevs_utils 0.1.0

Utility extensions for the xDEVS simulator.
Documentation
use serde::{Deserialize, Serialize};
use std::{
    collections::{HashMap, HashSet},
    ops::{Deref, DerefMut},
};

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Node(pub Option<String>, pub String);

impl Node {
    pub fn component(&self) -> Option<&String> {
        self.0.as_ref()
    }

    pub fn port(&self) -> &String {
        &self.1
    }
}

#[derive(Debug, Default)]
pub struct CouplingMap(HashMap<Node, HashSet<Node>>);

impl CouplingMap {
    pub fn new() -> Self {
        Self(HashMap::new())
    }

    pub fn insert(&mut self, from: Node, to: Node) {
        self.0.entry(from).or_default().insert(to);
    }

    pub fn get(&self, from: &Node) -> Option<&HashSet<Node>> {
        self.0.get(from)
    }

    pub fn iter(&self) -> impl Iterator<Item = (&Node, &HashSet<Node>)> {
        self.0.iter()
    }

    pub fn reverse(&self) -> CouplingMap {
        let mut map = CouplingMap::new();
        for (from, tos) in &self.0 {
            for to in tos {
                map.insert(to.clone(), from.clone());
            }
        }
        map
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CouplingType {
    EIC,
    IC,
    EOC,
    Invalid,
}

#[derive(Debug)]
pub enum CouplingError {
    NoComponents,
    InvalidPort,
    InvalidComponent,
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize)]
pub struct Coupling {
    component_from: Option<String>,
    port_from: String,
    component_to: Option<String>,
    port_to: String,
}

impl Coupling {
    fn get_type(&self) -> CouplingType {
        if self.component_from.is_none() && self.component_to.is_some() {
            CouplingType::EIC
        } else if self.component_from.is_some() && self.component_to.is_some() {
            CouplingType::IC
        } else if self.component_from.is_some() && self.component_to.is_none() {
            CouplingType::EOC
        } else {
            CouplingType::Invalid
        }
    }

    fn nodes(&self) -> (Node, Node) {
        (
            Node(self.component_from.clone(), self.port_from.clone()),
            Node(self.component_to.clone(), self.port_to.clone()),
        )
    }
}

#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
pub struct Component {
    #[serde(default)]
    input: HashSet<String>,
    #[serde(default)]
    output: HashSet<String>,
    #[serde(default)]
    components: HashMap<String, Component>,
    #[serde(default)]
    couplings: HashSet<Coupling>,
}

impl Component {
    pub fn new() -> Self {
        Self {
            input: HashSet::new(),
            output: HashSet::new(),
            components: HashMap::new(),
            couplings: HashSet::new(),
        }
    }

    pub fn add_input<S: Into<String>>(&mut self, input: S) -> bool {
        self.input.insert(input.into())
    }

    pub fn has_input(&self, input: &str) -> bool {
        self.input.contains(input)
    }

    pub fn add_output<S: Into<String>>(&mut self, output: S) -> bool {
        self.output.insert(output.into())
    }

    pub fn has_output(&self, output: &str) -> bool {
        self.output.contains(output)
    }

    pub fn add_component<S: Into<String>>(
        &mut self,
        name: S,
        component: Component,
    ) -> Option<Component> {
        self.components.insert(name.into(), component)
    }

    pub fn components(&self) -> &HashMap<String, Component> {
        &self.components
    }

    pub fn get_component(&self, name: &str) -> Option<&Component> {
        self.components.get(name)
    }

    pub fn add_coupling(&mut self, coupling: Coupling) -> Result<bool, CouplingError> {
        if !self.is_coupled() {
            return Err(CouplingError::NoComponents);
        }
        match coupling.get_type() {
            CouplingType::EIC => {
                if !self.has_input(&coupling.port_from) {
                    return Err(CouplingError::InvalidPort);
                }
                match self.get_component(coupling.component_to.as_ref().unwrap()) {
                    Some(component) => {
                        if component.has_input(&coupling.port_to) {
                            Ok(self.couplings.insert(coupling))
                        } else {
                            Err(CouplingError::InvalidPort)
                        }
                    }
                    None => Err(CouplingError::InvalidComponent),
                }
            }
            CouplingType::IC => {
                match (
                    self.get_component(coupling.component_from.as_ref().unwrap()),
                    self.get_component(coupling.component_to.as_ref().unwrap()),
                ) {
                    (Some(src), Some(dst)) => {
                        if src.has_output(&coupling.port_from) && dst.has_input(&coupling.port_to) {
                            Ok(self.couplings.insert(coupling))
                        } else {
                            Err(CouplingError::InvalidPort)
                        }
                    }
                    _ => Err(CouplingError::InvalidComponent),
                }
            }
            CouplingType::EOC => {
                match self.get_component(coupling.component_from.as_ref().unwrap()) {
                    Some(component) => {
                        if component.has_output(&coupling.port_from) {
                            Ok(self.couplings.insert(coupling))
                        } else {
                            Err(CouplingError::InvalidPort)
                        }
                    }
                    None => Err(CouplingError::InvalidComponent),
                }
            }
            CouplingType::Invalid => Err(CouplingError::InvalidComponent),
        }
    }

    pub fn get_couplings(&self) -> &HashSet<Coupling> {
        &self.couplings
    }

    pub fn coupling_map(&self) -> CouplingMap {
        let mut map = CouplingMap::new();
        for coupling in &self.couplings {
            let (from, to) = coupling.nodes();
            map.insert(from, to);
        }
        map
    }

    pub fn is_coupled(&self) -> bool {
        !self.components.is_empty()
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DevsModelTree {
    name: String,
    model: Component,
}

impl Deref for DevsModelTree {
    type Target = Component;

    fn deref(&self) -> &Self::Target {
        &self.model
    }
}

impl DerefMut for DevsModelTree {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.model
    }
}

impl DevsModelTree {
    pub fn new<S: Into<String>>(name: S) -> Self {
        Self {
            name: name.into(),
            model: Component::new(),
        }
    }
    pub fn from_json(json_str: &str) -> Result<Self, serde_json::Error> {
        serde_json::from_str(json_str)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::{fs, path::Path};

    #[test]
    fn efp() {
        let mut p = Component::new();
        p.add_input("input_req");
        p.add_output("output_res");

        let mut ef = Component::new();
        ef.add_input("input_res");
        ef.add_output("output_req");

        let mut efp = DevsModelTree::new("efp");
        assert_eq!(efp.add_component("p", p), None);
        assert_eq!(efp.add_component("ef", ef), None);
    }

    #[test]
    fn json() {
        let path = Path::new("examples/efp_deep.json");
        let json_str = fs::read_to_string(path).expect("Failed to read JSON file");

        match DevsModelTree::from_json(&json_str) {
            Ok(devs_model_tree) => {
                println!("{:#?}", devs_model_tree);
            }
            Err(e) => {
                eprintln!("Failed to parse JSON: {:?}", e);
            }
        }
    }
}