-
Udo Eisenbarth authoredUdo Eisenbarth authored
group.rs 31.96 KiB
#![warn(missing_docs)]
use crate::analyzer::AnalyzerType;
use crate::error::OpossumError;
use crate::light::Light;
use crate::lightdata::LightData;
use crate::optic_node::{Dottable, LightResult};
use crate::{
optic_node::{OpticNode, Optical},
optic_ports::OpticPorts,
};
use petgraph::prelude::{DiGraph, EdgeIndex, NodeIndex};
use petgraph::visit::EdgeRef;
use petgraph::{algo::*, Direction};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
type Result<T> = std::result::Result<T, OpossumError>;
#[derive(Default, Debug, Clone)]
/// A node that represents a group of other [`OpticNode`]s arranges in a subgraph.
///
/// All unconnected input and output ports of this subgraph could be used as ports of
/// this [`NodeGroup`]. For this, port mapping is neccessary (see below).
///
/// ## Optical Ports
/// - Inputs
/// - defined by [`map_input_port`](NodeGroup::map_input_port()) function.
/// - Outputs
/// - defined by [`map_output_port`](NodeGroup::map_output_port()) function.
pub struct NodeGroup {
g: DiGraph<Rc<RefCell<OpticNode>>, Light>,
expand_view: bool,
input_port_map: HashMap<String, (NodeIndex, String)>,
output_port_map: HashMap<String, (NodeIndex, String)>,
is_inverted: bool,
}
impl NodeGroup {
/// Creates a new [`NodeGroup`].
pub fn new() -> Self {
Self {
expand_view: false,
..Default::default()
}
}
/// Add a given [`OpticNode`] to the (sub-)graph of this [`NodeGroup`].
///
/// This command just adds an [`OpticNode`] but does not connect it to existing nodes in the (sub-)graph. The given node is
/// consumed (owned) by the [`NodeGroup`].
pub fn add_node(&mut self, node: OpticNode) -> NodeIndex {
self.g.add_node(Rc::new(RefCell::new(node)))
}
/// Connect (already existing) nodes denoted by the respective `NodeIndex`.
///
/// Both node indices must exist. Otherwise an [`OpossumError::OpticScenery`] is returned. In addition, connections are
/// rejected and an [`OpossumError::OpticScenery`] is returned, if the graph would form a cycle (loop in the graph). **Note**:
/// The connection of two internal nodes might affect external port mappings (see [`map_input_port`](NodeGroup::map_input_port())
/// & [`map_output_port`](NodeGroup::map_output_port()) functions). In this case no longer valid mappings will be deleted.
pub fn connect_nodes(
&mut self,
src_node: NodeIndex,
src_port: &str,
target_node: NodeIndex,
target_port: &str,
) -> Result<EdgeIndex> {
if let Some(source) = self.g.node_weight(src_node) {
if !source.borrow().ports().outputs().contains(&src_port.into()) {
return Err(OpossumError::OpticScenery(format!(
"source node {} does not have a port {}",
source.borrow().name(),
src_port
)));
}
} else {
return Err(OpossumError::OpticScenery(
"source node with given index does not exist".into(),
));
}
if let Some(target) = self.g.node_weight(target_node) {
if !target
.borrow()
.ports()
.inputs()
.contains(&target_port.into())
{
return Err(OpossumError::OpticScenery(format!(
"target node {} does not have a port {}",
target.borrow().name(),
target_port
)));
}
} else {
return Err(OpossumError::OpticScenery(
"target node with given index does not exist".into(),
));
}
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(
"connecting the given nodes would form a loop".into(),
));
}
let in_map = self.input_port_map.clone();
let invalid_mapping = in_map
.iter()
.find(|m| m.1 .0 == target_node && m.1 .1 == target_port);
if let Some(input) = invalid_mapping {
self.input_port_map.remove(input.0);
}
let out_map = self.output_port_map.clone();
let invalid_mapping = out_map
.iter()
.find(|m| m.1 .0 == src_node && m.1 .1 == src_port);
if let Some(input) = invalid_mapping {
self.output_port_map.remove(input.0);
}
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)
}
fn input_nodes(&self) -> Vec<NodeIndex> {
let mut input_nodes: Vec<NodeIndex> = Vec::default();
for node_idx in self.g.node_indices() {
let incoming_edges = self.g.edges_directed(node_idx, Direction::Incoming).count();
let input_ports = self
.g
.node_weight(node_idx)
.unwrap()
.borrow()
.ports()
.inputs()
.len();
if input_ports != incoming_edges {
input_nodes.push(node_idx);
}
}
input_nodes
}
fn output_nodes(&self) -> Vec<NodeIndex> {
let mut output_nodes: Vec<NodeIndex> = Vec::default();
for node_idx in self.g.node_indices() {
let outgoing_edges = self.g.edges_directed(node_idx, Direction::Outgoing).count();
let output_ports = self
.g
.node_weight(node_idx)
.unwrap()
.borrow()
.ports()
.outputs()
.len();
if output_ports != outgoing_edges {
output_nodes.push(node_idx);
}
}
output_nodes
}
/// Map an input port of an internal node to an external port of the group.
///
/// In oder to use a [`NodeGroup`] from the outside, internal nodes / ports must be mapped to be visible. The
/// corresponding [`ports`](NodeGroup::ports()) function only returns ports that have been mapped before.
/// # Errors
///
/// This function will return an error if
/// - an external input port name has already been assigned.
/// - the `input_node` / `internal_name` does not exist.
/// - the specified `input_node` is not an input node of the group (i.e. fully connected to other internal nodes).
/// - the `input_node` has an input port with the specified `internal_name` but is already internally connected.
pub fn map_input_port(
&mut self,
input_node: NodeIndex,
internal_name: &str,
external_name: &str,
) -> Result<()> {
if self.input_port_map.contains_key(external_name) {
return Err(OpossumError::OpticGroup(
"external input port name already assigned".into(),
));
}
if let Some(node) = self.g.node_weight(input_node) {
if !node
.borrow()
.ports()
.inputs()
.contains(&(internal_name.to_string()))
{
return Err(OpossumError::OpticGroup(
"internal input port name not found".into(),
));
}
} else {
return Err(OpossumError::OpticGroup(
"internal node index not found".into(),
));
}
if !self.input_nodes().contains(&input_node) {
return Err(OpossumError::OpticGroup(
"node to be mapped is not an input node of the group".into(),
));
}
let incoming_edge_connected = self
.g
.edges_directed(input_node, Direction::Incoming)
.map(|e| e.weight().target_port())
.any(|p| p == internal_name);
if incoming_edge_connected {
return Err(OpossumError::OpticGroup(
"port of input node is already internally connected".into(),
));
}
self.input_port_map.insert(
external_name.to_string(),
(input_node, internal_name.to_string()),
);
Ok(())
}
/// Map an output port of an internal node to an external port of the group.
///
/// In oder to use a [`NodeGroup`] from the outside, internal nodes / ports must be mapped to be visible. The
/// corresponding [`ports`](NodeGroup::ports()) function only returns ports that have been mapped before.
/// # Errors
///
/// This function will return an error if
/// - an external output port name has already been assigned.
/// - the `output_node` / `internal_name` does not exist.
/// - the specified `output_node` is not an output node of the group (i.e. fully connected to other internal nodes).
/// - the `output_node` has an output port with the specified `internal_name` but is already internally connected.
pub fn map_output_port(
&mut self,
output_node: NodeIndex,
internal_name: &str,
external_name: &str,
) -> Result<()> {
if self.output_port_map.contains_key(external_name) {
return Err(OpossumError::OpticGroup(
"external output port name already assigned".into(),
));
}
if let Some(node) = self.g.node_weight(output_node) {
if !node
.borrow()
.ports()
.outputs()
.contains(&(internal_name.to_string()))
{
return Err(OpossumError::OpticGroup(
"internal output port name not found".into(),
));
}
} else {
return Err(OpossumError::OpticGroup(
"internal node index not found".into(),
));
}
if !self.output_nodes().contains(&output_node) {
return Err(OpossumError::OpticGroup(
"node to be mapped is not an output node of the group".into(),
));
}
let outgoing_edge_connected = self
.g
.edges_directed(output_node, Direction::Outgoing)
.map(|e| e.weight().src_port())
.any(|p| p == internal_name);
if outgoing_edge_connected {
return Err(OpossumError::OpticGroup(
"port of output node is already internally connected".into(),
));
}
self.output_port_map.insert(
external_name.to_string(),
(output_node, internal_name.to_string()),
);
Ok(())
}
fn incoming_edges(&self, idx: NodeIndex) -> LightResult {
let edges = self.g.edges_directed(idx, Direction::Incoming);
edges
.into_iter()
.map(|e| {
(
e.weight().target_port().to_owned(),
e.weight().data().cloned(),
)
})
.collect::<HashMap<String, Option<LightData>>>()
}
fn set_outgoing_edge_data(&mut self, idx: NodeIndex, port: String, data: Option<LightData>) {
let edges = self.g.edges_directed(idx, Direction::Outgoing);
let edge_ref = edges
.into_iter()
.filter(|idx| idx.weight().src_port() == port)
.last();
if let Some(edge_ref) = edge_ref {
let edge_idx = edge_ref.id();
let light = self.g.edge_weight_mut(edge_idx);
if let Some(light) = light {
light.set_data(data);
}
} // else outgoing edge not connected -> data dropped
}
fn analyze_group(
&mut self,
incoming_data: LightResult,
analyzer_type: &AnalyzerType,
) -> Result<LightResult> {
let g_clone = self.g.clone();
let mut group_srcs = g_clone.externals(Direction::Incoming);
let mut light_result = LightResult::default();
let sorted = toposort(&self.g, None).unwrap();
for idx in sorted {
// Check if node is group src node
let incoming_edges = if group_srcs.any(|gs| gs == idx) {
// get from incoming_data
let portmap = if self.is_inverted { &self.output_port_map} else { &self.input_port_map};
let assigned_ports = portmap.iter().filter(|p| p.1 .0 == idx);
let mut incoming = LightResult::default();
for port in assigned_ports {
incoming.insert(
port.1 .1.to_owned(),
incoming_data.get(port.0).unwrap().clone(),
);
}
incoming
} else {
self.incoming_edges(idx)
};
let node = self.g.node_weight(idx).unwrap();
// println!("Analyzing node {}", node.borrow().name());
// println!("Incoming edges {:?}", incoming_edges);
let outgoing_edges = node.borrow_mut().analyze(incoming_edges, analyzer_type)?;
// println!("Outgoing edges: {:?}", outgoing_edges);
let mut group_sinks = self.g.externals(Direction::Outgoing);
// Check if node is group sink node
if group_sinks.any(|gs| gs == idx) {
let portmap = if self.is_inverted { &self.input_port_map} else { &self.output_port_map};
let assigned_ports = portmap.iter().filter(|p| p.1 .0 == idx);
for port in assigned_ports {
light_result.insert(
port.0.to_owned(),
outgoing_edges.get(&port.1 .1).unwrap().clone(),
);
}
} else {
for outgoing_edge in outgoing_edges {
self.set_outgoing_edge_data(idx, outgoing_edge.0, outgoing_edge.1)
}
}
}
Ok(light_result)
}
/// Sets the expansion flag of this [`NodeGroup`].
/// If true, the group expands and the internal nodes of this group are displayed in the dot format.
/// If false, only the group node itself is displayed and the internal setup is not shown
pub fn shall_expand(&self) -> bool {
self.expand_view
}
/// Defines and returns the node/port identifier to connect the edges in the dot format
/// parameters:
/// - port_name: name of the external port of the group
/// - parent_identifier: String that contains the hierarchical structure: parentidx_childidx_childofchildidx ...
///
/// Error, if the port is not mapped as input or output
pub fn get_mapped_port_str(
&self,
port_name: &str,
parent_identifier: String,
) -> Result<String> {
if self.shall_expand() {
if self.input_port_map.contains_key(port_name) {
let port = self.input_port_map.get(port_name).unwrap();
Ok(format!(
"{}_i{}:{}",
parent_identifier,
port.0.index(),
port.1
))
} else if self.output_port_map.contains_key(port_name) {
let port = self.output_port_map.get(port_name).unwrap();
Ok(format!(
"{}_i{}:{}",
parent_identifier,
port.0.index(),
port.1
))
} else {
Err(OpossumError::OpticGroup(format!(
"port {} is not mapped",
port_name
)))
}
} else {
Ok(format!("{}:{}", parent_identifier, port_name))
}
}
/// returns the boolean which defines whether the group expands or not.
pub fn expand_view(&mut self, expand_view: bool) {
self.expand_view = expand_view;
}
/// downcasts this "OpticNode" with trait "OpicComponent" to its actual struct format "NodeGroup"
/// parameters:
/// - ref_node: reference to the borrowed node of a graph
///
/// Returns a reference to the NodeGroup struct
///
/// Error, if the OpticNode can not be casted to the type of NodeGroup
fn cast_node_to_group<'a>(&self, ref_node: &'a OpticNode) -> Result<&'a NodeGroup> {
let node_boxed = (&*ref_node).node();
let downcasted_node = node_boxed.downcast_ref::<NodeGroup>();
match downcasted_node {
Some(i) => Ok(i),
_ => Err(OpossumError::OpticScenery(
"can not cast OpticNode to specific type of NodeGroup!".into(),
)),
}
}
/// checks if the contained node is a group_node itself.
/// Returns true, if the node is a group
/// Returns false otherwise
fn check_if_group(&self, node_ref: &OpticNode) -> bool {
if node_ref.node_type() == "group" {
true
} else {
false
}
}
/// Creates the dot-format string which describes the edge that connects two nodes
/// parameters:
/// - end_node_idx: NodeIndex of the node that should be connected
/// - light_port: port name that should be connected
/// - parent_identifier: String that contains the hierarchical structure: parentidx_childidx_childofchildidx ...
///
/// Returns the result of the edge strnig for the dot format
fn create_node_edge_str(
&self,
end_node_idx: NodeIndex,
light_port: &str,
mut parent_identifier: String,
) -> Result<String> {
let node = self.g.node_weight(end_node_idx).unwrap().borrow();
parent_identifier = if parent_identifier == "" {
format!("i{}", end_node_idx.index())
} else {
format!("{}_i{}", &parent_identifier, end_node_idx.index())
};
if self.check_if_group(&node) {
let group_node = self.cast_node_to_group(&node)?;
Ok(group_node.get_mapped_port_str(light_port, parent_identifier)?)
} else {
Ok(format!("{}:{}", parent_identifier, light_port))
}
}
/// creates the dot format of the group node in its expanded view
/// parameters:
/// - node_index: NodeIndex of the group
/// - name: name of the node
/// - inverted: boolean that descries wether the node is inverted or not
/// - parent_identifier: String that contains the hierarchical structure: parentidx_childidx_childofchildidx ...
///
/// Returns the result of the dot string that describes this node
fn to_dot_expanded_view(
&self,
node_index: &str,
name: &str,
inverted: bool,
mut parent_identifier: String,
) -> Result<String> {
let inv_string = if inverted { "(inv)" } else { "" };
parent_identifier = if parent_identifier == "" {
format!("i{}", node_index)
} else {
format!("{}_i{}", &parent_identifier, node_index)
};
let mut dot_string = format!(
" subgraph {} {{\n\tlabel=\"{}{}\"\n\tfontsize=15\n\tcluster=true\n\t",
parent_identifier, name, inv_string
);
for node_idx in self.g.node_indices() {
let node = self.g.node_weight(node_idx).unwrap();
dot_string += &node
.borrow()
.to_dot(&format!("{}", node_idx.index()), parent_identifier.clone())?;
}
for edge in self.g.edge_indices() {
let light: &Light = self.g.edge_weight(edge).unwrap();
let end_nodes = self.g.edge_endpoints(edge).unwrap();
let src_edge_str = self.create_node_edge_str(
end_nodes.0,
light.src_port(),
parent_identifier.clone(),
)?;
let target_edge_str = self.create_node_edge_str(
end_nodes.1,
light.target_port(),
parent_identifier.clone(),
)?;
dot_string.push_str(&format!(" {} -> {} \n", src_edge_str, target_edge_str));
// needed when multiple ports can be assigned
// for src in src_edge_str.iter(){
// println!("{}", src);
// for target in target_edge_str.iter(){
// println!("{}", target);
// dot_string.push_str(&format!(" {} -> {} \n", src, target));
// };
// };
}
dot_string += "}";
Ok(dot_string)
}
/// creates the dot format of the group node in its collapsed view
/// parameters:
/// - node_index: NodeIndex of the group
/// - name: name of the node
/// - inverted: boolean that descries wether the node is inverted or not
/// - _ports:
/// - parent_identifier: String that contains the hierarchical structure: parentidx_childidx_childofchildidx ...
///
/// Returns the result of the dot string that describes this node
fn to_dot_collapsed_view(
&self,
node_index: &str,
name: &str,
inverted: bool,
_ports: &OpticPorts,
mut parent_identifier: String,
) -> Result<String> {
let inv_string = if inverted { " (inv)" } else { "" };
let node_name = format!("{}{}", name, inv_string);
parent_identifier = if parent_identifier == "" {
format!("i{}", node_index)
} else {
format!("{}_i{}", &parent_identifier, node_index)
};
let mut dot_str = format!("\t{} [\n\t\tshape=plaintext\n", parent_identifier);
let mut indent_level = 2;
dot_str.push_str(&self.add_html_like_labels(
&node_name,
&mut indent_level,
_ports,
inverted,
));
Ok(dot_str)
}
fn invert_graph(&mut self) {
for node in self.g.node_weights_mut() {
node.borrow_mut().set_inverted(true);
}
for edge in self.g.edge_weights_mut() {
edge.inverse();
}
self.g.reverse();
}
}
impl Optical for NodeGroup {
fn node_type(&self) -> &str {
"group"
}
fn ports(&self) -> OpticPorts {
let mut ports = OpticPorts::new();
for p in self.input_port_map.iter() {
ports.add_input(p.0).unwrap();
}
for p in self.output_port_map.iter() {
ports.add_output(p.0).unwrap();
}
ports
}
fn analyze(
&mut self,
incoming_data: LightResult,
analyzer_type: &AnalyzerType,
) -> Result<LightResult> {
self.analyze_group(incoming_data, analyzer_type)
}
fn set_inverted(&mut self, inverted: bool) {
if inverted {
self.invert_graph();
}
self.is_inverted = inverted;
}
}
impl Dottable for NodeGroup {
fn to_dot(
&self,
node_index: &str,
name: &str,
inverted: bool,
_ports: &OpticPorts,
parent_identifier: String,
) -> Result<String> {
if self.expand_view {
self.to_dot_expanded_view(node_index, name, inverted, parent_identifier)
} else {
self.to_dot_collapsed_view(node_index, name, inverted, _ports, parent_identifier)
}
}
fn node_color(&self) -> &str {
"yellow"
}
}
#[cfg(test)]
mod test {
use super::NodeGroup;
use crate::{
nodes::{BeamSplitter, Dummy},
optic_node::{OpticNode, Optical},
};
#[test]
fn new() {
let og = NodeGroup::new();
assert_eq!(og.g.node_count(), 0);
assert_eq!(og.g.edge_count(), 0);
assert!(og.input_port_map.is_empty());
assert!(og.output_port_map.is_empty());
}
#[test]
fn add_node() {
let mut og = NodeGroup::new();
let sub_node = OpticNode::new("test", Dummy::default());
og.add_node(sub_node);
assert_eq!(og.g.node_count(), 1);
}
#[test]
fn connect_nodes() {
let mut og = NodeGroup::new();
let sub_node1 = OpticNode::new("test1", Dummy::default());
let sn1_i = og.add_node(sub_node1);
let sub_node2 = OpticNode::new("test2", Dummy::default());
let sn2_i = og.add_node(sub_node2);
// wrong port names
assert!(og.connect_nodes(sn1_i, "wrong", sn2_i, "front").is_err());
assert_eq!(og.g.edge_count(), 0);
assert!(og.connect_nodes(sn1_i, "rear", sn2_i, "wrong").is_err());
assert_eq!(og.g.edge_count(), 0);
// wrong node index
assert!(og.connect_nodes(5.into(), "rear", sn2_i, "front").is_err());
assert_eq!(og.g.edge_count(), 0);
assert!(og.connect_nodes(sn1_i, "rear", 5.into(), "front").is_err());
assert_eq!(og.g.edge_count(), 0);
// correct usage
assert!(og.connect_nodes(sn1_i, "rear", sn2_i, "front").is_ok());
assert_eq!(og.g.edge_count(), 1);
}
#[test]
fn connect_nodes_update_port_mapping() {
let mut og = NodeGroup::new();
let sub_node1 = OpticNode::new("test1", Dummy::default());
let sn1_i = og.add_node(sub_node1);
let sub_node2 = OpticNode::new("test2", Dummy::default());
let sn2_i = og.add_node(sub_node2);
og.map_input_port(sn2_i, "front", "input").unwrap();
og.map_output_port(sn1_i, "rear", "output").unwrap();
assert_eq!(og.input_port_map.len(), 1);
assert_eq!(og.output_port_map.len(), 1);
og.connect_nodes(sn1_i, "rear", sn2_i, "front").unwrap();
// delete no longer valid port mapping
assert_eq!(og.input_port_map.len(), 0);
assert_eq!(og.output_port_map.len(), 0);
}
#[test]
fn input_nodes() {
let mut og = NodeGroup::new();
let sub_node1 = OpticNode::new("test1", Dummy::default());
let sn1_i = og.add_node(sub_node1);
let sub_node1 = OpticNode::new("test2", Dummy::default());
let sn2_i = og.add_node(sub_node1);
let sub_node3 = OpticNode::new("test3", BeamSplitter::new(0.5));
let sn3_i = og.add_node(sub_node3);
og.connect_nodes(sn1_i, "rear", sn2_i, "front").unwrap();
og.connect_nodes(sn2_i, "rear", sn3_i, "input1").unwrap();
assert_eq!(og.input_nodes(), vec![0.into(), 2.into()])
}
#[test]
fn output_nodes() {
let mut og = NodeGroup::new();
let sub_node1 = OpticNode::new("test1", Dummy::default());
let sn1_i = og.add_node(sub_node1);
let sub_node1 = OpticNode::new("test2", BeamSplitter::new(0.5));
let sn2_i = og.add_node(sub_node1);
let sub_node3 = OpticNode::new("test3", Dummy::default());
let sn3_i = og.add_node(sub_node3);
og.connect_nodes(sn1_i, "rear", sn2_i, "input1").unwrap();
og.connect_nodes(sn2_i, "out1_trans1_refl2", sn3_i, "front")
.unwrap();
assert_eq!(og.input_nodes(), vec![0.into(), 1.into()])
}
#[test]
fn map_input_port() {
let mut og = NodeGroup::new();
let sub_node1 = OpticNode::new("test1", Dummy::default());
let sn1_i = og.add_node(sub_node1);
let sub_node2 = OpticNode::new("test2", Dummy::default());
let sn2_i = og.add_node(sub_node2);
og.connect_nodes(sn1_i, "rear", sn2_i, "front").unwrap();
// wrong port name
assert!(og.map_input_port(sn1_i, "wrong", "input").is_err());
assert!(og.input_port_map.is_empty());
// wrong node index
assert!(og.map_input_port(5.into(), "front", "input").is_err());
assert!(og.input_port_map.is_empty());
// map output port
assert!(og.map_input_port(sn2_i, "rear", "input").is_err());
assert!(og.input_port_map.is_empty());
// map internal node
assert!(og.map_input_port(sn2_i, "front", "input").is_err());
assert!(og.input_port_map.is_empty());
// correct usage
assert!(og.map_input_port(sn1_i, "front", "input").is_ok());
assert_eq!(og.input_port_map.len(), 1);
}
#[test]
fn map_input_port_half_connected_nodes() {
let mut og = NodeGroup::new();
let sub_node1 = OpticNode::new("test1", Dummy::default());
let sn1_i = og.add_node(sub_node1);
let sub_node2 = OpticNode::new("test2", BeamSplitter::default());
let sn2_i = og.add_node(sub_node2);
og.connect_nodes(sn1_i, "rear", sn2_i, "input1").unwrap();
// node port already internally connected
assert!(og.map_input_port(sn2_i, "input1", "bs_input").is_err());
// correct usage
assert!(og.map_input_port(sn1_i, "front", "input").is_ok());
assert!(og.map_input_port(sn2_i, "input2", "bs_input").is_ok());
assert_eq!(og.input_port_map.len(), 2);
}
#[test]
fn map_output_port() {
let mut og = NodeGroup::new();
let sub_node1 = OpticNode::new("test1", Dummy::default());
let sn1_i = og.add_node(sub_node1);
let sub_node2 = OpticNode::new("test2", Dummy::default());
let sn2_i = og.add_node(sub_node2);
og.connect_nodes(sn1_i, "rear", sn2_i, "front").unwrap();
// wrong port name
assert!(og.map_output_port(sn2_i, "wrong", "output").is_err());
assert!(og.output_port_map.is_empty());
// wrong node index
assert!(og.map_output_port(5.into(), "rear", "output").is_err());
assert!(og.output_port_map.is_empty());
// map input port
assert!(og.map_output_port(sn1_i, "front", "output").is_err());
assert!(og.output_port_map.is_empty());
// map internal node
assert!(og.map_output_port(sn1_i, "rear", "output").is_err());
assert!(og.output_port_map.is_empty());
// correct usage
assert!(og.map_output_port(sn2_i, "rear", "output").is_ok());
assert_eq!(og.output_port_map.len(), 1);
}
#[test]
fn map_output_port_half_connected_nodes() {
let mut og = NodeGroup::new();
let sub_node1 = OpticNode::new("test1", BeamSplitter::default());
let sn1_i = og.add_node(sub_node1);
let sub_node2 = OpticNode::new("test2", Dummy::default());
let sn2_i = og.add_node(sub_node2);
og.connect_nodes(sn1_i, "out1_trans1_refl2", sn2_i, "front")
.unwrap();
// node port already internally connected
assert!(og
.map_output_port(sn1_i, "out1_trans1_refl2", "bs_output")
.is_err());
// correct usage
assert!(og
.map_output_port(sn1_i, "out2_trans2_refl1", "bs_output")
.is_ok());
assert!(og.map_output_port(sn2_i, "rear", "output").is_ok());
assert_eq!(og.output_port_map.len(), 2);
}
#[test]
fn ports() {
let mut og = NodeGroup::new();
let sub_node1 = OpticNode::new("test1", Dummy::default());
let sn1_i = og.add_node(sub_node1);
let sub_node2 = OpticNode::new("test2", Dummy::default());
let sn2_i = og.add_node(sub_node2);
og.connect_nodes(sn1_i, "rear", sn2_i, "front").unwrap();
assert!(og.ports().inputs().is_empty());
assert!(og.ports().outputs().is_empty());
og.map_input_port(sn1_i, "front", "input").unwrap();
assert!(og.ports().inputs().contains(&("input".to_string())));
og.map_output_port(sn2_i, "rear", "output").unwrap();
assert!(og.ports().outputs().contains(&("output".to_string())));
}
}