#![warn(missing_docs)] use std::collections::HashMap; use std::fmt::Debug; use crate::{ dottable::Dottable, error::{OpmResult, OpossumError}, lightdata::LightData, optic_ports::OpticPorts, optical::{LightResult, Optical}, properties::{Properties, Property, Proptype}, }; /// This node represents a source of light. /// /// Hence it has only one output port (out1) and no input ports. Source nodes usually are the first nodes of an [`OpticScenery`](crate::OpticScenery). /// /// ## Optical Ports /// - Inputs /// - none /// - Outputs /// - `out1` /// /// ## Properties /// - `name` /// - `light data` /// /// **Note**: This node does not have the `inverted` property since it has only one output port. pub struct Source { props: Properties, } fn create_default_props() -> Properties { let mut props = Properties::default(); props.set("name", "source".into()); props.set( "light data", Property { prop: Proptype::LightData(None), }, ); props } impl Default for Source { fn default() -> Self { Self { props: create_default_props(), } } } impl Source { /// Creates a new [`Source`]. /// /// The light to be emitted from this source is defined in a [`LightData`] structure. /// /// ## Example /// /// ```rust /// use opossum::{ /// lightdata::{DataEnergy, LightData}, /// nodes::Source, /// spectrum::create_he_ne_spectrum}; /// /// let source=Source::new("My Source", LightData::Energy(DataEnergy {spectrum: create_he_ne_spectrum(1.0)})); /// ``` pub fn new(name: &str, light: LightData) -> Self { let mut props = create_default_props(); props.set("name", name.into()); props.set( "light data", Property { prop: Proptype::LightData(Some(light.clone())), }, ); Source { props } } /// Sets the light data of this [`Source`]. The [`LightData`] provided here represents the input data of an `OpticScenery`. pub fn set_light_data(&mut self, light_data: LightData) { self.props.set( "light data", Property { prop: Proptype::LightData(Some(light_data.clone())), }, ); } } impl Debug for Source { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let light_prop = self.props.get("light data").unwrap(); let data = if let Proptype::LightData(data) = &light_prop.prop { data } else { &None }; match data { Some(data) => write!(f, "{}", data), None => write!(f, "no data"), } } } impl Optical for Source { fn node_type(&self) -> &str { "light source" } fn name(&self) -> &str { if let Proptype::String(name) = &self.props.get("name").unwrap().prop { name } else { "light source" } } fn ports(&self) -> OpticPorts { let mut ports = OpticPorts::new(); ports.add_output("out1").unwrap(); ports } fn analyze( &mut self, _incoming_edges: LightResult, _analyzer_type: &crate::analyzer::AnalyzerType, ) -> OpmResult<LightResult> { let light_prop = self.props.get("light data").unwrap(); let data = if let Proptype::LightData(data) = &light_prop.prop { data } else { &None }; if data.is_some() { Ok(HashMap::from([("out1".into(), data.to_owned())])) } else { Err(OpossumError::Analysis("no input data available".into())) } } fn properties(&self) -> &Properties { &self.props } fn set_property(&mut self, name: &str, prop: Property) -> OpmResult<()> { if self.props.set(name, prop).is_none() { Err(OpossumError::Other("property not defined".into())) } else { Ok(()) } } } impl Dottable for Source { fn node_color(&self) -> &str { "slateblue" } } #[cfg(test)] mod test { use super::*; #[test] fn default() { let node = Source::default(); assert_eq!(node.name(), "source"); assert_eq!(node.node_type(), "light source"); assert_eq!(node.is_detector(), false); assert_eq!(node.inverted(), false); assert_eq!(node.node_color(), "slateblue"); assert!(node.as_group().is_err()); } #[test] fn new() { let source = Source::new("test", LightData::Fourier); assert_eq!(source.name(), "test"); } #[test] fn not_invertable() { let mut node = Source::default(); assert!(node.set_property("inverted", true.into()).is_err()); } #[test] fn ports() { let detector = Source::default(); assert!(detector.ports().inputs().is_empty()); assert_eq!(detector.ports().outputs(), vec!["out1"]); } }