hugr_core/hugr/
views.rs

1//! Read-only access into HUGR graphs and subgraphs.
2
3mod impls;
4mod nodes_iter;
5pub mod petgraph;
6pub mod render;
7mod rerooted;
8mod root_checked;
9pub mod sibling_subgraph;
10
11#[cfg(test)]
12mod tests;
13
14use std::borrow::Cow;
15use std::collections::HashMap;
16
17pub use self::petgraph::PetgraphWrapper;
18#[expect(deprecated)]
19use self::render::{MermaidFormatter, RenderConfig};
20pub use nodes_iter::NodesIter;
21pub use rerooted::Rerooted;
22pub use root_checked::{InvalidSignature, RootCheckable, RootChecked, check_tag};
23pub use sibling_subgraph::SiblingSubgraph;
24
25use itertools::Itertools;
26use portgraph::render::{DotFormat, MermaidFormat};
27use portgraph::{LinkView, PortView};
28
29use super::internal::{HugrInternals, HugrMutInternals};
30use super::validate::ValidationContext;
31use super::{Hugr, HugrMut, Node, NodeMetadata, ValidationError};
32use crate::core::HugrNode;
33use crate::extension::ExtensionRegistry;
34use crate::ops::handle::NodeHandle;
35use crate::ops::{OpParent, OpTag, OpTrait, OpType};
36
37use crate::types::{EdgeKind, PolyFuncType, Signature, Type};
38use crate::{Direction, IncomingPort, OutgoingPort, Port};
39
40use itertools::Either;
41
42/// A trait for inspecting HUGRs.
43/// For end users we intend this to be superseded by region-specific APIs.
44pub trait HugrView: HugrInternals {
45    /// The distinguished node from where operations are applied, commonly
46    /// defining a region of interest.
47    ///
48    /// This node represents the execution entrypoint of the HUGR. When running
49    /// local graph analysis or optimizations, the region defined under this
50    /// node will be used as the starting point.
51    fn entrypoint(&self) -> Self::Node;
52
53    /// Returns the operation type of the entrypoint node.
54    #[inline]
55    fn entrypoint_optype(&self) -> &OpType {
56        self.get_optype(self.entrypoint())
57    }
58
59    /// An operation tag that is guaranteed to represent the
60    /// [`HugrView::entrypoint`] node operation.
61    ///
62    /// The specificity of the tag may vary depending on the HUGR view.
63    /// [`OpTag::Any`] may be returned for any node, but more specific tags may
64    /// be used instead.
65    ///
66    /// The tag returned may vary if the entrypoint node's operation is modified,
67    /// or if the entrypoint node is replaced with another node.
68    #[inline]
69    fn entrypoint_tag(&self) -> OpTag {
70        self.entrypoint_optype().tag()
71    }
72
73    /// Returns a non-mutable view of the HUGR with a different entrypoint.
74    ///
75    /// For a mutable view, use [`HugrMut::with_entrypoint_mut`] instead.
76    ///
77    /// # Panics
78    ///
79    /// Panics if the entrypoint node is not valid in the HUGR.
80    fn with_entrypoint(&self, entrypoint: Self::Node) -> Rerooted<&Self>
81    where
82        Self: Sized,
83    {
84        Rerooted::new(self, entrypoint)
85    }
86
87    /// A pointer to the module region defined at the root of the HUGR.
88    ///
89    /// This node is the root node of the node hierarchy. It is the ancestor of
90    /// all other nodes in the HUGR.
91    ///
92    /// Operations applied to a hugr normally start at the
93    /// [`HugrView::entrypoint`] instead.
94    fn module_root(&self) -> Self::Node;
95
96    /// Returns `true` if the node exists in the HUGR.
97    fn contains_node(&self, node: Self::Node) -> bool;
98
99    /// Returns the parent of a node.
100    fn get_parent(&self, node: Self::Node) -> Option<Self::Node>;
101
102    /// Returns the metadata associated with a node.
103    #[inline]
104    fn get_metadata(&self, node: Self::Node, key: impl AsRef<str>) -> Option<&NodeMetadata> {
105        if self.contains_node(node) {
106            self.node_metadata_map(node).get(key.as_ref())
107        } else {
108            None
109        }
110    }
111
112    /// Returns the operation type of a node.
113    ///
114    /// # Panics
115    ///
116    /// If the node is not in the graph.
117    fn get_optype(&self, node: Self::Node) -> &OpType;
118
119    /// Returns the number of nodes in the HUGR.
120    fn num_nodes(&self) -> usize;
121
122    /// Returns the number of edges in the HUGR.
123    fn num_edges(&self) -> usize;
124
125    /// Number of ports in node for a given direction.
126    fn num_ports(&self, node: Self::Node, dir: Direction) -> usize;
127
128    /// Number of inputs to a node.
129    /// Shorthand for [`num_ports`][HugrView::num_ports]`(node, Direction::Incoming)`.
130    #[inline]
131    fn num_inputs(&self, node: Self::Node) -> usize {
132        self.num_ports(node, Direction::Incoming)
133    }
134
135    /// Number of outputs from a node.
136    /// Shorthand for [`num_ports`][HugrView::num_ports]`(node, Direction::Outgoing)`.
137    #[inline]
138    fn num_outputs(&self, node: Self::Node) -> usize {
139        self.num_ports(node, Direction::Outgoing)
140    }
141
142    /// Iterates over the all the nodes in the HUGR.
143    ///
144    /// This iterator returns every node in the HUGR. In most cases, you will
145    /// want to use [`HugrView::entry_descendants`] instead to get the nodes
146    /// that are reachable from the entrypoint.
147    ///
148    /// See also [`HugrView::descendants`] and [`HugrView::children`] for more
149    /// general iterators.
150    fn nodes(&self) -> impl Iterator<Item = Self::Node> + Clone;
151
152    /// Iterator over ports of node in a given direction.
153    fn node_ports(&self, node: Self::Node, dir: Direction) -> impl Iterator<Item = Port> + Clone;
154
155    /// Iterator over output ports of node.
156    /// Like [`node_ports`][HugrView::node_ports]`(node, Direction::Outgoing)`
157    /// but preserves knowledge that the ports are [`OutgoingPort`]s.
158    #[inline]
159    fn node_outputs(&self, node: Self::Node) -> impl Iterator<Item = OutgoingPort> + Clone {
160        self.node_ports(node, Direction::Outgoing)
161            .map(|p| p.as_outgoing().unwrap())
162    }
163
164    /// Iterator over inputs ports of node.
165    /// Like [`node_ports`][HugrView::node_ports]`(node, Direction::Incoming)`
166    /// but preserves knowledge that the ports are [`IncomingPort`]s.
167    #[inline]
168    fn node_inputs(&self, node: Self::Node) -> impl Iterator<Item = IncomingPort> + Clone {
169        self.node_ports(node, Direction::Incoming)
170            .map(|p| p.as_incoming().unwrap())
171    }
172
173    /// Iterator over both the input and output ports of node.
174    fn all_node_ports(&self, node: Self::Node) -> impl Iterator<Item = Port> + Clone;
175
176    /// Iterator over the nodes and ports connected to a port.
177    fn linked_ports(
178        &self,
179        node: Self::Node,
180        port: impl Into<Port>,
181    ) -> impl Iterator<Item = (Self::Node, Port)> + Clone;
182
183    /// Iterator over all the nodes and ports connected to a node in a given direction.
184    fn all_linked_ports(
185        &self,
186        node: Self::Node,
187        dir: Direction,
188    ) -> Either<
189        impl Iterator<Item = (Self::Node, OutgoingPort)>,
190        impl Iterator<Item = (Self::Node, IncomingPort)>,
191    > {
192        match dir {
193            Direction::Incoming => Either::Left(
194                self.node_inputs(node)
195                    .flat_map(move |port| self.linked_outputs(node, port)),
196            ),
197            Direction::Outgoing => Either::Right(
198                self.node_outputs(node)
199                    .flat_map(move |port| self.linked_inputs(node, port)),
200            ),
201        }
202    }
203
204    /// Iterator over all the nodes and ports connected to a node's inputs.
205    fn all_linked_outputs(
206        &self,
207        node: Self::Node,
208    ) -> impl Iterator<Item = (Self::Node, OutgoingPort)> {
209        self.all_linked_ports(node, Direction::Incoming)
210            .left()
211            .unwrap()
212    }
213
214    /// Iterator over all the nodes and ports connected to a node's outputs.
215    fn all_linked_inputs(
216        &self,
217        node: Self::Node,
218    ) -> impl Iterator<Item = (Self::Node, IncomingPort)> {
219        self.all_linked_ports(node, Direction::Outgoing)
220            .right()
221            .unwrap()
222    }
223
224    /// If there is exactly one port connected to this port, return
225    /// it and its node.
226    fn single_linked_port(
227        &self,
228        node: Self::Node,
229        port: impl Into<Port>,
230    ) -> Option<(Self::Node, Port)> {
231        self.linked_ports(node, port).exactly_one().ok()
232    }
233
234    /// If there is exactly one `OutgoingPort` connected to this `IncomingPort`, return
235    /// it and its node.
236    fn single_linked_output(
237        &self,
238        node: Self::Node,
239        port: impl Into<IncomingPort>,
240    ) -> Option<(Self::Node, OutgoingPort)> {
241        self.single_linked_port(node, port.into())
242            .map(|(n, p)| (n, p.as_outgoing().unwrap()))
243    }
244
245    /// If there is exactly one `IncomingPort` connected to this `OutgoingPort`, return
246    /// it and its node.
247    fn single_linked_input(
248        &self,
249        node: Self::Node,
250        port: impl Into<OutgoingPort>,
251    ) -> Option<(Self::Node, IncomingPort)> {
252        self.single_linked_port(node, port.into())
253            .map(|(n, p)| (n, p.as_incoming().unwrap()))
254    }
255    /// Iterator over the nodes and output ports connected to a given *input* port.
256    /// Like [`linked_ports`][HugrView::linked_ports] but preserves knowledge
257    /// that the linked ports are [`OutgoingPort`]s.
258    fn linked_outputs(
259        &self,
260        node: Self::Node,
261        port: impl Into<IncomingPort>,
262    ) -> impl Iterator<Item = (Self::Node, OutgoingPort)> {
263        self.linked_ports(node, port.into())
264            .map(|(n, p)| (n, p.as_outgoing().unwrap()))
265    }
266
267    /// Iterator over the nodes and input ports connected to a given *output* port
268    /// Like [`linked_ports`][HugrView::linked_ports] but preserves knowledge
269    /// that the linked ports are [`IncomingPort`]s.
270    fn linked_inputs(
271        &self,
272        node: Self::Node,
273        port: impl Into<OutgoingPort>,
274    ) -> impl Iterator<Item = (Self::Node, IncomingPort)> {
275        self.linked_ports(node, port.into())
276            .map(|(n, p)| (n, p.as_incoming().unwrap()))
277    }
278
279    /// Iterator the links between two nodes.
280    fn node_connections(
281        &self,
282        node: Self::Node,
283        other: Self::Node,
284    ) -> impl Iterator<Item = [Port; 2]> + Clone;
285
286    /// Returns whether a port is connected.
287    fn is_linked(&self, node: Self::Node, port: impl Into<Port>) -> bool {
288        self.linked_ports(node, port).next().is_some()
289    }
290
291    /// Returns an iterator over the direct children of node.
292    fn children(&self, node: Self::Node) -> impl DoubleEndedIterator<Item = Self::Node> + Clone;
293
294    /// Returns an iterator over all the descendants of a node,
295    /// including the node itself.
296    ///
297    /// Yields the node itself first, followed by its children in breath-first order.
298    fn descendants(&self, node: Self::Node) -> impl Iterator<Item = Self::Node> + Clone;
299
300    /// Returns an iterator over all the descendants of the hugr entrypoint,
301    /// including the node itself.
302    ///
303    /// Yields the node itself first, followed by its children in breath-first order.
304    fn entry_descendants(&self) -> impl Iterator<Item = Self::Node> + Clone {
305        self.descendants(self.entrypoint())
306    }
307
308    /// Returns the first child of the specified node (if it is a parent).
309    /// Useful because `x.children().next()` leaves x borrowed.
310    fn first_child(&self, node: Self::Node) -> Option<Self::Node> {
311        self.children(node).next()
312    }
313
314    /// Iterates over neighbour nodes in the given direction.
315    /// May contain duplicates if the graph has multiple links between nodes.
316    fn neighbours(
317        &self,
318        node: Self::Node,
319        dir: Direction,
320    ) -> impl Iterator<Item = Self::Node> + Clone;
321
322    /// Iterates over the input neighbours of the `node`.
323    /// Shorthand for [`neighbours`][HugrView::neighbours]`(node, Direction::Incoming)`.
324    #[inline]
325    fn input_neighbours(&self, node: Self::Node) -> impl Iterator<Item = Self::Node> + Clone {
326        self.neighbours(node, Direction::Incoming)
327    }
328
329    /// Iterates over the output neighbours of the `node`.
330    /// Shorthand for [`neighbours`][HugrView::neighbours]`(node, Direction::Outgoing)`.
331    #[inline]
332    fn output_neighbours(&self, node: Self::Node) -> impl Iterator<Item = Self::Node> + Clone {
333        self.neighbours(node, Direction::Outgoing)
334    }
335
336    /// Iterates over the input and output neighbours of the `node` in sequence.
337    fn all_neighbours(&self, node: Self::Node) -> impl Iterator<Item = Self::Node> + Clone;
338
339    /// Get the input and output child nodes of a dataflow parent.
340    /// If the node isn't a dataflow parent, then return None
341    #[inline]
342    fn get_io(&self, node: Self::Node) -> Option<[Self::Node; 2]> {
343        let op = self.get_optype(node);
344        // Nodes outside the view have no children (and a non-DataflowParent OpType::default())
345        if OpTag::DataflowParent.is_superset(op.tag()) {
346            self.children(node).take(2).collect_vec().try_into().ok()
347        } else {
348            None
349        }
350    }
351
352    /// Returns the function type defined by this dataflow HUGR.
353    ///
354    /// If the root of the Hugr is a
355    /// [`DataflowParent`][crate::ops::DataflowParent] operation, report the
356    /// signature corresponding to the input and output node of its sibling
357    /// graph. Otherwise, returns `None`.
358    ///
359    /// In contrast to [`poly_func_type`][HugrView::poly_func_type], this
360    /// method always return a concrete [`Signature`].
361    fn inner_function_type(&self) -> Option<Cow<'_, Signature>> {
362        self.entrypoint_optype().inner_function_type()
363    }
364
365    /// Returns the function type defined by this HUGR, i.e. `Some` iff the root is
366    /// a [`FuncDecl`][crate::ops::FuncDecl] or [`FuncDefn`][crate::ops::FuncDefn].
367    fn poly_func_type(&self) -> Option<PolyFuncType> {
368        match self.entrypoint_optype() {
369            OpType::FuncDecl(decl) => Some(decl.signature().clone()),
370            OpType::FuncDefn(defn) => Some(defn.signature().clone()),
371            _ => None,
372        }
373    }
374
375    /// Return a wrapper over the view that can be used in petgraph algorithms.
376    #[inline]
377    fn as_petgraph(&self) -> PetgraphWrapper<'_, Self>
378    where
379        Self: Sized,
380    {
381        PetgraphWrapper { hugr: self }
382    }
383
384    /// Return the mermaid representation of the underlying hierarchical graph.
385    ///
386    /// The hierarchy is represented using subgraphs. Edges are labelled with
387    /// their source and target ports.
388    ///
389    /// For a more detailed representation, use the [`HugrView::dot_string`]
390    /// format instead.
391    fn mermaid_string(&self) -> String {
392        self.mermaid_string_with_formatter(self.mermaid_format())
393    }
394
395    /// Return the mermaid representation of the underlying hierarchical graph.
396    ///
397    /// The hierarchy is represented using subgraphs. Edges are labelled with
398    /// their source and target ports.
399    ///
400    /// For a more detailed representation, use the [`HugrView::dot_string`]
401    /// format instead.
402    #[deprecated(note = "Use `mermaid_format` instead", since = "0.20.2")]
403    #[expect(deprecated)]
404    fn mermaid_string_with_config(&self, config: RenderConfig<Self::Node>) -> String;
405
406    /// Return the mermaid representation of the underlying hierarchical graph
407    /// according to the provided [`MermaidFormatter`] formatting options.
408    ///
409    /// The hierarchy is represented using subgraphs. Edges are labelled with
410    /// their source and target ports.
411    ///
412    /// For a more detailed representation, use the [`HugrView::dot_string`]
413    /// format instead.
414    ///
415    /// ## Deprecation of [`RenderConfig`]
416    /// While the deprecated [HugrView::mermaid_string_with_config] exists, this
417    /// will by default try to convert the formatter options to a [`RenderConfig`],
418    /// but this may panic if the configuration is not supported. Users are
419    /// encouraged to provide an implementation of this method overriding the default
420    /// and no longer rely on [HugrView::mermaid_string_with_config].
421    fn mermaid_string_with_formatter(&self, formatter: MermaidFormatter<Self>) -> String {
422        #[expect(deprecated)]
423        let config = match RenderConfig::try_from(formatter) {
424            Ok(config) => config,
425            Err(e) => {
426                panic!("Unsupported format option: {e}");
427            }
428        };
429        #[expect(deprecated)]
430        self.mermaid_string_with_config(config)
431    }
432
433    /// Construct a mermaid representation of the underlying hierarchical graph.
434    ///
435    /// Options can be set on the returned [`MermaidFormatter`] struct, before
436    /// generating the String with [`MermaidFormatter::finish`].
437    ///
438    /// The hierarchy is represented using subgraphs. Edges are labelled with
439    /// their source and target ports.
440    ///
441    /// For a more detailed representation, use the [`HugrView::dot_string`]
442    /// format instead.
443    fn mermaid_format(&self) -> MermaidFormatter<'_, Self> {
444        MermaidFormatter::new(self).with_entrypoint(self.entrypoint())
445    }
446
447    /// Return the graphviz representation of the underlying graph and hierarchy side by side.
448    ///
449    /// For a simpler representation, use the [`HugrView::mermaid_string`] format instead.
450    fn dot_string(&self) -> String
451    where
452        Self: Sized;
453
454    /// If a node has a static input, return the source node.
455    fn static_source(&self, node: Self::Node) -> Option<Self::Node> {
456        self.linked_outputs(node, self.get_optype(node).static_input_port()?)
457            .next()
458            .map(|(n, _)| n)
459    }
460
461    /// If a node has a static output, return the targets.
462    fn static_targets(
463        &self,
464        node: Self::Node,
465    ) -> Option<impl Iterator<Item = (Self::Node, IncomingPort)>> {
466        Some(self.linked_inputs(node, self.get_optype(node).static_output_port()?))
467    }
468
469    /// Get the "signature" (incoming and outgoing types) of a node, non-Value
470    /// kind ports will be missing.
471    fn signature(&self, node: Self::Node) -> Option<Cow<'_, Signature>> {
472        self.get_optype(node).dataflow_signature()
473    }
474
475    /// Iterator over all outgoing ports that have Value type, along
476    /// with corresponding types.
477    fn value_types(&self, node: Self::Node, dir: Direction) -> impl Iterator<Item = (Port, Type)> {
478        let sig = self.signature(node).unwrap_or_default();
479        self.node_ports(node, dir)
480            .filter_map(move |port| sig.port_type(port).map(|typ| (port, typ.clone())))
481    }
482
483    /// Iterator over all incoming ports that have Value type, along
484    /// with corresponding types.
485    fn in_value_types(&self, node: Self::Node) -> impl Iterator<Item = (IncomingPort, Type)> {
486        self.value_types(node, Direction::Incoming)
487            .map(|(p, t)| (p.as_incoming().unwrap(), t))
488    }
489
490    /// Iterator over all outgoing ports that have Value type, along
491    /// with corresponding types.
492    fn out_value_types(&self, node: Self::Node) -> impl Iterator<Item = (OutgoingPort, Type)> {
493        self.value_types(node, Direction::Outgoing)
494            .map(|(p, t)| (p.as_outgoing().unwrap(), t))
495    }
496
497    /// Returns the set of extensions used by the HUGR.
498    ///
499    /// This set contains all extensions required to define the operations and
500    /// types in the HUGR.
501    fn extensions(&self) -> &ExtensionRegistry;
502
503    /// Check the validity of the underlying HUGR.
504    fn validate(&self) -> Result<(), ValidationError<Self::Node>>
505    where
506        Self: Sized,
507    {
508        let mut validator = ValidationContext::new(self);
509        validator.validate()
510    }
511
512    /// Extracts a HUGR containing the parent node and all its descendants.
513    ///
514    /// Returns a new HUGR and a map from the nodes in the source HUGR to the
515    /// nodes in the extracted HUGR. The new HUGR entrypoint corresponds to the
516    /// extracted `parent` node.
517    ///
518    /// Edges that connected to nodes outside the parent node are not
519    /// included in the new HUGR.
520    ///
521    /// If the parent is not a module, the returned HUGR will contain some
522    /// additional nodes to contain the new entrypoint. E.g. if the optype must
523    /// be contained in a dataflow region, a module with a function definition
524    /// will be created to contain it.
525    fn extract_hugr(
526        &self,
527        parent: Self::Node,
528    ) -> (Hugr, impl ExtractionResult<Self::Node> + 'static);
529}
530
531/// Records the result of extracting a Hugr via [`HugrView::extract_hugr`].
532///
533/// Contains a map from the nodes in the source HUGR to the nodes in the extracted
534/// HUGR, using their respective `Node` types.
535pub trait ExtractionResult<SourceN> {
536    /// Returns the node in the extracted HUGR that corresponds to the given
537    /// node in the source HUGR.
538    ///
539    /// If the source node was not a descendant of the entrypoint, the result
540    /// is undefined.
541    fn extracted_node(&self, node: SourceN) -> Node;
542}
543
544/// A node map that defaults to the identity function if the node is not found.
545struct DefaultNodeMap(HashMap<Node, Node>);
546
547impl ExtractionResult<Node> for DefaultNodeMap {
548    #[inline]
549    fn extracted_node(&self, node: Node) -> Node {
550        self.0.get(&node).copied().unwrap_or(node)
551    }
552}
553
554impl<S: HugrNode> ExtractionResult<S> for HashMap<S, Node> {
555    #[inline]
556    fn extracted_node(&self, node: S) -> Node {
557        self[&node]
558    }
559}
560
561impl HugrView for Hugr {
562    #[inline]
563    fn entrypoint(&self) -> Self::Node {
564        self.entrypoint.into()
565    }
566
567    #[inline]
568    fn module_root(&self) -> Self::Node {
569        let node: Self::Node = self.module_root.into();
570        let handle = node.try_cast();
571        debug_assert!(
572            handle.is_some(),
573            "The root node in a HUGR must be a module."
574        );
575        handle.unwrap()
576    }
577
578    #[inline]
579    fn contains_node(&self, node: Self::Node) -> bool {
580        self.graph.contains_node(node.into_portgraph())
581    }
582
583    #[inline]
584    fn get_parent(&self, node: Self::Node) -> Option<Self::Node> {
585        if !check_valid_non_root(self, node) {
586            return None;
587        }
588        self.hierarchy.parent(node.into_portgraph()).map(Into::into)
589    }
590
591    #[inline]
592    fn get_optype(&self, node: Node) -> &OpType {
593        panic_invalid_node(self, node);
594        self.op_types.get(node.into_portgraph())
595    }
596
597    #[inline]
598    fn num_nodes(&self) -> usize {
599        self.graph.node_count()
600    }
601
602    #[inline]
603    fn num_edges(&self) -> usize {
604        self.graph.link_count()
605    }
606
607    #[inline]
608    fn num_ports(&self, node: Self::Node, dir: Direction) -> usize {
609        self.graph.num_ports(node.into_portgraph(), dir)
610    }
611
612    #[inline]
613    fn nodes(&self) -> impl Iterator<Item = Node> + Clone {
614        self.graph.nodes_iter().map_into()
615    }
616
617    #[inline]
618    fn node_ports(&self, node: Node, dir: Direction) -> impl Iterator<Item = Port> + Clone {
619        self.graph
620            .port_offsets(node.into_portgraph(), dir)
621            .map_into()
622    }
623
624    #[inline]
625    fn all_node_ports(&self, node: Node) -> impl Iterator<Item = Port> + Clone {
626        self.graph
627            .all_port_offsets(node.into_portgraph())
628            .map_into()
629    }
630
631    #[inline]
632    fn linked_ports(
633        &self,
634        node: Node,
635        port: impl Into<Port>,
636    ) -> impl Iterator<Item = (Node, Port)> + Clone {
637        let port = port.into();
638
639        let port = self
640            .graph
641            .port_index(node.into_portgraph(), port.pg_offset())
642            .unwrap();
643        self.graph.port_links(port).map(|(_, link)| {
644            let port = link.port();
645            let node = self.graph.port_node(port).unwrap();
646            let offset = self.graph.port_offset(port).unwrap();
647            (node.into(), offset.into())
648        })
649    }
650
651    #[inline]
652    fn node_connections(&self, node: Node, other: Node) -> impl Iterator<Item = [Port; 2]> + Clone {
653        self.graph
654            .get_connections(node.into_portgraph(), other.into_portgraph())
655            .map(|(p1, p2)| {
656                [p1, p2].map(|link| self.graph.port_offset(link.port()).unwrap().into())
657            })
658    }
659
660    #[inline]
661    fn children(&self, node: Self::Node) -> impl DoubleEndedIterator<Item = Self::Node> + Clone {
662        self.hierarchy.children(node.into_portgraph()).map_into()
663    }
664
665    #[inline]
666    fn descendants(&self, node: Self::Node) -> impl Iterator<Item = Self::Node> + Clone {
667        self.hierarchy.descendants(node.into_portgraph()).map_into()
668    }
669
670    #[inline]
671    fn neighbours(&self, node: Node, dir: Direction) -> impl Iterator<Item = Node> + Clone {
672        self.graph.neighbours(node.into_portgraph(), dir).map_into()
673    }
674
675    #[inline]
676    fn all_neighbours(&self, node: Node) -> impl Iterator<Item = Node> + Clone {
677        self.graph.all_neighbours(node.into_portgraph()).map_into()
678    }
679
680    #[expect(deprecated)]
681    fn mermaid_string_with_config(&self, config: RenderConfig) -> String {
682        self.mermaid_string_with_formatter(MermaidFormatter::from_render_config(config, self))
683    }
684
685    fn mermaid_string_with_formatter(&self, formatter: MermaidFormatter<Self>) -> String {
686        self.graph
687            .mermaid_format()
688            .with_hierarchy(&self.hierarchy)
689            .with_node_style(render::node_style(self, formatter.clone()))
690            .with_edge_style(render::edge_style(self, formatter))
691            .finish()
692    }
693
694    fn dot_string(&self) -> String
695    where
696        Self: Sized,
697    {
698        let formatter = MermaidFormatter::new(self).with_entrypoint(self.entrypoint());
699        self.graph
700            .dot_format()
701            .with_hierarchy(&self.hierarchy)
702            .with_node_style(render::node_style(self, formatter.clone()))
703            .with_port_style(render::port_style(self))
704            .with_edge_style(render::edge_style(self, formatter))
705            .finish()
706    }
707
708    #[inline]
709    fn extensions(&self) -> &ExtensionRegistry {
710        &self.extensions
711    }
712
713    #[inline]
714    fn extract_hugr(&self, target: Node) -> (Hugr, impl ExtractionResult<Node> + 'static) {
715        // Shortcircuit if the extracted HUGR is the same as the original
716        if target == self.module_root().node() {
717            return (self.clone(), DefaultNodeMap(HashMap::new()));
718        }
719
720        // Initialize a new HUGR with the desired entrypoint operation.
721        // If we cannot create a new hugr with the parent's optype (e.g. if it's a `BasicBlock`),
722        // find the first ancestor that can be extracted and use that instead.
723        //
724        // The final entrypoint will be set to the original `parent`.
725        let mut parent = target;
726        let mut extracted = loop {
727            let parent_op = self.get_optype(parent).clone();
728            if let Ok(hugr) = Hugr::new_with_entrypoint(parent_op) {
729                break hugr;
730            }
731            // If the operation is not extractable, try the parent.
732            // This loop always terminates, since at least the module root is extractable.
733            parent = self
734                .get_parent(parent)
735                .expect("The module root is always extractable");
736        };
737
738        // The entrypoint and its parent in the newly created HUGR.
739        // These will be replaced with nodes from the original HUGR.
740        let old_entrypoint = extracted.entrypoint();
741        let old_parent = extracted.get_parent(old_entrypoint);
742
743        let inserted = extracted.insert_from_view(old_entrypoint, &self.with_entrypoint(parent));
744        let new_entrypoint = inserted.inserted_entrypoint;
745
746        match old_parent {
747            Some(old_parent) => {
748                // Depending on the entrypoint operation, the old entrypoint may
749                // be connected to other nodes (dataflow region input/outputs).
750                let old_ins = extracted
751                    .node_inputs(old_entrypoint)
752                    .flat_map(|inp| {
753                        extracted
754                            .linked_outputs(old_entrypoint, inp)
755                            .map(move |link| (inp, link))
756                    })
757                    .collect_vec();
758                let old_outs = extracted
759                    .node_outputs(old_entrypoint)
760                    .flat_map(|out| {
761                        extracted
762                            .linked_inputs(old_entrypoint, out)
763                            .map(move |link| (out, link))
764                    })
765                    .collect_vec();
766                // Replace the node
767                extracted.set_entrypoint(inserted.node_map[&target]);
768                extracted.remove_node(old_entrypoint);
769                extracted.set_parent(new_entrypoint, old_parent);
770                // Reconnect the inputs and outputs to the new entrypoint
771                for (inp, (neigh, neigh_out)) in old_ins {
772                    extracted.connect(neigh, neigh_out, new_entrypoint, inp);
773                }
774                for (out, (neigh, neigh_in)) in old_outs {
775                    extracted.connect(new_entrypoint, out, neigh, neigh_in);
776                }
777            }
778            // The entrypoint a module op
779            None => {
780                extracted.set_entrypoint(inserted.node_map[&target]);
781                extracted.set_module_root(new_entrypoint);
782                extracted.remove_node(old_entrypoint);
783            }
784        }
785        (extracted, DefaultNodeMap(inserted.node_map))
786    }
787}
788
789/// Trait implementing methods on port iterators.
790pub trait PortIterator<P>: Iterator<Item = (Node, P)>
791where
792    P: Into<Port> + Copy,
793    Self: Sized,
794{
795    /// Filter an iterator of node-ports to only dataflow dependency specifying
796    /// ports (Value and `StateOrder`)
797    fn dataflow_ports_only(
798        self,
799        hugr: &impl HugrView<Node = Node>,
800    ) -> impl Iterator<Item = (Node, P)> {
801        self.filter_edge_kind(
802            |kind| matches!(kind, Some(EdgeKind::Value(..) | EdgeKind::StateOrder)),
803            hugr,
804        )
805    }
806
807    /// Filter an iterator of node-ports based on the port kind.
808    fn filter_edge_kind(
809        self,
810        predicate: impl Fn(Option<EdgeKind>) -> bool,
811        hugr: &impl HugrView<Node = Node>,
812    ) -> impl Iterator<Item = (Node, P)> {
813        self.filter(move |(n, p)| {
814            let kind = HugrView::get_optype(hugr, *n).port_kind(*p);
815            predicate(kind)
816        })
817    }
818}
819
820impl<I, P> PortIterator<P> for I
821where
822    I: Iterator<Item = (Node, P)>,
823    P: Into<Port> + Copy,
824{
825}
826
827/// Returns `true` if the node exists in the graph and is not the entrypoint node.
828pub(super) fn check_valid_non_entrypoint<H: HugrView + ?Sized>(hugr: &H, node: H::Node) -> bool {
829    hugr.contains_node(node) && node != hugr.entrypoint()
830}
831
832/// Returns `true` if the node exists in the graph and is not the module at the hierarchy root.
833pub(super) fn check_valid_non_root<H: HugrView + ?Sized>(hugr: &H, node: H::Node) -> bool {
834    hugr.contains_node(node) && node != hugr.module_root().node()
835}
836
837/// Panic if [`HugrView::contains_node`] fails.
838#[track_caller]
839pub(super) fn panic_invalid_node<H: HugrView + ?Sized>(hugr: &H, node: H::Node) {
840    assert!(hugr.contains_node(node), "Received an invalid node {node}.",);
841}
842
843/// Panic if [`check_valid_non_entrypoint`] fails.
844#[track_caller]
845pub(super) fn panic_invalid_non_entrypoint<H: HugrView + ?Sized>(hugr: &H, node: H::Node) {
846    assert!(
847        check_valid_non_entrypoint(hugr, node),
848        "Received an invalid non-entrypoint node {node}.",
849    );
850}
851
852/// Panic if [`HugrView::valid_node`] fails.
853#[track_caller]
854pub(super) fn panic_invalid_port(hugr: &Hugr, node: Node, port: impl Into<Port>) {
855    let port = port.into();
856    if hugr
857        .graph
858        .port_index(node.into_portgraph(), port.pg_offset())
859        .is_none()
860    {
861        panic!("Received an invalid {port} for {node} while mutating a HUGR");
862    }
863}