hugr_core/hugr/
internal.rs

1//! Internal traits, not exposed in the public `hugr` API.
2
3use std::ops::Range;
4use std::sync::OnceLock;
5
6use itertools::Itertools;
7use portgraph::{LinkMut, LinkView, MultiPortGraph, PortMut, PortOffset, PortView};
8
9use crate::extension::ExtensionRegistry;
10use crate::{Direction, Hugr, Node};
11
12use super::HugrView;
13use super::views::{panic_invalid_node, panic_invalid_non_entrypoint};
14use super::{NodeMetadataMap, OpType};
15use crate::ops::handle::NodeHandle;
16
17/// Trait for accessing the internals of a Hugr(View).
18///
19/// Specifically, this trait provides access to the underlying portgraph
20/// view.
21pub trait HugrInternals {
22    /// The portgraph graph structure returned by [`HugrInternals::region_portgraph`].
23    type RegionPortgraph<'p>: LinkView<LinkEndpoint: Eq> + Clone + 'p
24    where
25        Self: 'p;
26
27    /// The type of nodes in the Hugr.
28    type Node: Copy + Ord + std::fmt::Debug + std::fmt::Display + std::hash::Hash;
29
30    /// A mapping between HUGR nodes and portgraph nodes in the graph returned by
31    /// [`HugrInternals::region_portgraph`].
32    type RegionPortgraphNodes: PortgraphNodeMap<Self::Node>;
33
34    /// Returns a flat portgraph view of a region in the HUGR, and a mapping between
35    /// HUGR nodes and portgraph nodes in the graph.
36    //
37    // NOTE: Ideally here we would just return `Self::RegionPortgraph<'_>`, but
38    // when doing so we are unable to restrict the type to implement petgraph's
39    // traits over references (e.g. `&MyGraph : IntoNodeIdentifiers`, which is
40    // needed if we want to use petgraph's algorithms on the region graph).
41    // This won't be solvable until we do the big petgraph refactor -.-
42    // In the meantime, just wrap the portgraph in a `FlatRegion` as needed.
43    fn region_portgraph(
44        &self,
45        parent: Self::Node,
46    ) -> (
47        portgraph::view::FlatRegion<'_, Self::RegionPortgraph<'_>>,
48        Self::RegionPortgraphNodes,
49    );
50
51    /// Returns a metadata entry associated with a node.
52    ///
53    /// # Panics
54    ///
55    /// If the node is not in the graph.
56    fn node_metadata_map(&self, node: Self::Node) -> &NodeMetadataMap;
57}
58
59/// A map between hugr nodes and portgraph nodes in the graph returned by
60/// [`HugrInternals::region_portgraph`].
61pub trait PortgraphNodeMap<N>: Clone + Sized + std::fmt::Debug {
62    /// Returns the portgraph index of a HUGR node in the associated region
63    /// graph.
64    ///
65    /// If the node is not in the region, the result is undefined.
66    fn to_portgraph(&self, node: N) -> portgraph::NodeIndex;
67
68    /// Returns the HUGR node for a portgraph node in the associated region
69    /// graph.
70    ///
71    /// If the node is not in the region, the result is undefined.
72    #[allow(clippy::wrong_self_convention)]
73    fn from_portgraph(&self, node: portgraph::NodeIndex) -> N;
74}
75
76/// An identity map between HUGR nodes and portgraph nodes.
77#[derive(
78    Copy, Clone, Debug, Default, Eq, PartialEq, Hash, PartialOrd, Ord, derive_more::Display,
79)]
80pub struct DefaultPGNodeMap;
81
82impl PortgraphNodeMap<Node> for DefaultPGNodeMap {
83    #[inline]
84    fn to_portgraph(&self, node: Node) -> portgraph::NodeIndex {
85        node.into_portgraph()
86    }
87
88    #[inline]
89    fn from_portgraph(&self, node: portgraph::NodeIndex) -> Node {
90        node.into()
91    }
92}
93
94impl HugrInternals for Hugr {
95    type RegionPortgraph<'p>
96        = &'p MultiPortGraph
97    where
98        Self: 'p;
99
100    type Node = Node;
101
102    type RegionPortgraphNodes = DefaultPGNodeMap;
103
104    #[inline]
105    fn region_portgraph(
106        &self,
107        parent: Self::Node,
108    ) -> (
109        portgraph::view::FlatRegion<'_, Self::RegionPortgraph<'_>>,
110        Self::RegionPortgraphNodes,
111    ) {
112        let root = parent.into_portgraph();
113        let region =
114            portgraph::view::FlatRegion::new_without_root(&self.graph, &self.hierarchy, root);
115        (region, DefaultPGNodeMap)
116    }
117
118    #[inline]
119    fn node_metadata_map(&self, node: Self::Node) -> &NodeMetadataMap {
120        static EMPTY: OnceLock<NodeMetadataMap> = OnceLock::new();
121        panic_invalid_node(self, node);
122        let map = self.metadata.get(node.into_portgraph()).as_ref();
123        map.unwrap_or(EMPTY.get_or_init(Default::default))
124    }
125}
126
127/// Trait for accessing the mutable internals of a Hugr(Mut).
128///
129/// Specifically, this trait lets you apply arbitrary modifications that may
130/// invalidate the HUGR.
131pub trait HugrMutInternals: HugrView {
132    /// Set the node at the root of the HUGR hierarchy.
133    ///
134    /// Any node not reachable from this root should be manually removed from
135    /// the HUGR.
136    ///
137    /// To set the working entrypoint of the HUGR, use
138    /// [`HugrMut::set_entrypoint`][crate::hugr::HugrMut::set_entrypoint]
139    /// instead.
140    ///
141    /// # Panics
142    ///
143    /// If the node is not in the graph.
144    fn set_module_root(&mut self, root: Self::Node);
145
146    /// Set the number of ports on a node. This may invalidate the node's `PortIndex`.
147    ///
148    /// # Panics
149    ///
150    /// If the node is not in the graph.
151    fn set_num_ports(&mut self, node: Self::Node, incoming: usize, outgoing: usize);
152
153    /// Alter the number of ports on a node and returns a range with the new
154    /// port offsets, if any. This may invalidate the node's `PortIndex`.
155    ///
156    /// The `direction` parameter specifies whether to add ports to the incoming
157    /// or outgoing list.
158    ///
159    /// Returns the range of newly created ports.
160    ///
161    /// # Panics
162    ///
163    /// If the node is not in the graph.
164    fn add_ports(&mut self, node: Self::Node, direction: Direction, amount: isize) -> Range<usize>;
165
166    /// Insert `amount` new ports for a node, starting at `index`.  The
167    /// `direction` parameter specifies whether to add ports to the incoming or
168    /// outgoing list. Links from this node are preserved, even when ports are
169    /// renumbered by the insertion.
170    ///
171    /// Returns the range of newly created ports.
172    /// # Panics
173    ///
174    /// If the node is not in the graph.
175    fn insert_ports(
176        &mut self,
177        node: Self::Node,
178        direction: Direction,
179        index: usize,
180        amount: usize,
181    ) -> Range<usize>;
182
183    /// Sets the parent of a node.
184    ///
185    /// The node becomes the parent's last child.
186    ///
187    /// # Panics
188    ///
189    /// If either the node or the parent is not in the graph.
190    fn set_parent(&mut self, node: Self::Node, parent: Self::Node);
191
192    /// Move a node in the hierarchy to be the subsequent sibling of another
193    /// node.
194    ///
195    /// The sibling node's parent becomes the new node's parent.
196    ///
197    /// The node becomes the parent's last child.
198    ///
199    /// # Panics
200    ///
201    /// If either node is not in the graph, or if it is a root.
202    fn move_after_sibling(&mut self, node: Self::Node, after: Self::Node);
203
204    /// Move a node in the hierarchy to be the prior sibling of another node.
205    ///
206    /// The sibling node's parent becomes the new node's parent.
207    ///
208    /// The node becomes the parent's last child.
209    ///
210    /// # Panics
211    ///
212    /// If either node is not in the graph, or if it is a root.
213    fn move_before_sibling(&mut self, node: Self::Node, before: Self::Node);
214
215    /// Replace the `OpType` at node and return the old `OpType`.
216    /// In general this invalidates the ports, which may need to be resized to
217    /// match the `OpType` signature.
218    ///
219    /// Returns the old `OpType`.
220    ///
221    /// If the module root is set to a non-module operation the hugr will
222    /// become invalid.
223    ///
224    /// # Panics
225    ///
226    /// If the node is not in the graph.
227    fn replace_op(&mut self, node: Self::Node, op: impl Into<OpType>) -> OpType;
228
229    /// Gets a mutable reference to the optype.
230    ///
231    /// Changing this may invalidate the ports, which may need to be resized to
232    /// match the `OpType` signature.
233    ///
234    /// Mutating the root node operation may invalidate the root tag.
235    ///
236    /// Mutating the module root into a non-module operation will invalidate the hugr.
237    ///
238    /// # Panics
239    ///
240    /// If the node is not in the graph.
241    fn optype_mut(&mut self, node: Self::Node) -> &mut OpType;
242
243    /// Returns a metadata entry associated with a node.
244    ///
245    /// # Panics
246    ///
247    /// If the node is not in the graph.
248    fn node_metadata_map_mut(&mut self, node: Self::Node) -> &mut NodeMetadataMap;
249
250    /// Returns a mutable reference to the extension registry for this HUGR.
251    ///
252    /// This set contains all extensions required to define the operations and
253    /// types in the HUGR.
254    fn extensions_mut(&mut self) -> &mut ExtensionRegistry;
255}
256
257/// Impl for non-wrapped Hugrs. Overwrites the recursive default-impls to directly use the hugr.
258impl HugrMutInternals for Hugr {
259    fn set_module_root(&mut self, root: Node) {
260        panic_invalid_node(self, root.node());
261        let root = root.into_portgraph();
262        self.hierarchy.detach(root);
263        self.module_root = root;
264    }
265
266    #[inline]
267    fn set_num_ports(&mut self, node: Node, incoming: usize, outgoing: usize) {
268        panic_invalid_node(self, node);
269        self.graph
270            .set_num_ports(node.into_portgraph(), incoming, outgoing, |_, _| {});
271    }
272
273    fn add_ports(&mut self, node: Node, direction: Direction, amount: isize) -> Range<usize> {
274        panic_invalid_node(self, node);
275        let mut incoming = self.graph.num_inputs(node.into_portgraph());
276        let mut outgoing = self.graph.num_outputs(node.into_portgraph());
277        let increment = |num: &mut usize| {
278            let new = num.saturating_add_signed(amount);
279            let range = *num..new;
280            *num = new;
281            range
282        };
283        let range = match direction {
284            Direction::Incoming => increment(&mut incoming),
285            Direction::Outgoing => increment(&mut outgoing),
286        };
287        self.graph
288            .set_num_ports(node.into_portgraph(), incoming, outgoing, |_, _| {});
289        range
290    }
291
292    fn insert_ports(
293        &mut self,
294        node: Node,
295        direction: Direction,
296        index: usize,
297        amount: usize,
298    ) -> Range<usize> {
299        panic_invalid_node(self, node);
300        let old_num_ports = self.graph.num_ports(node.into_portgraph(), direction);
301
302        self.add_ports(node, direction, amount as isize);
303
304        for swap_from_port in (index..old_num_ports).rev() {
305            let swap_to_port = swap_from_port + amount;
306            let [from_port_index, to_port_index] = [swap_from_port, swap_to_port].map(|p| {
307                self.graph
308                    .port_index(node.into_portgraph(), PortOffset::new(direction, p))
309                    .unwrap()
310            });
311            let linked_ports = self
312                .graph
313                .port_links(from_port_index)
314                .map(|(_, to_subport)| to_subport.port())
315                .collect_vec();
316            self.graph.unlink_port(from_port_index);
317            for linked_port_index in linked_ports {
318                let _ = self
319                    .graph
320                    .link_ports(to_port_index, linked_port_index)
321                    .expect("Ports exist");
322            }
323        }
324        index..index + amount
325    }
326
327    fn set_parent(&mut self, node: Node, parent: Node) {
328        panic_invalid_node(self, parent);
329        panic_invalid_node(self, node);
330        self.hierarchy.detach(node.into_portgraph());
331        self.hierarchy
332            .push_child(node.into_portgraph(), parent.into_portgraph())
333            .expect("Inserting a newly-created node into the hierarchy should never fail.");
334    }
335
336    fn move_after_sibling(&mut self, node: Node, after: Node) {
337        panic_invalid_non_entrypoint(self, node);
338        panic_invalid_non_entrypoint(self, after);
339        self.hierarchy.detach(node.into_portgraph());
340        self.hierarchy
341            .insert_after(node.into_portgraph(), after.into_portgraph())
342            .expect("Inserting a newly-created node into the hierarchy should never fail.");
343    }
344
345    fn move_before_sibling(&mut self, node: Node, before: Node) {
346        panic_invalid_non_entrypoint(self, node);
347        panic_invalid_non_entrypoint(self, before);
348        self.hierarchy.detach(node.into_portgraph());
349        self.hierarchy
350            .insert_before(node.into_portgraph(), before.into_portgraph())
351            .expect("Inserting a newly-created node into the hierarchy should never fail.");
352    }
353
354    fn replace_op(&mut self, node: Node, op: impl Into<OpType>) -> OpType {
355        panic_invalid_node(self, node);
356        std::mem::replace(self.optype_mut(node), op.into())
357    }
358
359    fn optype_mut(&mut self, node: Node) -> &mut OpType {
360        panic_invalid_node(self, node);
361        let node = node.into_portgraph();
362        self.op_types.get_mut(node)
363    }
364
365    fn node_metadata_map_mut(&mut self, node: Self::Node) -> &mut NodeMetadataMap {
366        panic_invalid_node(self, node);
367        self.metadata
368            .get_mut(node.into_portgraph())
369            .get_or_insert_with(Default::default)
370    }
371
372    fn extensions_mut(&mut self) -> &mut ExtensionRegistry {
373        &mut self.extensions
374    }
375}
376
377#[cfg(test)]
378mod test {
379    use crate::{
380        Direction, HugrView as _,
381        builder::{Container, DFGBuilder, Dataflow, DataflowHugr},
382        extension::prelude::Noop,
383        hugr::internal::HugrMutInternals as _,
384        ops::handle::NodeHandle,
385        types::{Signature, Type},
386    };
387
388    #[test]
389    fn insert_ports() {
390        let (nop, mut hugr) = {
391            let mut builder = DFGBuilder::new(Signature::new_endo(Type::UNIT)).unwrap();
392            let [nop_in] = builder.input_wires_arr();
393            let nop = builder
394                .add_dataflow_op(Noop::new(Type::UNIT), [nop_in])
395                .unwrap();
396            builder.add_other_wire(nop.node(), builder.output().node());
397            let [nop_out] = nop.outputs_arr();
398            (
399                nop.node(),
400                builder.finish_hugr_with_outputs([nop_out]).unwrap(),
401            )
402        };
403        let [i, o] = hugr.get_io(hugr.entrypoint()).unwrap();
404        assert_eq!(0..2, hugr.insert_ports(nop, Direction::Incoming, 0, 2));
405        assert_eq!(1..3, hugr.insert_ports(nop, Direction::Outgoing, 1, 2));
406
407        assert_eq!(hugr.single_linked_input(i, 0), Some((nop, 2.into())));
408        assert_eq!(hugr.single_linked_output(o, 0), Some((nop, 0.into())));
409        assert_eq!(hugr.single_linked_output(o, 1), Some((nop, 3.into())));
410    }
411}