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()); } }