Skip to content
Snippets Groups Projects
reference.rs 9.30 KiB
use std::cell::RefCell;
use std::rc::{Rc, Weak};

use uuid::Uuid;

use crate::analyzer::AnalyzerType;
use crate::dottable::Dottable;
use crate::error::{OpmResult, OpossumError};
use crate::optic_ports::OpticPorts;
use crate::optic_ref::OpticRef;
use crate::optical::{LightResult, Optical};
use crate::properties::{PropCondition, Properties, Proptype};

#[derive(Debug)]
/// A virtual component referring to another existing component.
///
/// This node type is necessary in order to model resonators (loops) or double-pass systems.
///
/// ## Optical Ports
///   - Inputs
///     - input ports of the referenced [`Optical`]
///   - Outputs
///     - output ports of the referenced [`Optical`]
///
/// ## Rpeoperties
///   - `name`
///   - `inverted`
pub struct NodeReference {
    reference: Option<Weak<RefCell<dyn Optical>>>,
    props: Properties,
}
fn create_default_props() -> Properties {
    let mut props = Properties::default();
    props
        .create(
            "name",
            "name of the reference node",
            Some(vec![PropCondition::NonEmptyString]),
            "reference".into(),
        )
        .unwrap();
    props
        .create("inverted", "inverse propagation?", None, false.into())
        .unwrap();
    props
        .create(
            "reference id",
            "unique id of the referenced node",
            None,
            Uuid::nil().into(),
        )
        .unwrap();
    props
}
impl Default for NodeReference {
    fn default() -> Self {
        Self {
            reference: Default::default(),
            props: create_default_props(),
        }
    }
}
impl NodeReference {
    /// Create a new [`NodeReference`] referring to another optical node.
    pub fn from_node(node: OpticRef) -> Self {
        let mut props = create_default_props();
        props.set("reference id", node.uuid().into()).unwrap();
        let ref_name = format!("ref ({})", node.optical_ref.borrow().name());
        props.set("name", Proptype::String(ref_name)).unwrap();
        Self {
            reference: Some(Rc::downgrade(&node.optical_ref)),
            props,
        }
    }
    pub fn assign_reference(&mut self, node: OpticRef) {
        self.reference = Some(Rc::downgrade(&node.optical_ref));
    }
}

impl Optical for NodeReference {
    fn name(&self) -> &str {
        if let Proptype::String(name) = &self.props.get("name").unwrap() {
            name
        } else {
            self.node_type()
        }
    }
    fn node_type(&self) -> &str {
        "reference"
    }
    fn inverted(&self) -> bool {
        self.properties().get_bool("inverted").unwrap().unwrap()
    }
    fn ports(&self) -> OpticPorts {
        if let Some(rf) = &self.reference {
            let mut ports = rf.upgrade().unwrap().borrow().ports().clone();
            if self.inverted() {
                ports.set_inverted(true);
            }
            ports
        } else {
            OpticPorts::default()
        }
    }
    fn analyze(
        &mut self,
        incoming_data: LightResult,
        analyzer_type: &AnalyzerType,
    ) -> OpmResult<LightResult> {
        let rf = &self
            .reference
            .clone()
            .ok_or(OpossumError::Analysis("no reference defined".into()))?;
        let ref_node = rf.upgrade().unwrap();
        let mut ref_node = ref_node.borrow_mut();
        if self.inverted() {
            ref_node
                .set_property("inverted", true.into())
                .map_err(|_e| {
                    OpossumError::Analysis(format!(
                        "referenced node {} <{}> cannot be inverted",
                        ref_node.name(),
                        ref_node.node_type()
                    ))
                })?;
        }
        let output = ref_node.analyze(incoming_data, analyzer_type);
        if self.inverted() {
            ref_node.set_property("inverted", false.into())?;
        }
        output
    }
    fn properties(&self) -> &Properties {
        &self.props
    }
    fn set_property(&mut self, name: &str, prop: Proptype) -> OpmResult<()> {
        self.props.set(name, prop)
    }
    fn as_refnode_mut(&mut self) -> OpmResult<&mut NodeReference> {
        Ok(self)
    }
}

impl Dottable for NodeReference {
    fn node_color(&self) -> &str {
        "lightsalmon3"
    }
}
#[cfg(test)]
mod test {
    use super::*;
    use crate::{
        lightdata::{DataEnergy, LightData},
        nodes::{Dummy, Source},
        spectrum::create_he_ne_spectrum,
        OpticScenery,
    };
    #[test]
    fn default() {
        let node = NodeReference::default();
        assert!(node.reference.is_none());
        assert_eq!(node.name(), "reference");
        assert_eq!(node.node_type(), "reference");
        assert_eq!(node.is_detector(), false);
        assert_eq!(node.inverted(), false);
        assert_eq!(node.node_color(), "lightsalmon3");
        assert!(node.as_group().is_err());
    }
    #[test]
    fn from_node() {
        let mut scenery = OpticScenery::default();
        let idx = scenery.add_node(Dummy::default());
        let node_ref = scenery.node(idx).unwrap();
        let node = NodeReference::from_node(node_ref);
        assert!(node.reference.is_some());
    }
    #[test]
    fn from_node_name() {
        let mut scenery = OpticScenery::default();
        let idx = scenery.add_node(Dummy::default());
        let node_ref = scenery.node(idx).unwrap();
        let node_name = format!("ref ({})", node_ref.optical_ref.borrow().name());
        let node = NodeReference::from_node(node_ref);

        assert_eq!(node.name(), node_name);
    }
    #[test]
    fn assign_reference() {
        let mut scenery = OpticScenery::default();
        let idx = scenery.add_node(Dummy::default());
        let node_ref = scenery.node(idx).unwrap();
        let mut node = NodeReference::default();
        assert!(node.reference.is_none());
        node.assign_reference(node_ref);
        assert!(node.reference.is_some());
    }
    #[test]
    fn inverted() {
        let mut node = NodeReference::default();
        node.set_property("inverted", true.into()).unwrap();
        assert_eq!(node.inverted(), true)
    }
    #[test]
    fn ports_empty() {
        let node = NodeReference::default();
        assert!(node.ports().inputs().is_empty());
        assert!(node.ports().outputs().is_empty());
    }
    #[test]
    fn ports_non_empty() {
        let mut scenery = OpticScenery::default();
        let idx = scenery.add_node(Dummy::default());
        let node = NodeReference::from_node(scenery.node(idx).unwrap());
        assert_eq!(node.ports().inputs(), vec!["front"]);
        assert_eq!(node.ports().outputs(), vec!["rear"]);
    }
    #[test]
    fn ports_inverted() {
        let mut scenery = OpticScenery::default();
        let idx = scenery.add_node(Dummy::default());
        let mut node = NodeReference::from_node(scenery.node(idx).unwrap());
        node.set_property("inverted", true.into()).unwrap();
        assert_eq!(node.ports().inputs(), vec!["rear"]);
        assert_eq!(node.ports().outputs(), vec!["front"]);
    }
    #[test]
    fn analyze() {
        let mut scenery = OpticScenery::default();
        let idx = scenery.add_node(Dummy::default());
        let mut node = NodeReference::from_node(scenery.node(idx).unwrap());

        let mut input = LightResult::default();
        let input_light = LightData::Energy(DataEnergy {
            spectrum: create_he_ne_spectrum(1.0),
        });
        input.insert("front".into(), Some(input_light.clone()));
        let output = node.analyze(input, &AnalyzerType::Energy);
        assert!(output.is_ok());
        let output = output.unwrap();
        assert!(output.contains_key("rear".into()));
        assert_eq!(output.len(), 1);
        let output = output.get("rear".into()).unwrap();
        assert!(output.is_some());
        let output = output.clone().unwrap();
        assert_eq!(output, input_light);
    }
    #[test]
    fn analyze_inverse() {
        let mut scenery = OpticScenery::default();
        let idx = scenery.add_node(Dummy::default());
        let mut node = NodeReference::from_node(scenery.node(idx).unwrap());
        node.set_property("inverted", true.into()).unwrap();
        let mut input = LightResult::default();
        let input_light = LightData::Energy(DataEnergy {
            spectrum: create_he_ne_spectrum(1.0),
        });
        input.insert("rear".into(), Some(input_light.clone()));

        let output = node.analyze(input, &AnalyzerType::Energy);
        assert!(output.is_ok());
        let output = output.unwrap();
        assert!(output.contains_key("front".into()));
        assert_eq!(output.len(), 1);
        let output = output.get("front".into()).unwrap();
        assert!(output.is_some());
        let output = output.clone().unwrap();
        assert_eq!(output, input_light);
    }
    #[test]
    fn analyze_non_invertible_ref() {
        let mut scenery = OpticScenery::default();
        let idx = scenery.add_node(Source::default());
        let mut node = NodeReference::from_node(scenery.node(idx).unwrap());
        node.set_property("inverted", true.into()).unwrap();
        let mut input = LightResult::default();
        let input_light = LightData::Energy(DataEnergy {
            spectrum: create_he_ne_spectrum(1.0),
        });
        input.insert("rear".into(), Some(input_light.clone()));
        let output = node.analyze(input, &AnalyzerType::Energy);
        assert!(output.is_err());
    }
}