hugr_core/hugr/
views.rs

1//! Read-only access into HUGR graphs and subgraphs.
2
3pub mod descendants;
4mod impls;
5pub mod petgraph;
6pub mod render;
7mod root_checked;
8pub mod sibling;
9pub mod sibling_subgraph;
10
11#[cfg(test)]
12mod tests;
13
14use std::borrow::Cow;
15
16pub use self::petgraph::PetgraphWrapper;
17use self::render::RenderConfig;
18pub use descendants::DescendantsGraph;
19pub use root_checked::RootChecked;
20pub use sibling::SiblingGraph;
21pub use sibling_subgraph::SiblingSubgraph;
22
23use itertools::Itertools;
24use portgraph::render::{DotFormat, MermaidFormat};
25use portgraph::{LinkView, PortView};
26
27use super::internal::HugrInternals;
28use super::{
29    Hugr, HugrError, HugrMut, Node, NodeMetadata, NodeMetadataMap, ValidationError, DEFAULT_OPTYPE,
30};
31use crate::extension::ExtensionRegistry;
32use crate::ops::handle::NodeHandle;
33use crate::ops::{OpParent, OpTag, OpTrait, OpType};
34
35use crate::types::{EdgeKind, PolyFuncType, Signature, Type};
36use crate::{Direction, IncomingPort, OutgoingPort, Port};
37
38use itertools::Either;
39
40/// A trait for inspecting HUGRs.
41/// For end users we intend this to be superseded by region-specific APIs.
42pub trait HugrView: HugrInternals {
43    /// Return the root node of this view.
44    #[inline]
45    fn root(&self) -> Self::Node {
46        self.root_node()
47    }
48
49    /// Return the type of the HUGR root node.
50    #[inline]
51    fn root_type(&self) -> &OpType {
52        let node_type = self.get_optype(self.root());
53        // Sadly no way to do this at present
54        // debug_assert!(Self::RootHandle::can_hold(node_type.tag()));
55        node_type
56    }
57
58    /// Returns whether the node exists.
59    fn contains_node(&self, node: Self::Node) -> bool;
60
61    /// Validates that a node is valid in the graph.
62    #[inline]
63    fn valid_node(&self, node: Self::Node) -> bool {
64        self.contains_node(node)
65    }
66
67    /// Validates that a node is a valid root descendant in the graph.
68    ///
69    /// To include the root node use [`HugrView::valid_node`] instead.
70    #[inline]
71    fn valid_non_root(&self, node: Self::Node) -> bool {
72        self.root() != node && self.valid_node(node)
73    }
74
75    /// Returns the parent of a node.
76    #[inline]
77    fn get_parent(&self, node: Self::Node) -> Option<Self::Node> {
78        if !self.valid_non_root(node) {
79            return None;
80        };
81        self.base_hugr()
82            .hierarchy
83            .parent(self.get_pg_index(node))
84            .map(|index| self.get_node(index))
85    }
86
87    /// Returns the operation type of a node.
88    #[inline]
89    fn get_optype(&self, node: Self::Node) -> &OpType {
90        match self.contains_node(node) {
91            true => self.base_hugr().op_types.get(self.get_pg_index(node)),
92            false => &DEFAULT_OPTYPE,
93        }
94    }
95
96    /// Returns the metadata associated with a node.
97    #[inline]
98    fn get_metadata(&self, node: Self::Node, key: impl AsRef<str>) -> Option<&NodeMetadata> {
99        match self.contains_node(node) {
100            true => self.get_node_metadata(node)?.get(key.as_ref()),
101            false => None,
102        }
103    }
104
105    /// Retrieve the complete metadata map for a node.
106    fn get_node_metadata(&self, node: Self::Node) -> Option<&NodeMetadataMap> {
107        if !self.valid_node(node) {
108            return None;
109        }
110        self.base_hugr()
111            .metadata
112            .get(self.get_pg_index(node))
113            .as_ref()
114    }
115
116    /// Returns the number of nodes in the hugr.
117    fn node_count(&self) -> usize;
118
119    /// Returns the number of edges in the hugr.
120    fn edge_count(&self) -> usize;
121
122    /// Iterates over the nodes in the port graph.
123    fn nodes(&self) -> impl Iterator<Item = Self::Node> + Clone;
124
125    /// Iterator over ports of node in a given direction.
126    fn node_ports(&self, node: Self::Node, dir: Direction) -> impl Iterator<Item = Port> + Clone;
127
128    /// Iterator over output ports of node.
129    /// Like [`node_ports`][HugrView::node_ports]`(node, Direction::Outgoing)`
130    /// but preserves knowledge that the ports are [OutgoingPort]s.
131    #[inline]
132    fn node_outputs(&self, node: Self::Node) -> impl Iterator<Item = OutgoingPort> + Clone {
133        self.node_ports(node, Direction::Outgoing)
134            .map(|p| p.as_outgoing().unwrap())
135    }
136
137    /// Iterator over inputs ports of node.
138    /// Like [`node_ports`][HugrView::node_ports]`(node, Direction::Incoming)`
139    /// but preserves knowledge that the ports are [IncomingPort]s.
140    #[inline]
141    fn node_inputs(&self, node: Self::Node) -> impl Iterator<Item = IncomingPort> + Clone {
142        self.node_ports(node, Direction::Incoming)
143            .map(|p| p.as_incoming().unwrap())
144    }
145
146    /// Iterator over both the input and output ports of node.
147    fn all_node_ports(&self, node: Self::Node) -> impl Iterator<Item = Port> + Clone;
148
149    /// Iterator over the nodes and ports connected to a port.
150    fn linked_ports(
151        &self,
152        node: Self::Node,
153        port: impl Into<Port>,
154    ) -> impl Iterator<Item = (Self::Node, Port)> + Clone;
155
156    /// Iterator over all the nodes and ports connected to a node in a given direction.
157    fn all_linked_ports(
158        &self,
159        node: Self::Node,
160        dir: Direction,
161    ) -> Either<
162        impl Iterator<Item = (Self::Node, OutgoingPort)>,
163        impl Iterator<Item = (Self::Node, IncomingPort)>,
164    > {
165        match dir {
166            Direction::Incoming => Either::Left(
167                self.node_inputs(node)
168                    .flat_map(move |port| self.linked_outputs(node, port)),
169            ),
170            Direction::Outgoing => Either::Right(
171                self.node_outputs(node)
172                    .flat_map(move |port| self.linked_inputs(node, port)),
173            ),
174        }
175    }
176
177    /// Iterator over all the nodes and ports connected to a node's inputs.
178    fn all_linked_outputs(
179        &self,
180        node: Self::Node,
181    ) -> impl Iterator<Item = (Self::Node, OutgoingPort)> {
182        self.all_linked_ports(node, Direction::Incoming)
183            .left()
184            .unwrap()
185    }
186
187    /// Iterator over all the nodes and ports connected to a node's outputs.
188    fn all_linked_inputs(
189        &self,
190        node: Self::Node,
191    ) -> impl Iterator<Item = (Self::Node, IncomingPort)> {
192        self.all_linked_ports(node, Direction::Outgoing)
193            .right()
194            .unwrap()
195    }
196
197    /// If there is exactly one port connected to this port, return
198    /// it and its node.
199    fn single_linked_port(
200        &self,
201        node: Self::Node,
202        port: impl Into<Port>,
203    ) -> Option<(Self::Node, Port)> {
204        self.linked_ports(node, port).exactly_one().ok()
205    }
206
207    /// If there is exactly one OutgoingPort connected to this IncomingPort, return
208    /// it and its node.
209    fn single_linked_output(
210        &self,
211        node: Self::Node,
212        port: impl Into<IncomingPort>,
213    ) -> Option<(Self::Node, OutgoingPort)> {
214        self.single_linked_port(node, port.into())
215            .map(|(n, p)| (n, p.as_outgoing().unwrap()))
216    }
217
218    /// If there is exactly one IncomingPort connected to this OutgoingPort, return
219    /// it and its node.
220    fn single_linked_input(
221        &self,
222        node: Self::Node,
223        port: impl Into<OutgoingPort>,
224    ) -> Option<(Self::Node, IncomingPort)> {
225        self.single_linked_port(node, port.into())
226            .map(|(n, p)| (n, p.as_incoming().unwrap()))
227    }
228    /// Iterator over the nodes and output ports connected to a given *input* port.
229    /// Like [`linked_ports`][HugrView::linked_ports] but preserves knowledge
230    /// that the linked ports are [OutgoingPort]s.
231    fn linked_outputs(
232        &self,
233        node: Self::Node,
234        port: impl Into<IncomingPort>,
235    ) -> impl Iterator<Item = (Self::Node, OutgoingPort)> {
236        self.linked_ports(node, port.into())
237            .map(|(n, p)| (n, p.as_outgoing().unwrap()))
238    }
239
240    /// Iterator over the nodes and input ports connected to a given *output* port
241    /// Like [`linked_ports`][HugrView::linked_ports] but preserves knowledge
242    /// that the linked ports are [IncomingPort]s.
243    fn linked_inputs(
244        &self,
245        node: Self::Node,
246        port: impl Into<OutgoingPort>,
247    ) -> impl Iterator<Item = (Self::Node, IncomingPort)> {
248        self.linked_ports(node, port.into())
249            .map(|(n, p)| (n, p.as_incoming().unwrap()))
250    }
251
252    /// Iterator the links between two nodes.
253    fn node_connections(
254        &self,
255        node: Self::Node,
256        other: Self::Node,
257    ) -> impl Iterator<Item = [Port; 2]> + Clone;
258
259    /// Returns whether a port is connected.
260    fn is_linked(&self, node: Self::Node, port: impl Into<Port>) -> bool {
261        self.linked_ports(node, port).next().is_some()
262    }
263
264    /// Number of ports in node for a given direction.
265    fn num_ports(&self, node: Self::Node, dir: Direction) -> usize;
266
267    /// Number of inputs to a node.
268    /// Shorthand for [`num_ports`][HugrView::num_ports]`(node, Direction::Incoming)`.
269    #[inline]
270    fn num_inputs(&self, node: Self::Node) -> usize {
271        self.num_ports(node, Direction::Incoming)
272    }
273
274    /// Number of outputs from a node.
275    /// Shorthand for [`num_ports`][HugrView::num_ports]`(node, Direction::Outgoing)`.
276    #[inline]
277    fn num_outputs(&self, node: Self::Node) -> usize {
278        self.num_ports(node, Direction::Outgoing)
279    }
280
281    /// Return iterator over the direct children of node.
282    fn children(&self, node: Self::Node) -> impl DoubleEndedIterator<Item = Self::Node> + Clone;
283
284    /// Returns the first child of the specified node (if it is a parent).
285    /// Useful because `x.children().next()` leaves x borrowed.
286    fn first_child(&self, node: Self::Node) -> Option<Self::Node> {
287        self.children(node).next()
288    }
289
290    /// Iterates over neighbour nodes in the given direction.
291    /// May contain duplicates if the graph has multiple links between nodes.
292    fn neighbours(
293        &self,
294        node: Self::Node,
295        dir: Direction,
296    ) -> impl Iterator<Item = Self::Node> + Clone;
297
298    /// Iterates over the input neighbours of the `node`.
299    /// Shorthand for [`neighbours`][HugrView::neighbours]`(node, Direction::Incoming)`.
300    #[inline]
301    fn input_neighbours(&self, node: Self::Node) -> impl Iterator<Item = Self::Node> + Clone {
302        self.neighbours(node, Direction::Incoming)
303    }
304
305    /// Iterates over the output neighbours of the `node`.
306    /// Shorthand for [`neighbours`][HugrView::neighbours]`(node, Direction::Outgoing)`.
307    #[inline]
308    fn output_neighbours(&self, node: Self::Node) -> impl Iterator<Item = Self::Node> + Clone {
309        self.neighbours(node, Direction::Outgoing)
310    }
311
312    /// Iterates over the input and output neighbours of the `node` in sequence.
313    fn all_neighbours(&self, node: Self::Node) -> impl Iterator<Item = Self::Node> + Clone;
314
315    /// Get the input and output child nodes of a dataflow parent.
316    /// If the node isn't a dataflow parent, then return None
317    #[inline]
318    fn get_io(&self, node: Self::Node) -> Option<[Self::Node; 2]> {
319        let op = self.get_optype(node);
320        // Nodes outside the view have no children (and a non-DataflowParent OpType::default())
321        if OpTag::DataflowParent.is_superset(op.tag()) {
322            self.children(node).take(2).collect_vec().try_into().ok()
323        } else {
324            None
325        }
326    }
327
328    /// Returns the function type defined by this dataflow HUGR.
329    ///
330    /// If the root of the Hugr is a
331    /// [`DataflowParent`][crate::ops::DataflowParent] operation, report the
332    /// signature corresponding to the input and output node of its sibling
333    /// graph. Otherwise, returns `None`.
334    ///
335    /// In contrast to [`poly_func_type`][HugrView::poly_func_type], this
336    /// method always return a concrete [`Signature`].
337    fn inner_function_type(&self) -> Option<Cow<'_, Signature>> {
338        self.root_type().inner_function_type()
339    }
340
341    /// Returns the function type defined by this HUGR.
342    ///
343    /// For HUGRs with a [`DataflowParent`][crate::ops::DataflowParent] root
344    /// operation, report the signature of the inner dataflow sibling graph.
345    ///
346    /// For HUGRS with a [`FuncDecl`][crate::ops::FuncDecl] or
347    /// [`FuncDefn`][crate::ops::FuncDefn] root operation, report the signature
348    /// of the function.
349    ///
350    /// Otherwise, returns `None`.
351    fn poly_func_type(&self) -> Option<PolyFuncType> {
352        match self.root_type() {
353            OpType::FuncDecl(decl) => Some(decl.signature.clone()),
354            OpType::FuncDefn(defn) => Some(defn.signature.clone()),
355            _ => None,
356        }
357    }
358
359    /// Return a wrapper over the view that can be used in petgraph algorithms.
360    #[inline]
361    fn as_petgraph(&self) -> PetgraphWrapper<'_, Self>
362    where
363        Self: Sized,
364    {
365        PetgraphWrapper { hugr: self }
366    }
367
368    /// Return the mermaid representation of the underlying hierarchical graph.
369    ///
370    /// The hierarchy is represented using subgraphs. Edges are labelled with
371    /// their source and target ports.
372    ///
373    /// For a more detailed representation, use the [`HugrView::dot_string`]
374    /// format instead.
375    fn mermaid_string(&self) -> String {
376        self.mermaid_string_with_config(RenderConfig {
377            node_indices: true,
378            port_offsets_in_edges: true,
379            type_labels_in_edges: true,
380        })
381    }
382
383    /// Return the mermaid representation of the underlying hierarchical graph.
384    ///
385    /// The hierarchy is represented using subgraphs. Edges are labelled with
386    /// their source and target ports.
387    ///
388    /// For a more detailed representation, use the [`HugrView::dot_string`]
389    /// format instead.
390    fn mermaid_string_with_config(&self, config: RenderConfig) -> String {
391        let hugr = self.base_hugr();
392        let graph = self.portgraph();
393        graph
394            .mermaid_format()
395            .with_hierarchy(&hugr.hierarchy)
396            .with_node_style(render::node_style(self, config))
397            .with_edge_style(render::edge_style(self, config))
398            .finish()
399    }
400
401    /// Return the graphviz representation of the underlying graph and hierarchy side by side.
402    ///
403    /// For a simpler representation, use the [`HugrView::mermaid_string`] format instead.
404    fn dot_string(&self) -> String
405    where
406        Self: Sized,
407    {
408        let hugr = self.base_hugr();
409        let graph = self.portgraph();
410        let config = RenderConfig::default();
411        graph
412            .dot_format()
413            .with_hierarchy(&hugr.hierarchy)
414            .with_node_style(render::node_style(self, config))
415            .with_port_style(render::port_style(self, config))
416            .with_edge_style(render::edge_style(self, config))
417            .finish()
418    }
419
420    /// If a node has a static input, return the source node.
421    fn static_source(&self, node: Self::Node) -> Option<Self::Node> {
422        self.linked_outputs(node, self.get_optype(node).static_input_port()?)
423            .next()
424            .map(|(n, _)| n)
425    }
426
427    /// If a node has a static output, return the targets.
428    fn static_targets(
429        &self,
430        node: Self::Node,
431    ) -> Option<impl Iterator<Item = (Self::Node, IncomingPort)>> {
432        Some(self.linked_inputs(node, self.get_optype(node).static_output_port()?))
433    }
434
435    /// Get the "signature" (incoming and outgoing types) of a node, non-Value
436    /// kind ports will be missing.
437    fn signature(&self, node: Self::Node) -> Option<Cow<'_, Signature>> {
438        self.get_optype(node).dataflow_signature()
439    }
440
441    /// Iterator over all outgoing ports that have Value type, along
442    /// with corresponding types.
443    fn value_types(&self, node: Self::Node, dir: Direction) -> impl Iterator<Item = (Port, Type)> {
444        let sig = self.signature(node).unwrap_or_default();
445        self.node_ports(node, dir)
446            .flat_map(move |port| sig.port_type(port).map(|typ| (port, typ.clone())))
447    }
448
449    /// Iterator over all incoming ports that have Value type, along
450    /// with corresponding types.
451    fn in_value_types(&self, node: Self::Node) -> impl Iterator<Item = (IncomingPort, Type)> {
452        self.value_types(node, Direction::Incoming)
453            .map(|(p, t)| (p.as_incoming().unwrap(), t))
454    }
455
456    /// Iterator over all incoming ports that have Value type, along
457    /// with corresponding types.
458    fn out_value_types(&self, node: Self::Node) -> impl Iterator<Item = (OutgoingPort, Type)> {
459        self.value_types(node, Direction::Outgoing)
460            .map(|(p, t)| (p.as_outgoing().unwrap(), t))
461    }
462
463    /// Returns the set of extensions used by the HUGR.
464    ///
465    /// This set may contain extensions that are no longer required by the HUGR.
466    fn extensions(&self) -> &ExtensionRegistry {
467        &self.base_hugr().extensions
468    }
469
470    /// Check the validity of the underlying HUGR.
471    ///
472    /// This includes checking consistency of extension requirements between
473    /// connected nodes and between parents and children.
474    /// See [`HugrView::validate_no_extensions`] for a version that doesn't check
475    /// extension requirements.
476    fn validate(&self) -> Result<(), ValidationError> {
477        self.base_hugr().validate()
478    }
479
480    /// Check the validity of the underlying HUGR, but don't check consistency
481    /// of extension requirements between connected nodes or between parents and
482    /// children.
483    ///
484    /// For a more thorough check, use [`HugrView::validate`].
485    fn validate_no_extensions(&self) -> Result<(), ValidationError> {
486        self.base_hugr().validate_no_extensions()
487    }
488}
489
490/// Trait for views that provides a guaranteed bound on the type of the root node.
491pub trait RootTagged: HugrView {
492    /// The kind of handle that can be used to refer to the root node.
493    ///
494    /// The handle is guaranteed to be able to contain the operation returned by
495    /// [`HugrView::root_type`].
496    type RootHandle: NodeHandle;
497}
498
499/// A common trait for views of a HUGR hierarchical subgraph.
500pub trait HierarchyView<'a>: RootTagged + Sized {
501    /// Create a hierarchical view of a HUGR given a root node.
502    ///
503    /// # Errors
504    /// Returns [`HugrError::InvalidTag`] if the root isn't a node of the required [OpTag]
505    fn try_new(
506        hugr: &'a impl HugrView<Node = Self::Node>,
507        root: Self::Node,
508    ) -> Result<Self, HugrError>;
509}
510
511/// A trait for [`HugrView`]s that can be extracted into a valid HUGR containing
512/// only the nodes and edges of the view.
513pub trait ExtractHugr: HugrView + Sized {
514    /// Extracts the view into an owned HUGR, rooted at the view's root node
515    /// and containing only the nodes and edges of the view.
516    fn extract_hugr(self) -> Hugr {
517        let mut hugr = Hugr::default();
518        let old_root = hugr.root();
519        let new_root = hugr.insert_from_view(old_root, &self).new_root;
520        hugr.set_root(new_root);
521        hugr.remove_node(old_root);
522        hugr
523    }
524}
525
526fn check_tag<Required: NodeHandle, N>(
527    hugr: &impl HugrView<Node = N>,
528    node: N,
529) -> Result<(), HugrError> {
530    let actual = hugr.get_optype(node).tag();
531    let required = Required::TAG;
532    if !required.is_superset(actual) {
533        return Err(HugrError::InvalidTag { required, actual });
534    }
535    Ok(())
536}
537
538impl RootTagged for Hugr {
539    type RootHandle = Node;
540}
541
542impl RootTagged for &Hugr {
543    type RootHandle = Node;
544}
545
546impl RootTagged for &mut Hugr {
547    type RootHandle = Node;
548}
549
550// Explicit implementation to avoid cloning the Hugr.
551impl ExtractHugr for Hugr {
552    fn extract_hugr(self) -> Hugr {
553        self
554    }
555}
556
557impl ExtractHugr for &Hugr {
558    fn extract_hugr(self) -> Hugr {
559        self.clone()
560    }
561}
562
563impl ExtractHugr for &mut Hugr {
564    fn extract_hugr(self) -> Hugr {
565        self.clone()
566    }
567}
568
569impl HugrView for Hugr {
570    #[inline]
571    fn contains_node(&self, node: Node) -> bool {
572        self.graph.contains_node(node.pg_index())
573    }
574
575    #[inline]
576    fn node_count(&self) -> usize {
577        self.graph.node_count()
578    }
579
580    #[inline]
581    fn edge_count(&self) -> usize {
582        self.graph.link_count()
583    }
584
585    #[inline]
586    fn nodes(&self) -> impl Iterator<Item = Node> + Clone {
587        self.graph.nodes_iter().map_into()
588    }
589
590    #[inline]
591    fn node_ports(&self, node: Node, dir: Direction) -> impl Iterator<Item = Port> + Clone {
592        self.graph.port_offsets(node.pg_index(), dir).map_into()
593    }
594
595    #[inline]
596    fn all_node_ports(&self, node: Node) -> impl Iterator<Item = Port> + Clone {
597        self.graph.all_port_offsets(node.pg_index()).map_into()
598    }
599
600    #[inline]
601    fn linked_ports(
602        &self,
603        node: Node,
604        port: impl Into<Port>,
605    ) -> impl Iterator<Item = (Node, Port)> + Clone {
606        let port = port.into();
607
608        let port = self
609            .graph
610            .port_index(node.pg_index(), port.pg_offset())
611            .unwrap();
612        self.graph.port_links(port).map(|(_, link)| {
613            let port = link.port();
614            let node = self.graph.port_node(port).unwrap();
615            let offset = self.graph.port_offset(port).unwrap();
616            (node.into(), offset.into())
617        })
618    }
619
620    #[inline]
621    fn node_connections(&self, node: Node, other: Node) -> impl Iterator<Item = [Port; 2]> + Clone {
622        self.graph
623            .get_connections(node.pg_index(), other.pg_index())
624            .map(|(p1, p2)| {
625                [p1, p2].map(|link| self.graph.port_offset(link.port()).unwrap().into())
626            })
627    }
628
629    #[inline]
630    fn num_ports(&self, node: Node, dir: Direction) -> usize {
631        self.graph.num_ports(node.pg_index(), dir)
632    }
633
634    #[inline]
635    fn children(&self, node: Node) -> impl DoubleEndedIterator<Item = Node> + Clone {
636        self.hierarchy.children(node.pg_index()).map_into()
637    }
638
639    #[inline]
640    fn neighbours(&self, node: Node, dir: Direction) -> impl Iterator<Item = Node> + Clone {
641        self.graph.neighbours(node.pg_index(), dir).map_into()
642    }
643
644    #[inline]
645    fn all_neighbours(&self, node: Node) -> impl Iterator<Item = Node> + Clone {
646        self.graph.all_neighbours(node.pg_index()).map_into()
647    }
648}
649
650/// Trait implementing methods on port iterators.
651pub trait PortIterator<P>: Iterator<Item = (Node, P)>
652where
653    P: Into<Port> + Copy,
654    Self: Sized,
655{
656    /// Filter an iterator of node-ports to only dataflow dependency specifying
657    /// ports (Value and StateOrder)
658    fn dataflow_ports_only(
659        self,
660        hugr: &impl HugrView<Node = Node>,
661    ) -> impl Iterator<Item = (Node, P)> {
662        self.filter_edge_kind(
663            |kind| matches!(kind, Some(EdgeKind::Value(..) | EdgeKind::StateOrder)),
664            hugr,
665        )
666    }
667
668    /// Filter an iterator of node-ports based on the port kind.
669    fn filter_edge_kind(
670        self,
671        predicate: impl Fn(Option<EdgeKind>) -> bool,
672        hugr: &impl HugrView<Node = Node>,
673    ) -> impl Iterator<Item = (Node, P)> {
674        self.filter(move |(n, p)| {
675            let kind = hugr.get_optype(*n).port_kind(*p);
676            predicate(kind)
677        })
678    }
679}
680
681impl<I, P> PortIterator<P> for I
682where
683    I: Iterator<Item = (Node, P)>,
684    P: Into<Port> + Copy,
685{
686}