pub struct Graph<N, P, E> { /* private fields */ }Expand description
A node-port-edge graph.
Type parameters are the user payloads:
N— per-node (component) dataP— per-port (pin / terminal) dataE— per-edge (wire / pipe / signal) data
All structural invariants are maintained internally:
- every port belongs to exactly one live node,
- every edge references exactly two live ports,
- removals cascade (node → its ports → their edges).
Implementations§
Source§impl<N, P, E> Graph<N, P, E>
impl<N, P, E> Graph<N, P, E>
Sourcepub fn add_node(&mut self, data: N) -> NodeId
pub fn add_node(&mut self, data: N) -> NodeId
Adds a node (component) carrying data. Starts with no ports.
Sourcepub fn add_port(&mut self, node: NodeId, data: P) -> Result<PortId, NodeMissing>
pub fn add_port(&mut self, node: NodeId, data: P) -> Result<PortId, NodeMissing>
Adds a port to node. Ports are returned by Graph::ports in the
order they were added.
Sourcepub fn connect(
&mut self,
a: PortId,
b: PortId,
data: E,
) -> Result<EdgeId, ConnectError>
pub fn connect( &mut self, a: PortId, b: PortId, data: E, ) -> Result<EdgeId, ConnectError>
Connects two ports with an edge carrying data.
Parallel edges between the same pair of ports are allowed (draw two wires if you like; deduplicate at the GUI layer if you don’t).
Sourcepub fn connect_with<F>(
&mut self,
a: PortId,
b: PortId,
data: E,
check: F,
) -> Result<EdgeId, ConnectError>
pub fn connect_with<F>( &mut self, a: PortId, b: PortId, data: E, check: F, ) -> Result<EdgeId, ConnectError>
Like Graph::connect, but runs check on the two ports’ data first.
This is the hook for domain rules: direction compatibility in dataflow
graphs, voltage domains, pipe diameters, connector genders, etc.
#[derive(PartialEq)] enum Dir { In, Out }
let mut g: Graph<(), Dir, ()> = Graph::new();
let a = g.add_node(()); let b = g.add_node(());
let out = g.add_port(a, Dir::Out).unwrap();
let inp = g.add_port(b, Dir::In).unwrap();
assert!(g.connect_with(out, inp, (), |x, y| *x == Dir::Out && *y == Dir::In).is_ok());
assert_eq!(
g.connect_with(inp, out, (), |x, y| *x == Dir::Out && *y == Dir::In),
Err(ConnectError::Rejected)
);Sourcepub fn disconnect(&mut self, edge: EdgeId) -> Option<E>
pub fn disconnect(&mut self, edge: EdgeId) -> Option<E>
Removes an edge, returning its data. Stale IDs return None.
Sourcepub fn remove_port(&mut self, port: PortId) -> Option<P>
pub fn remove_port(&mut self, port: PortId) -> Option<P>
Removes a port, cascading removal of every edge incident to it.
Sourcepub fn remove_node(&mut self, node: NodeId) -> Option<N>
pub fn remove_node(&mut self, node: NodeId) -> Option<N>
Removes a node, cascading removal of all its ports and their edges.
Sourcepub fn node(&self, id: NodeId) -> Option<&N>
pub fn node(&self, id: NodeId) -> Option<&N>
Node data, if the ID is live. Stale IDs return None (never panic),
which is what you want when a GUI holds selections across edits.
Sourcepub fn edge_endpoints(&self, edge: EdgeId) -> Option<(PortId, PortId)>
pub fn edge_endpoints(&self, edge: EdgeId) -> Option<(PortId, PortId)>
The two ports an edge connects, in the order passed to connect.
Sourcepub fn edge_nodes(&self, edge: EdgeId) -> Option<(NodeId, NodeId)>
pub fn edge_nodes(&self, edge: EdgeId) -> Option<(NodeId, NodeId)>
The two nodes an edge connects (through its ports).
Sourcepub fn opposite(&self, edge: EdgeId, port: PortId) -> Option<PortId>
pub fn opposite(&self, edge: EdgeId, port: PortId) -> Option<PortId>
Given an edge and one of its ports, the port at the other end.
Sourcepub fn ports(&self, node: NodeId) -> impl Iterator<Item = PortId> + '_
pub fn ports(&self, node: NodeId) -> impl Iterator<Item = PortId> + '_
A node’s ports, in insertion order. Empty iterator for stale IDs.
Sourcepub fn find_port<F>(&self, node: NodeId, pred: F) -> Option<PortId>
pub fn find_port<F>(&self, node: NodeId, pred: F) -> Option<PortId>
The first port of node whose data satisfies pred, in pinout order.
This is the data-agnostic hook for “find the pin named X”: the core
can’t know your P has names, so you supply the predicate. For a
repeated lookup, build a map once instead (see crate docs); for a
fixed pinout known at compile time, an index constant is cheaper still.
let mut g: Graph<(), &str, ()> = Graph::new();
let n = g.add_node(());
g.add_port(n, "a").unwrap();
let out = g.add_port(n, "Output").unwrap();
assert_eq!(g.find_port(n, |p| *p == "Output"), Some(out));Sourcepub fn find_ports<'a, F>(
&'a self,
node: NodeId,
pred: F,
) -> impl Iterator<Item = PortId> + 'a
pub fn find_ports<'a, F>( &'a self, node: NodeId, pred: F, ) -> impl Iterator<Item = PortId> + 'a
Every port of node whose data satisfies pred, in pinout order.
Sourcepub fn port_edges(&self, port: PortId) -> impl Iterator<Item = EdgeId> + '_
pub fn port_edges(&self, port: PortId) -> impl Iterator<Item = EdgeId> + '_
Edges incident to a port.
Sourcepub fn node_edges(&self, node: NodeId) -> impl Iterator<Item = EdgeId> + '_
pub fn node_edges(&self, node: NodeId) -> impl Iterator<Item = EdgeId> + '_
Edges incident to any port of node.
Sourcepub fn neighbors(&self, node: NodeId) -> impl Iterator<Item = NodeId> + '_
pub fn neighbors(&self, node: NodeId) -> impl Iterator<Item = NodeId> + '_
Neighboring nodes of node (one entry per connecting edge — a node
connected by two wires appears twice; collect into a set to dedup).
Sourcepub fn edges_between(
&self,
a: PortId,
b: PortId,
) -> impl Iterator<Item = EdgeId> + '_
pub fn edges_between( &self, a: PortId, b: PortId, ) -> impl Iterator<Item = EdgeId> + '_
Edges directly between ports a and b (parallel edges possible).
Sourcepub fn nodes(&self) -> impl Iterator<Item = (NodeId, &N)> + '_
pub fn nodes(&self) -> impl Iterator<Item = (NodeId, &N)> + '_
All nodes with their data. Order is deterministic but not insertion order; sort by your own data if presentation order matters.
Sourcepub fn node_count(&self) -> usize
pub fn node_count(&self) -> usize
Number of live nodes.
Sourcepub fn port_count(&self) -> usize
pub fn port_count(&self) -> usize
Number of live ports.
Sourcepub fn edge_count(&self) -> usize
pub fn edge_count(&self) -> usize
Number of live edges.
Sourcepub fn contains_node(&self, id: NodeId) -> bool
pub fn contains_node(&self, id: NodeId) -> bool
Is this ID live?
Sourcepub fn contains_port(&self, id: PortId) -> bool
pub fn contains_port(&self, id: PortId) -> bool
Is this ID live?
Sourcepub fn contains_edge(&self, id: EdgeId) -> bool
pub fn contains_edge(&self, id: EdgeId) -> bool
Is this ID live?
Source§impl<N, P, E> Graph<N, P, E>
impl<N, P, E> Graph<N, P, E>
Sourcepub fn instantiate(
&mut self,
template: &(impl NodeTemplate<N, P> + ?Sized),
) -> (NodeId, Vec<PortId>)
pub fn instantiate( &mut self, template: &(impl NodeTemplate<N, P> + ?Sized), ) -> (NodeId, Vec<PortId>)
Stamps one instance of template into the graph: adds the node, then
its ports in pinout order.
Returns the new node and its ports, where ports[i] corresponds to
template.port_data()[i] — so a GUI symbol or a netlister can address
pins positionally. (The same ordering is also recoverable later via
Graph::ports, which preserves insertion order.)
Accepts &dyn NodeTemplate<N, P> as well as concrete types.
Sourcepub fn instantiate_keyed<K>(
&mut self,
template: &(impl KeyedNodeTemplate<N, P, K> + ?Sized),
) -> (NodeId, HashMap<K, PortId>)
pub fn instantiate_keyed<K>( &mut self, template: &(impl KeyedNodeTemplate<N, P, K> + ?Sized), ) -> (NodeId, HashMap<K, PortId>)
Stamps template into the graph and returns its ports keyed by the
label each pin declared — no positional indices to keep in sync.
This is the fix for the “magic index constant” problem: with
Graph::instantiate you address ports[2] and must keep that 2
aligned with the pinout by hand. Here the key travels with the pin
data in a single declaration, so reordering or inserting pins can’t
break a call site. Use an enum key for compile-time-checked, typo-proof
lookups: pins[&Pin::Output].
Insertion order still follows the declared order, so Graph::ports
remains positional too — you get both views from one source of truth.
Duplicate keys collapse (last wins); for a pinout that’s a bug worth a
debug assertion in your template.
use npe_graph::{Graph, KeyedNodeTemplate};
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
enum Pin { A, B }
struct Resistor;
impl KeyedNodeTemplate<&'static str, &'static str, Pin> for Resistor {
fn node_data(&self) -> &'static str { "R" }
fn keyed_ports(&self) -> Vec<(Pin, &'static str)> {
vec![(Pin::A, "a"), (Pin::B, "b")]
}
}
let mut g: Graph<&str, &str, ()> = Graph::new();
let (n, pins) = g.instantiate_keyed(&Resistor);
assert_eq!(g[pins[&Pin::A]], "a");
assert_eq!(g.ports(n).count(), 2); // positional order preserved tooSource§impl<N, P, E> Graph<N, P, E>
impl<N, P, E> Graph<N, P, E>
Sourcepub fn nets(&self) -> Vec<Net>
pub fn nets(&self) -> Vec<Net>
Computes all nets (connected components over ports, treating every edge as a perfect junction).
O(ports + edges) with union-find; intended to be recomputed on demand after edits rather than maintained incrementally — at the scale this crate targets (hundreds of elements), that’s microseconds.
let mut g: Graph<(), (), ()> = Graph::new();
let n1 = g.add_node(()); let n2 = g.add_node(()); let n3 = g.add_node(());
let a = g.add_port(n1, ()).unwrap();
let b = g.add_port(n2, ()).unwrap();
let c = g.add_port(n3, ()).unwrap(); // floating
g.connect(a, b, ()).unwrap();
let nets = g.nets();
assert_eq!(nets.len(), 2); // {a,b} and {c}
assert!(nets.iter().any(|n| n.ports.len() == 2 && n.edges.len() == 1));
assert!(nets.iter().any(|n| n.ports == vec![c] && n.edges.is_empty()));