Newer
Older
use crate::analyzer::AnalyzerType;
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};
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.
g: DiGraph<Rc<RefCell<OpticNode>>, Light>,
input_port_map: HashMap<String, (NodeIndex, String)>,
output_port_map: HashMap<String, (NodeIndex, String)>,
/// 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.
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 {}",
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_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);
}
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)
}
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
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
.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(())
}
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
}
&mut self,
incoming_data: LightResult,
analyzer_type: &AnalyzerType,
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
) -> 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 assigned_ports = self.input_port_map.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();
let outgoing_edges = node.borrow_mut().analyze(incoming_edges, analyzer_type)?;
let mut group_sinks = self.g.externals(Direction::Outgoing);
// Check if node is group sink node
if group_sinks.any(|gs| gs == idx) {
let assigned_ports = self.output_port_map.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)
}
}
}
}
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 to_dot(&self, node_index: &str, name: &str, inverted: bool, _ports: &OpticPorts) -> String {
let inv_string = if inverted { "(inv)" } else { "" };
let mut dot_string = format!(
" subgraph i{} {{\n\tlabel=\"{}{}\"\n\tfontsize=15\n\tcluster=true\n\t",
node_index, 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!("{}_i{}", node_index, node_idx.index()));
}
for edge in self.g.edge_indices() {
let end_nodes = self.g.edge_endpoints(edge).unwrap();
let light = self.g.edge_weight(edge).unwrap();
end_nodes.1.index(),
light.target_port()
fn node_color(&self) -> &str {
"yellow"
#[cfg(test)]
mod test {
use super::NodeGroup;
use crate::{
nodes::{BeamSplitter, Dummy},
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
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);
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);
let sn1_i = og.add_node(sub_node1);
let sub_node2 = OpticNode::new("test2", Dummy);
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);
let sn1_i = og.add_node(sub_node1);
let sub_node2 = OpticNode::new("test2", Dummy);
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);
let sn1_i = og.add_node(sub_node1);
let sub_node1 = OpticNode::new("test2", Dummy);
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);
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);
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()])
fn map_input_port() {
let mut og = NodeGroup::new();
let sub_node1 = OpticNode::new("test1", Dummy);
let sn1_i = og.add_node(sub_node1);
let sub_node2 = OpticNode::new("test2", Dummy);
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);
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);
let sn1_i = og.add_node(sub_node1);
let sub_node2 = OpticNode::new("test2", Dummy);
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);
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);
let sn1_i = og.add_node(sub_node1);
let sub_node2 = OpticNode::new("test2", Dummy);
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())));
}
}