diff --git a/Cargo.lock b/Cargo.lock index d867090c792f1c1b070978ef18ff52d6b70e9612..ec7537a8e4b6694459f1962eaadc80e200b55c99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,9 +51,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -69,15 +69,15 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", diff --git a/examples/opticscenery.rs b/examples/opticscenery.rs index 9836238a6d3fd3c518655457ae970c930618be26..03c40a6e4f08e1c936fd9819a614fd5dbc52afa9 100644 --- a/examples/opticscenery.rs +++ b/examples/opticscenery.rs @@ -12,7 +12,7 @@ fn main() { println!("export to `dot` format: {}", scenery.to_dot()); let node1 = scenery.add_element("my optic", NodeDummy); let node2 = scenery.add_element("my other optic", NodeDummy); - if let Ok(_) = scenery.connect_nodes(node1, node2) { + if let Ok(_) = scenery.connect_nodes(node1, "rear", node2, "front") { let path = "graph.dot"; let mut output = File::create(path).unwrap(); write!(output, "{}", scenery.to_dot()).unwrap(); diff --git a/examples/pa_doublepass_graph.rs b/examples/pa_doublepass_graph.rs index fc975a11a7f260225e7750303226c6ec0cca001e..703e554c6fcfdf49625515dbd7dca4ccadd7ccd6 100644 --- a/examples/pa_doublepass_graph.rs +++ b/examples/pa_doublepass_graph.rs @@ -28,12 +28,12 @@ fn main() { node.set_inverted(true); let n1i = scenery.add_node(node); - scenery.connect_nodes(n1, n2).unwrap(); - scenery.connect_nodes(n2, n3).unwrap(); - scenery.connect_nodes(n3, n4).unwrap(); - scenery.connect_nodes(n4, n3i).unwrap(); - scenery.connect_nodes(n3i, n2i).unwrap(); - scenery.connect_nodes(n2i, n1i).unwrap(); + scenery.connect_nodes(n1, "rear", n2, "front").unwrap(); + scenery.connect_nodes(n2, "rear", n3, "front").unwrap(); + scenery.connect_nodes(n3, "rear", n4, "front").unwrap(); + scenery.connect_nodes(n4, "rear", n3i, "rear").unwrap(); + scenery.connect_nodes(n3i, "front", n2i, "rear").unwrap(); + scenery.connect_nodes(n2i, "front", n1i, "rear").unwrap(); let mut group = NodeGroup::new(); let g_n1 = group.add_node(OpticNode::new("Beamsplitter", NodeDummy)); diff --git a/examples/uopa_graph.rs b/examples/uopa_graph.rs index 0a2eb8cc74f404f34d4fe2f542ab562cbd0bfb57..6ffd97592dcfcfe75cc1eeb102d2ad9d43732303 100644 --- a/examples/uopa_graph.rs +++ b/examples/uopa_graph.rs @@ -1,5 +1,5 @@ +use opossum::nodes::{NodeBeamSplitter, NodeDummy}; use opossum::optic_scenery::OpticScenery; -use opossum::nodes::{NodeDummy, NodeBeamSplitter}; use std::fs::File; use std::io::Write; @@ -21,15 +21,30 @@ fn main() { let pump_shg_node = scenery.add_element("Pump SHG", NodeDummy); let pump_splitter_node = scenery.add_element("Pump Beam Splitter", NodeBeamSplitter); - scenery.connect_nodes(pulse_generation_split_node, uOPA_1_node); - scenery.connect_nodes(pulse_generation_split_node, pump_pre_amplifier_node); - scenery.connect_nodes(pump_pre_amplifier_node, pump_main_amplifier_node); - scenery.connect_nodes(pump_main_amplifier_node, pump_compressor_node); - scenery.connect_nodes(pump_compressor_node, pump_shg_node); - scenery.connect_nodes(pump_shg_node, pump_splitter_node); - scenery.connect_nodes(pump_splitter_node, uOPA_1_node); - scenery.connect_nodes(uOPA_1_node, uOPA_2_node); - scenery.connect_nodes(pump_splitter_node, uOPA_2_node); + scenery.connect_nodes(pulse_generation_split_node, "rear", uOPA_1_node, "front"); + scenery.connect_nodes( + pulse_generation_split_node, + "rear", + pump_pre_amplifier_node, + "front", + ); + scenery.connect_nodes( + pump_pre_amplifier_node, + "rear", + pump_main_amplifier_node, + "front", + ); + scenery.connect_nodes( + pump_main_amplifier_node, + "rear", + pump_compressor_node, + "front", + ); + scenery.connect_nodes(pump_compressor_node, "rear", pump_shg_node, "front"); + scenery.connect_nodes(pump_shg_node, "rear", pump_splitter_node, "front"); + scenery.connect_nodes(pump_splitter_node, "transmitted", uOPA_1_node, "front").unwrap(); + scenery.connect_nodes(uOPA_1_node, "rear", uOPA_2_node, "front"); + scenery.connect_nodes(pump_splitter_node, "reflected", uOPA_2_node, "front").unwrap(); let mut scenery_2 = OpticScenery::new(); scenery_2.set_description("PHELIX uOPA Pump Pre-Amplifier".into()); @@ -54,25 +69,25 @@ fn main() { let monitor2_node = scenery_2.add_element("Monitor", NodeDummy); let monitor3_node = scenery_2.add_element("Monitor", NodeDummy); - scenery_2.connect_nodes(spm_node, circ1_node); - scenery_2.connect_nodes(circ1_node, circ2_node); - scenery_2.connect_nodes(circ2_node, cfbg_node); - scenery_2.connect_nodes(cfbg_node, circ3_node); - scenery_2.connect_nodes(cfbg_node, monitor1_node); - scenery_2.connect_nodes(circ3_node, isolator1_node); - scenery_2.connect_nodes(isolator1_node, tap1_node); - scenery_2.connect_nodes(tap1_node, monitor2_node); - scenery_2.connect_nodes(tap1_node, wdm_node); - scenery_2.connect_nodes(diode1_node, wdm_node); - scenery_2.connect_nodes(wdm_node, yb_fiber1_node); - scenery_2.connect_nodes(yb_fiber1_node, tap2_node); - scenery_2.connect_nodes(tap2_node, monitor3_node); - scenery_2.connect_nodes(tap2_node, aom_node); - scenery_2.connect_nodes(aom_node, isolator2_node); - scenery_2.connect_nodes(isolator2_node, yb_fiber2_node_node); - scenery_2.connect_nodes(yb_fiber2_node_node, dichroic_node); - scenery_2.connect_nodes(dichroic_node, dichroic_node); - scenery_2.connect_nodes(diode2_node, dichroic_node); + scenery_2.connect_nodes(spm_node, "rear", circ1_node, "front"); + scenery_2.connect_nodes(circ1_node, "rear", circ2_node, "front"); + scenery_2.connect_nodes(circ2_node, "rear", cfbg_node, "front"); + scenery_2.connect_nodes(cfbg_node, "rear", circ3_node, "front"); + scenery_2.connect_nodes(cfbg_node, "rear", monitor1_node, "front"); + scenery_2.connect_nodes(circ3_node, "rear", isolator1_node, "front"); + scenery_2.connect_nodes(isolator1_node, "rear", tap1_node, "front"); + scenery_2.connect_nodes(tap1_node, "rear", monitor2_node, "front"); + scenery_2.connect_nodes(tap1_node, "rear", wdm_node, "front"); + scenery_2.connect_nodes(diode1_node, "rear", wdm_node, "front"); + scenery_2.connect_nodes(wdm_node, "rear", yb_fiber1_node, "front"); + scenery_2.connect_nodes(yb_fiber1_node, "rear", tap2_node, "front"); + scenery_2.connect_nodes(tap2_node, "rear", monitor3_node, "front"); + scenery_2.connect_nodes(tap2_node, "rear", aom_node, "front"); + scenery_2.connect_nodes(aom_node, "rear", isolator2_node, "front"); + scenery_2.connect_nodes(isolator2_node, "rear", yb_fiber2_node_node, "front"); + scenery_2.connect_nodes(yb_fiber2_node_node, "rear", dichroic_node, "front"); + scenery_2.connect_nodes(dichroic_node, "rear", dichroic_node, "front"); + scenery_2.connect_nodes(diode2_node, "rear", dichroic_node, "front"); let mut scenery_3 = OpticScenery::new(); scenery_3.set_description("PHELIX uOPA Pump Regenerative Main-Amplifier".into()); diff --git a/src/error.rs b/src/error.rs index 01e7892a3b579a78449b237bc1feec208aec2046..82d7fe857ebb0081e135a9e593687a3ccb4c766d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,33 @@ +use std::{error::Error, fmt::Display}; + #[derive(Debug, Clone)] pub enum OpossumError { - OpticScenery(String), - OpticGroup(String), - OpticPort(String), - Other(String) + OpticScenery(String), + OpticGroup(String), + OpticPort(String), + Other(String), +} + +impl Display for OpossumError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + OpossumError::OpticScenery(m) => { + write!(f, "Opossum Error::OpticScenery::{}", m) + } + OpossumError::OpticGroup(m) => { + write!(f, "Opossum Error::OpticGroup::{}", m) + } + OpossumError::OpticPort(m) => { + write!(f, "Opossum Error::OpticPort::{}", m) + } + OpossumError::Other(m) => write!(f, "Opossum Error::Other::{}", m), + } + } +} +impl Error for OpossumError {} + +impl std::convert::From<String> for OpossumError { + fn from(msg: String) -> Self { + Self::Other(msg) + } } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index eb6cc7d179992efdacd72efdebcdd7773240ec9b..66cf11c6e602e1caf11af44d98a7db253355c72f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ pub mod optic_scenery; /// The basic structure representing an optical element pub mod optic_node; +pub mod light; + pub mod optic_ports; pub mod nodes; diff --git a/src/light.rs b/src/light.rs new file mode 100644 index 0000000000000000000000000000000000000000..2a8f4c3bbfcfbc9ab5bdb2f170cdd09a27c7ad6b --- /dev/null +++ b/src/light.rs @@ -0,0 +1,59 @@ +#[derive(Debug)] +pub struct Light { + src_port: String, + target_port: String, +} + +impl Light { + pub fn new(src_port: &str, target_port: &str) -> Self { + Self { + src_port: src_port.into(), + target_port: target_port.into(), + } + } + pub fn src_port(&self) -> &str { + self.src_port.as_ref() + } + pub fn target_port(&self) -> &str { + self.target_port.as_ref() + } + pub fn set_src_port(&mut self, src_port: String) { + self.src_port = src_port; + } + pub fn set_target_port(&mut self, target_port: String) { + self.target_port = target_port; + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn new() { + let light = Light::new("test1", "test2"); + assert_eq!(light.src_port, "test1"); + assert_eq!(light.target_port, "test2"); + } + #[test] + fn src_port() { + let light = Light::new("test1", "test2"); + assert_eq!(light.src_port(), "test1"); + } + #[test] + fn target_port() { + let light = Light::new("test1", "test2"); + assert_eq!(light.target_port(), "test2"); + } + #[test] + fn set_src_port() { + let mut light = Light::new("test1", "test2"); + light.set_src_port("test3".into()); + assert_eq!(light.src_port, "test3"); + } + #[test] + fn set_target_port() { + let mut light = Light::new("test1", "test2"); + light.set_target_port("test3".into()); + assert_eq!(light.target_port, "test3"); + } +} diff --git a/src/nodes/node_reference.rs b/src/nodes/node_reference.rs index 81e4016676e3f9d01dd92f6031e02b79489cc39d..330ddaa550e558b824e9da027bb9dc7ededb4799 100644 --- a/src/nodes/node_reference.rs +++ b/src/nodes/node_reference.rs @@ -1,17 +1,19 @@ +use std::rc::{Weak, Rc}; + use crate::optic_node::{OpticNode, Optical}; /// A virtual component referring to another existing component. This node type is necessary in order to model resonators (loops) or double-pass systems. -pub struct NodeReference<'a> { - reference: &'a OpticNode, +pub struct NodeReference { + reference: Rc<OpticNode>, } -impl<'a> NodeReference<'a> { - pub fn new(node: &'a OpticNode) -> Self { +impl NodeReference { + pub fn new(node: Rc<OpticNode>) -> Self { Self { reference: node } } } -impl<'a> Optical for NodeReference<'a> { +impl Optical for NodeReference { fn node_type(&self) -> &str { "reference" } diff --git a/src/optic_node.rs b/src/optic_node.rs index 62a28f24fc047768d03545aae1494ad8319cde47..d0de2252931b5eec20a19db6f7011e09df3ede18 100644 --- a/src/optic_node.rs +++ b/src/optic_node.rs @@ -1,4 +1,4 @@ -use std::fmt::Debug; +use std::{fmt::Debug, rc::Rc}; use crate::optic_ports::OpticPorts; /// An [`OpticNode`] is the basic struct representing an optical component. @@ -18,7 +18,7 @@ impl OpticNode { /// use opossum::optic_node::OpticNode; /// use opossum::nodes::NodeDummy; /// - /// let node=OpticNode::new("My node", Box::new(NodeDummy)); + /// let node=OpticNode::new("My node", NodeDummy); /// ``` pub fn new<T: Optical+ 'static>(name: &str, node_type: T) -> Self { let ports=node_type.ports(); diff --git a/src/optic_scenery.rs b/src/optic_scenery.rs index cccc6d8ad4b3ac8131c0161d123a67adf0d3837d..d733d21e39e193a85ae34abd8f7102f5b008fc6b 100644 --- a/src/optic_scenery.rs +++ b/src/optic_scenery.rs @@ -1,4 +1,7 @@ +use std::rc::Rc; + use crate::error::OpossumError; +use crate::light::Light; use crate::optic_node::{OpticNode, Optical}; use petgraph::algo::*; use petgraph::prelude::{DiGraph, EdgeIndex, NodeIndex}; @@ -9,7 +12,7 @@ type Result<T> = std::result::Result<T, OpossumError>; /// to be added to this structure in order to be considered for an analysis. #[derive(Default, Debug)] pub struct OpticScenery { - g: DiGraph<OpticNode, ()>, + g: DiGraph<Rc<OpticNode>, Light>, description: String, } @@ -23,7 +26,7 @@ impl OpticScenery { /// This command just adds an [`OpticNode`] to the graph. It does not connect /// it to existing nodes in the graph. The given optical element is consumed (owned) by the [`OpticScenery`]. pub fn add_node(&mut self, node: OpticNode) -> NodeIndex { - self.g.add_node(node) + self.g.add_node(Rc::new(node)) } /// Add a given optical element to the graph of this [`OpticScenery`]. /// @@ -31,7 +34,7 @@ impl OpticScenery { /// it to existing nodes in the graph. The given optical element is consumed (owned) by the [`OpticScenery`]. Internally the corresponding [`OpticNode`] is /// automatically generated. It serves as a short-cut to the `add_node` function. pub fn add_element<T: Optical + 'static>(&mut self, name: &str, t: T) -> NodeIndex { - self.g.add_node(OpticNode::new(name, t)) + self.g.add_node(Rc::new(OpticNode::new(name, t))) } /// Get reference of [`OpticNode`]. /// @@ -50,19 +53,51 @@ impl OpticScenery { pub fn connect_nodes( &mut self, src_node: NodeIndex, + src_port: &str, target_node: NodeIndex, + target_port: &str, ) -> Result<EdgeIndex> { - if self.g.node_weight(src_node).is_none() { + if let Some(source) = self.g.node_weight(src_node) { + if !source.ports().outputs().contains(&src_port.into()) { + return Err(OpossumError::OpticScenery(format!( + "source node {} does not have a port {}", + source.name(), + src_port + ))); + } + } else { return Err(OpossumError::OpticScenery( - "source node with gievn index does not exist".into(), + "source node with given index does not exist".into(), )); } - if self.g.node_weight(target_node).is_none() { + if let Some(target) = self.g.node_weight(target_node) { + if !target.ports().inputs().contains(&target_port.into()) { + return Err(OpossumError::OpticScenery(format!( + "target node {} does not have a port {}", + target.name(), + target_port + ))); + } + } else { return Err(OpossumError::OpticScenery( "target node with given index does not exist".into(), )); } - let edge_index = self.g.add_edge(src_node, target_node, ()); + if self.src_node_port_exists(src_node, src_port) { + return Err(OpossumError::OpticScenery(format!( + "src node with given port {} is already connected", + src_port + ))); + } + if self.target_node_port_exists(src_node, src_port) { + return Err(OpossumError::OpticScenery(format!( + "target node with given port {} is already connected", + target_port + ))); + } + let edge_index = self + .g + .add_edge(src_node, target_node, Light::new(src_port, target_port)); if is_cyclic_directed(&self.g) { self.g.remove_edge(edge_index); return Err(OpossumError::OpticScenery( @@ -71,6 +106,30 @@ impl OpticScenery { } Ok(edge_index) } + fn src_node_port_exists(&self, src_node: NodeIndex, src_port: &str) -> bool { + self.g + .edges_directed(src_node, petgraph::Direction::Outgoing) + .any(|e| e.weight().src_port() == src_port) + } + fn target_node_port_exists(&self, target_node: NodeIndex, target_port: &str) -> bool { + self.g + .edges_directed(target_node, petgraph::Direction::Incoming) + .any(|e| e.weight().target_port() == target_port) + } + /// Return a reference to the [`OpticNode`] specifiec by the node index. + /// + /// This function is mainly useful for setting up a reference node. + /// + /// # Errors + /// + /// This function will return an error if the node does not exist. + pub fn node_ref(&self, node: NodeIndex) ->Result<Rc<OpticNode>> { + if let Some(node) = self.g.node_weight(node) { + Ok(node.to_owned()) + } else { + Err(OpossumError::OpticScenery("node index does not exist".into())) + } + } /// Export the optic graph into the `dot` format to be used in combination with the [`graphviz`](https://graphviz.org/) software. pub fn to_dot(&self) -> String { let mut dot_string = "digraph {\n".to_owned(); @@ -83,11 +142,14 @@ impl OpticScenery { dot_string += &node.to_dot(&format!("i{}", node_idx.index())); } for edge in self.g.edge_indices() { + let light=self.g.edge_weight(edge).unwrap(); let end_nodes = self.g.edge_endpoints(edge).unwrap(); dot_string.push_str(&format!( - " i{} -> i{}\n", + " i{} -> i{} [label=\"{}->{}\"]\n", end_nodes.0.index(), - end_nodes.1.index() + end_nodes.1.index(), + light.src_port(), + light.target_port() )); } dot_string += "}"; @@ -135,7 +197,7 @@ mod test { let mut scenery = OpticScenery::new(); let n1 = scenery.add_element("Test", NodeDummy); let n2 = scenery.add_element("Test", NodeDummy); - assert!(scenery.connect_nodes(n1, n2).is_ok()); + assert!(scenery.connect_nodes(n1, "rear", n2, "front").is_ok()); assert_eq!(scenery.g.edge_count(), 1); } #[test] @@ -143,16 +205,20 @@ mod test { let mut scenery = OpticScenery::new(); let n1 = scenery.add_element("Test", NodeDummy); let n2 = scenery.add_element("Test", NodeDummy); - assert!(scenery.connect_nodes(n1, NodeIndex::new(5)).is_err()); - assert!(scenery.connect_nodes(NodeIndex::new(5), n2).is_err()); + assert!(scenery + .connect_nodes(n1, "rear", NodeIndex::new(5), "front") + .is_err()); + assert!(scenery + .connect_nodes(NodeIndex::new(5), "rear", n2, "front") + .is_err()); } #[test] fn connect_nodes_loop_error() { let mut scenery = OpticScenery::new(); let n1 = scenery.add_element("Test", NodeDummy); let n2 = scenery.add_element("Test", NodeDummy); - assert!(scenery.connect_nodes(n1, n2).is_ok()); - assert!(scenery.connect_nodes(n2, n1).is_err()); + assert!(scenery.connect_nodes(n1, "rear", n2, "front").is_ok()); + assert!(scenery.connect_nodes(n2, "rear", n1, "front").is_err()); assert_eq!(scenery.g.edge_count(), 1); } #[test] @@ -175,12 +241,12 @@ mod test { fn to_dot_with_edge() { let mut scenery = OpticScenery::new(); scenery.set_description("SceneryTest".into()); - let n1 = scenery.add_element("Test1", NodeDummy); - let n2 = scenery.add_element("Test2", NodeDummy); - if let Ok(_) = scenery.connect_nodes(n1, n2) { + let n1 = scenery.add_element("Test1", NodeDummy); + let n2 = scenery.add_element("Test2", NodeDummy); + if let Ok(_) = scenery.connect_nodes(n1, "rear", n2, "front") { assert_eq!( scenery.to_dot(), - "digraph {\n label=\"SceneryTest\"\n fontname=\"Helvetica,Arial,sans-serif\"\n node [fontname=\"Helvetica,Arial,sans-serif\"]\n edge [fontname=\"Helvetica,Arial,sans-serif\"]\n i0 [label=\"Test1\"]\n i1 [label=\"Test2\"]\n i0 -> i1\n}" + "digraph {\n label=\"SceneryTest\"\n fontname=\"Helvetica,Arial,sans-serif\"\n node [fontname=\"Helvetica,Arial,sans-serif\"]\n edge [fontname=\"Helvetica,Arial,sans-serif\"]\n i0 [label=\"Test1\"]\n i1 [label=\"Test2\"]\n i0 -> i1 [label=\"rear->front\"]\n}" ); } else { assert!(false);