Newer
Older
use crate::{
optic_node::{OpticNode, Optical},
optic_ports::OpticPorts,
};
use petgraph::algo::*;
use petgraph::prelude::{DiGraph, EdgeIndex, NodeIndex};
type Result<T> = std::result::Result<T, OpossumError>;
#[derive(Default, Debug)]
/// A node that represents a group of other [`OpticNode`]s. These subnodes are arranged in its own subgraph. All unconnected input and output ports of this subgraph form the ports of this [`NodeGroup`].
}
impl NodeGroup {
pub fn new() -> Self {
Self::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(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).
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 given index does not exist".into(),
));
}
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(),
));
}
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(),
));
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)
}
}
impl Optical for NodeGroup {
fn node_type(&self) -> &str {
"group"
}
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.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"