Skip to content
Snippets Groups Projects
group.rs 5.09 KiB
Newer Older
Udo Eisenbarth's avatar
Udo Eisenbarth committed
use crate::error::OpossumError;
Udo Eisenbarth's avatar
Udo Eisenbarth committed
use crate::light::Light;
use crate::optic_node::Dottable;
Udo Eisenbarth's avatar
Udo Eisenbarth committed
use crate::{
    optic_node::{OpticNode, Optical},
    optic_ports::OpticPorts,
};
Udo Eisenbarth's avatar
Udo Eisenbarth committed
use petgraph::algo::*;
use petgraph::prelude::{DiGraph, EdgeIndex, NodeIndex};

type Result<T> = std::result::Result<T, OpossumError>;

#[derive(Default, Debug)]
Udo Eisenbarth's avatar
Udo Eisenbarth committed
/// 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`].
Udo Eisenbarth's avatar
Udo Eisenbarth committed
pub struct NodeGroup {
    g: DiGraph<OpticNode, Light>,
Udo Eisenbarth's avatar
Udo Eisenbarth committed
}

impl NodeGroup {
    pub fn new() -> Self {
        Self::default()
    }
Udo Eisenbarth's avatar
Udo Eisenbarth committed
    /// Add a given [`OpticNode`] to the (sub-)graph of this [`NodeGroup`].
Udo Eisenbarth's avatar
Udo Eisenbarth committed
    ///
    /// 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`.
    ///
Udo Eisenbarth's avatar
Udo Eisenbarth committed
    /// 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).
Udo Eisenbarth's avatar
Udo Eisenbarth committed
    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.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(),
            ));
        }
        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)
    }
Udo Eisenbarth's avatar
Udo Eisenbarth committed
}

impl Optical for NodeGroup {
    fn node_type(&self) -> &str {
        "group"
    }
Udo Eisenbarth's avatar
Udo Eisenbarth committed
impl Dottable for NodeGroup {
    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!(
y.zobus's avatar
y.zobus committed
            "  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();
            dot_string.push_str(&format!(
                "      i{}_i{}:{} -> i{}_i{}:{}\n",
                node_index,
                end_nodes.0.index(),
                light.src_port(),
                node_index,
                end_nodes.1.index(),
                light.target_port()
        dot_string += "}";
        dot_string
    }

    fn node_color(&self) -> &str {
        "yellow"
Udo Eisenbarth's avatar
Udo Eisenbarth committed
    }