Skip to main content

Graph

Struct Graph 

Source
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) data
  • P — per-port (pin / terminal) data
  • E — 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>

Source

pub fn new() -> Self

Creates an empty graph.

Source

pub fn add_node(&mut self, data: N) -> NodeId

Adds a node (component) carrying data. Starts with no ports.

Source

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.

Source

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).

Source

pub fn connect_with<F>( &mut self, a: PortId, b: PortId, data: E, check: F, ) -> Result<EdgeId, ConnectError>
where F: FnOnce(&P, &P) -> bool,

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)
);
Source

pub fn disconnect(&mut self, edge: EdgeId) -> Option<E>

Removes an edge, returning its data. Stale IDs return None.

Source

pub fn remove_port(&mut self, port: PortId) -> Option<P>

Removes a port, cascading removal of every edge incident to it.

Source

pub fn remove_node(&mut self, node: NodeId) -> Option<N>

Removes a node, cascading removal of all its ports and their edges.

Source

pub fn clear(&mut self)

Removes everything. All previously issued IDs become stale.

Source

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.

Source

pub fn node_mut(&mut self, id: NodeId) -> Option<&mut N>

Mutable node data.

Source

pub fn port(&self, id: PortId) -> Option<&P>

Port data.

Source

pub fn port_mut(&mut self, id: PortId) -> Option<&mut P>

Mutable port data.

Source

pub fn edge(&self, id: EdgeId) -> Option<&E>

Edge data.

Source

pub fn edge_mut(&mut self, id: EdgeId) -> Option<&mut E>

Mutable edge data.

Source

pub fn port_node(&self, port: PortId) -> Option<NodeId>

The node that owns port.

Source

pub fn edge_endpoints(&self, edge: EdgeId) -> Option<(PortId, PortId)>

The two ports an edge connects, in the order passed to connect.

Source

pub fn edge_nodes(&self, edge: EdgeId) -> Option<(NodeId, NodeId)>

The two nodes an edge connects (through its ports).

Source

pub fn opposite(&self, edge: EdgeId, port: PortId) -> Option<PortId>

Given an edge and one of its ports, the port at the other end.

Source

pub fn ports(&self, node: NodeId) -> impl Iterator<Item = PortId> + '_

A node’s ports, in insertion order. Empty iterator for stale IDs.

Source

pub fn find_port<F>(&self, node: NodeId, pred: F) -> Option<PortId>
where F: FnMut(&P) -> bool,

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));
Source

pub fn find_ports<'a, F>( &'a self, node: NodeId, pred: F, ) -> impl Iterator<Item = PortId> + 'a
where F: FnMut(&P) -> bool + 'a,

Every port of node whose data satisfies pred, in pinout order.

Source

pub fn port_edges(&self, port: PortId) -> impl Iterator<Item = EdgeId> + '_

Edges incident to a port.

Source

pub fn node_edges(&self, node: NodeId) -> impl Iterator<Item = EdgeId> + '_

Edges incident to any port of node.

Source

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).

Source

pub fn edges_between( &self, a: PortId, b: PortId, ) -> impl Iterator<Item = EdgeId> + '_

Edges directly between ports a and b (parallel edges possible).

Source

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.

Source

pub fn all_ports(&self) -> impl Iterator<Item = (PortId, &P)> + '_

All ports with their data.

Source

pub fn all_edges(&self) -> impl Iterator<Item = (EdgeId, &E)> + '_

All edges with their data.

Source

pub fn node_count(&self) -> usize

Number of live nodes.

Source

pub fn port_count(&self) -> usize

Number of live ports.

Source

pub fn edge_count(&self) -> usize

Number of live edges.

Source

pub fn contains_node(&self, id: NodeId) -> bool

Is this ID live?

Source

pub fn contains_port(&self, id: PortId) -> bool

Is this ID live?

Source

pub fn contains_edge(&self, id: EdgeId) -> bool

Is this ID live?

Source§

impl<N, P, E> Graph<N, P, E>

Source

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.

Source

pub fn instantiate_keyed<K>( &mut self, template: &(impl KeyedNodeTemplate<N, P, K> + ?Sized), ) -> (NodeId, HashMap<K, PortId>)
where K: Eq + Hash,

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 too
Source§

impl<N, P, E> Graph<N, P, E>

Source

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()));

Trait Implementations§

Source§

impl<N: Clone, P: Clone, E: Clone> Clone for Graph<N, P, E>

Source§

fn clone(&self) -> Graph<N, P, E>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<N: Debug, P: Debug, E: Debug> Debug for Graph<N, P, E>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<N, P, E> Default for Graph<N, P, E>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<N, P, E> Index<EdgeId> for Graph<N, P, E>

Source§

type Output = E

The returned type after indexing.
Source§

fn index(&self, id: EdgeId) -> &E

Performs the indexing (container[index]) operation. Read more
Source§

impl<N, P, E> Index<NodeId> for Graph<N, P, E>

Source§

type Output = N

The returned type after indexing.
Source§

fn index(&self, id: NodeId) -> &N

Performs the indexing (container[index]) operation. Read more
Source§

impl<N, P, E> Index<PortId> for Graph<N, P, E>

Source§

type Output = P

The returned type after indexing.
Source§

fn index(&self, id: PortId) -> &P

Performs the indexing (container[index]) operation. Read more
Source§

impl<N, P, E> IndexMut<EdgeId> for Graph<N, P, E>

Source§

fn index_mut(&mut self, id: EdgeId) -> &mut E

Performs the mutable indexing (container[index]) operation. Read more
Source§

impl<N, P, E> IndexMut<NodeId> for Graph<N, P, E>

Source§

fn index_mut(&mut self, id: NodeId) -> &mut N

Performs the mutable indexing (container[index]) operation. Read more
Source§

impl<N, P, E> IndexMut<PortId> for Graph<N, P, E>

Source§

fn index_mut(&mut self, id: PortId) -> &mut P

Performs the mutable indexing (container[index]) operation. Read more

Auto Trait Implementations§

§

impl<N, P, E> Freeze for Graph<N, P, E>

§

impl<N, P, E> RefUnwindSafe for Graph<N, P, E>

§

impl<N, P, E> Send for Graph<N, P, E>
where N: Send, P: Send, E: Send,

§

impl<N, P, E> Sync for Graph<N, P, E>
where N: Sync, P: Sync, E: Sync,

§

impl<N, P, E> Unpin for Graph<N, P, E>
where N: Unpin, P: Unpin, E: Unpin,

§

impl<N, P, E> UnsafeUnpin for Graph<N, P, E>

§

impl<N, P, E> UnwindSafe for Graph<N, P, E>
where N: UnwindSafe, P: UnwindSafe, E: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.