1pub mod hugrmut;
4
5pub(crate) mod ident;
6pub mod internal;
7pub mod patch;
8pub mod persistent;
9pub mod serialize;
10pub mod validate;
11pub mod views;
12
13use std::collections::VecDeque;
14use std::io;
15use std::iter;
16
17pub(crate) use self::hugrmut::HugrMut;
18pub use self::validate::ValidationError;
19
20pub use ident::{IdentList, InvalidIdentifier};
21use itertools::Itertools;
22pub use patch::{Patch, SimpleReplacement, SimpleReplacementError};
23
24use portgraph::multiportgraph::MultiPortGraph;
25use portgraph::{Hierarchy, PortMut, PortView, UnmanagedDenseMap};
26use thiserror::Error;
27
28pub use self::views::HugrView;
29use crate::core::NodeIndex;
30use crate::envelope::{self, EnvelopeConfig, EnvelopeError};
31use crate::extension::resolution::{
32 ExtensionResolutionError, WeakExtensionRegistry, resolve_op_extensions,
33 resolve_op_types_extensions,
34};
35use crate::extension::{EMPTY_REG, ExtensionRegistry, ExtensionSet};
36use crate::ops::{self, Module, NamedOp, OpName, OpTag, OpTrait};
37pub use crate::ops::{DEFAULT_OPTYPE, OpType};
38use crate::package::Package;
39use crate::{Direction, Node};
40
41#[derive(Clone, Debug, PartialEq)]
43pub struct Hugr {
44 graph: MultiPortGraph,
46
47 hierarchy: Hierarchy,
49
50 module_root: portgraph::NodeIndex,
54
55 entrypoint: portgraph::NodeIndex,
57
58 op_types: UnmanagedDenseMap<portgraph::NodeIndex, OpType>,
60
61 metadata: UnmanagedDenseMap<portgraph::NodeIndex, Option<NodeMetadataMap>>,
63
64 extensions: ExtensionRegistry,
66}
67
68impl Default for Hugr {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl AsRef<Hugr> for Hugr {
75 fn as_ref(&self) -> &Hugr {
76 self
77 }
78}
79
80impl AsMut<Hugr> for Hugr {
81 fn as_mut(&mut self) -> &mut Hugr {
82 self
83 }
84}
85
86pub type NodeMetadata = serde_json::Value;
90
91pub type NodeMetadataMap = serde_json::Map<String, NodeMetadata>;
93
94impl Hugr {
96 #[must_use]
98 pub fn new() -> Self {
99 make_module_hugr(Module::new().into(), 0, 0).unwrap()
100 }
101
102 pub fn new_with_entrypoint(entrypoint_op: impl Into<OpType>) -> Result<Self, HugrError> {
116 Self::with_capacity(entrypoint_op, 0, 0)
117 }
118
119 pub fn with_capacity(
133 entrypoint_op: impl Into<OpType>,
134 nodes: usize,
135 ports: usize,
136 ) -> Result<Self, HugrError> {
137 let entrypoint_op: OpType = entrypoint_op.into();
138 let op_name = entrypoint_op.name();
139 make_module_hugr(entrypoint_op, nodes, ports)
140 .ok_or(HugrError::UnsupportedEntrypoint { op: op_name })
141 }
142
143 pub fn reserve(&mut self, nodes: usize, links: usize) {
149 let ports = links * 2;
150 self.graph.reserve(nodes, ports);
151 }
152
153 pub fn load(
161 reader: impl io::BufRead,
162 extensions: Option<&ExtensionRegistry>,
163 ) -> Result<Self, EnvelopeError> {
164 let pkg = Package::load(reader, extensions)?;
165 match pkg.modules.into_iter().exactly_one() {
166 Ok(hugr) => Ok(hugr),
167 Err(e) => Err(EnvelopeError::ExpectedSingleHugr { count: e.count() }),
168 }
169 }
170
171 pub fn load_str(
182 envelope: impl AsRef<str>,
183 extensions: Option<&ExtensionRegistry>,
184 ) -> Result<Self, EnvelopeError> {
185 Self::load(envelope.as_ref().as_bytes(), extensions)
186 }
187
188 pub fn store(
195 &self,
196 writer: impl io::Write,
197 config: EnvelopeConfig,
198 ) -> Result<(), EnvelopeError> {
199 self.store_with_exts(writer, config, &EMPTY_REG)
200 }
201
202 pub fn store_with_exts(
208 &self,
209 writer: impl io::Write,
210 config: EnvelopeConfig,
211 extensions: &ExtensionRegistry,
212 ) -> Result<(), EnvelopeError> {
213 envelope::write_envelope_impl(writer, [self], extensions, config)
214 }
215
216 pub fn store_str(&self, config: EnvelopeConfig) -> Result<String, EnvelopeError> {
227 self.store_str_with_exts(config, &EMPTY_REG)
228 }
229
230 pub fn store_str_with_exts(
240 &self,
241 config: EnvelopeConfig,
242 extensions: &ExtensionRegistry,
243 ) -> Result<String, EnvelopeError> {
244 if !config.format.ascii_printable() {
245 return Err(EnvelopeError::NonASCIIFormat {
246 format: config.format,
247 });
248 }
249
250 let mut buf = Vec::new();
251 self.store_with_exts(&mut buf, config, extensions)?;
252 Ok(String::from_utf8(buf).expect("Envelope is valid utf8"))
253 }
254
255 pub fn resolve_extension_defs(
283 &mut self,
284 extensions: &ExtensionRegistry,
285 ) -> Result<(), ExtensionResolutionError> {
286 let mut used_extensions = ExtensionRegistry::default();
287
288 let weak_extensions: WeakExtensionRegistry = extensions.into();
299 for n in 0..self.graph.node_capacity() {
300 let pg_node = portgraph::NodeIndex::new(n);
301 let node: Node = pg_node.into();
302 if !self.contains_node(node) {
303 continue;
304 }
305
306 let op = &mut self.op_types[pg_node];
307
308 if let Some(extension) = resolve_op_extensions(node, op, extensions)? {
309 used_extensions.register_updated_ref(extension);
310 }
311 used_extensions.extend(
312 resolve_op_types_extensions(Some(node), op, &weak_extensions)?.map(|weak| {
313 weak.upgrade()
314 .expect("Extension comes from a valid registry")
315 }),
316 );
317 }
318
319 self.extensions = used_extensions;
320 Ok(())
321 }
322}
323
324impl Hugr {
326 pub(crate) fn add_node(&mut self, nodetype: OpType) -> Node {
328 let node = self
329 .graph
330 .add_node(nodetype.input_count(), nodetype.output_count());
331 self.op_types[node] = nodetype;
332 node.into()
333 }
334
335 fn canonical_order(&self, root: Node) -> impl Iterator<Item = Node> + '_ {
343 let mut queue = VecDeque::from([root]);
345 iter::from_fn(move || {
346 let node = queue.pop_front()?;
347 for child in self.children(node) {
348 queue.push_back(child);
349 }
350 Some(node)
351 })
352 }
353
354 pub fn canonicalize_nodes(&mut self, mut rekey: impl FnMut(Node, Node)) {
362 let ordered = {
364 let mut v = Vec::with_capacity(self.num_nodes());
365 v.extend(self.canonical_order(self.module_root()));
366 v
367 };
368 let mut new_entrypoint = None;
369
370 for position in 0..ordered.len() {
374 let pg_target = portgraph::NodeIndex::new(position);
375 let mut source: Node = ordered[position];
376
377 if source.into_portgraph() == self.entrypoint {
379 let old = new_entrypoint.replace(pg_target);
380 debug_assert!(old.is_none());
381 }
382
383 while position > source.index() {
386 source = ordered[source.index()];
387 }
388
389 let pg_source = source.into_portgraph();
390 if pg_target != pg_source {
391 self.graph.swap_nodes(pg_target, pg_source);
392 self.op_types.swap(pg_target, pg_source);
393 self.hierarchy.swap_nodes(pg_target, pg_source);
394 rekey(source, pg_target.into());
395 }
396 }
397 self.module_root = portgraph::NodeIndex::new(0);
398 self.entrypoint = new_entrypoint.unwrap();
399
400 self.graph.compact_nodes(|_, _| {});
404 }
405}
406
407#[derive(Debug, Clone, PartialEq, Error)]
408#[error(
409 "Parent node {parent} has extensions {parent_extensions} that are too restrictive for child node {child}, they must include child extensions {child_extensions}"
410)]
411pub struct ExtensionError {
413 parent: Node,
414 parent_extensions: ExtensionSet,
415 child: Node,
416 child_extensions: ExtensionSet,
417}
418
419#[derive(Debug, Clone, PartialEq, Eq, Error)]
421#[non_exhaustive]
422pub enum HugrError {
423 #[error("Invalid tag: required a tag in {required} but found {actual}")]
425 #[allow(missing_docs)]
426 InvalidTag { required: OpTag, actual: OpTag },
427 #[error("Invalid port direction {0:?}.")]
429 InvalidPortDirection(Direction),
430 #[error("Cannot initialize a HUGR with entrypoint type {op}")]
432 UnsupportedEntrypoint {
433 op: OpName,
435 },
436}
437
438fn make_module_hugr(root_op: OpType, nodes: usize, ports: usize) -> Option<Hugr> {
454 let mut graph = MultiPortGraph::with_capacity(nodes, ports);
455 let hierarchy = Hierarchy::new();
456 let mut op_types = UnmanagedDenseMap::with_capacity(nodes);
457 let extensions = root_op.used_extensions().unwrap_or_default();
458
459 let tag = root_op.tag();
461 let container_tags = [
462 OpTag::ModuleRoot,
463 OpTag::DataflowParent,
464 OpTag::Cfg,
465 OpTag::Conditional,
466 ];
467 if !container_tags.iter().any(|t| t.is_superset(tag)) {
468 return None;
469 }
470
471 let module = graph.add_node(0, 0);
472 op_types[module] = OpType::Module(ops::Module::new());
473
474 let mut hugr = Hugr {
475 graph,
476 hierarchy,
477 module_root: module,
478 entrypoint: module,
479 op_types,
480 metadata: UnmanagedDenseMap::with_capacity(nodes),
481 extensions,
482 };
483 let module: Node = module.into();
484
485 if root_op.is_module() {
487 }
489 else if OpTag::ModuleOp.is_superset(tag) {
491 let node = hugr.add_node_with_parent(module, root_op);
492 hugr.set_entrypoint(node);
493 }
494 else if OpTag::DataflowChild.is_superset(tag) && !root_op.is_input() && !root_op.is_output() {
497 let signature = root_op
498 .dataflow_signature()
499 .unwrap_or_else(|| panic!("Dataflow child {} without signature", root_op.name()))
500 .into_owned();
501 let dataflow_inputs = signature.input_count();
502 let dataflow_outputs = signature.output_count();
503
504 let func = hugr.add_node_with_parent(module, ops::FuncDefn::new("main", signature.clone()));
505 let inp = hugr.add_node_with_parent(
506 func,
507 ops::Input {
508 types: signature.input.clone(),
509 },
510 );
511 let out = hugr.add_node_with_parent(
512 func,
513 ops::Output {
514 types: signature.output.clone(),
515 },
516 );
517 let entrypoint = hugr.add_node_with_parent(func, root_op);
518
519 for port in 0..dataflow_inputs {
522 hugr.connect(inp, port, entrypoint, port);
523 }
524 for port in 0..dataflow_outputs {
525 hugr.connect(entrypoint, port, out, port);
526 }
527
528 hugr.set_entrypoint(entrypoint);
529 }
530 else {
532 debug_assert!(matches!(
533 root_op,
534 OpType::Input(_)
535 | OpType::Output(_)
536 | OpType::DataflowBlock(_)
537 | OpType::ExitBlock(_)
538 | OpType::Case(_)
539 ));
540 return None;
541 }
542
543 Some(hugr)
544}
545
546#[cfg(test)]
547pub(crate) mod test {
548 use std::{fs::File, io::BufReader};
549
550 use super::*;
551
552 use crate::builder::{Container, Dataflow, DataflowSubContainer, ModuleBuilder};
553 use crate::envelope::{EnvelopeError, PackageEncodingError};
554 use crate::extension::prelude::bool_t;
555 use crate::ops::OpaqueOp;
556 use crate::ops::handle::NodeHandle;
557 use crate::test_file;
558 use crate::types::Signature;
559 use cool_asserts::assert_matches;
560 use itertools::Either;
561 use portgraph::LinkView;
562 use rstest::rstest;
563
564 pub(crate) fn check_hugr_equality(lhs: &Hugr, rhs: &Hugr) {
566 let mut lhs = lhs.clone();
570 lhs.canonicalize_nodes(|_, _| {});
571 let mut rhs = rhs.clone();
572 rhs.canonicalize_nodes(|_, _| {});
573
574 assert_eq!(rhs.module_root(), lhs.module_root());
575 assert_eq!(rhs.entrypoint(), lhs.entrypoint());
576 assert_eq!(rhs.hierarchy, lhs.hierarchy);
577 assert_eq!(rhs.metadata, lhs.metadata);
578
579 for node in rhs.nodes() {
581 let new_op = rhs.get_optype(node);
582 let old_op = lhs.get_optype(node);
583 if !new_op.is_const() {
584 match (new_op, old_op) {
585 (OpType::ExtensionOp(ext), OpType::OpaqueOp(opaque))
586 | (OpType::OpaqueOp(opaque), OpType::ExtensionOp(ext)) => {
587 let ext_opaque: OpaqueOp = ext.clone().into();
588 assert_eq!(ext_opaque, opaque.clone());
589 }
590 _ => assert_eq!(new_op, old_op),
591 }
592 }
593 }
594
595 let new_graph = &rhs.graph;
597 let old_graph = &lhs.graph;
598 assert_eq!(new_graph.node_count(), old_graph.node_count());
599 assert_eq!(new_graph.port_count(), old_graph.port_count());
600 assert_eq!(new_graph.link_count(), old_graph.link_count());
601 for n in old_graph.nodes_iter() {
602 assert_eq!(new_graph.num_inputs(n), old_graph.num_inputs(n));
603 assert_eq!(new_graph.num_outputs(n), old_graph.num_outputs(n));
604 assert_eq!(
605 new_graph.output_neighbours(n).collect_vec(),
606 old_graph.output_neighbours(n).collect_vec()
607 );
608 }
609 }
610
611 #[test]
612 fn impls_send_and_sync() {
613 #[allow(dead_code)]
616 trait Test: Send + Sync {}
617 impl Test for Hugr {}
618 }
619
620 #[test]
621 fn io_node() {
622 use crate::builder::test::simple_dfg_hugr;
623
624 let hugr = simple_dfg_hugr();
625 assert_matches!(hugr.get_io(hugr.entrypoint()), Some(_));
626 }
627
628 #[test]
629 #[cfg_attr(miri, ignore)] fn hugr_validation_0() {
631 let hugr = Hugr::load(
633 BufReader::new(File::open(test_file!("hugr-0.hugr")).unwrap()),
634 None,
635 );
636 assert_matches!(
637 hugr,
638 Err(EnvelopeError::PackageEncoding {
639 source: PackageEncodingError::JsonEncoding(_)
640 })
641 );
642 }
643
644 #[test]
645 #[cfg_attr(miri, ignore)] fn hugr_validation_1() {
647 let hugr = Hugr::load(
649 BufReader::new(File::open(test_file!("hugr-1.hugr")).unwrap()),
650 None,
651 );
652 assert_matches!(&hugr, Ok(_));
653 }
654
655 #[test]
656 #[cfg_attr(miri, ignore)] fn hugr_validation_2() {
658 let hugr = Hugr::load(
660 BufReader::new(File::open(test_file!("hugr-2.hugr")).unwrap()),
661 None,
662 )
663 .unwrap();
664 assert_matches!(hugr.validate(), Err(_));
665 }
666
667 #[test]
668 #[cfg_attr(miri, ignore)] fn hugr_validation_3() {
670 let hugr = Hugr::load(
672 BufReader::new(File::open(test_file!("hugr-3.hugr")).unwrap()),
673 None,
674 );
675 assert_matches!(&hugr, Ok(_));
676 }
677
678 fn hugr_failing_2262() -> Hugr {
679 let sig = Signature::new(vec![bool_t(); 2], bool_t());
680 let mut mb = ModuleBuilder::new();
681 let mut fa = mb.define_function("a", sig.clone()).unwrap();
682 let mut dfg = fa.dfg_builder(sig.clone(), fa.input_wires()).unwrap();
683 let call_op = ops::Call::try_new(sig.clone().into(), []).unwrap();
685 let call = dfg.add_dataflow_op(call_op, dfg.input_wires()).unwrap();
686 let dfg = dfg.finish_with_outputs(call.outputs()).unwrap();
687 fa.finish_with_outputs(dfg.outputs()).unwrap();
688 let fb = mb.define_function("b", sig).unwrap();
689 let [fst, _] = fb.input_wires_arr();
690 let fb = fb.finish_with_outputs([fst]).unwrap();
691 let mut h = mb.hugr().clone();
692
693 h.set_entrypoint(dfg.node()); let static_in = h.get_optype(call.node()).static_input_port().unwrap();
695 let static_out = h.get_optype(fb.node()).static_output_port().unwrap();
696 assert_eq!(h.single_linked_output(call.node(), static_in), None);
697 h.disconnect(call.node(), static_in);
698 h.connect(fb.node(), static_out, call.node(), static_in);
699 h
700 }
701
702 #[rstest]
703 #[cfg_attr(not(miri), case(Either::Left(test_file!("hugr-1.hugr"))))]
705 #[cfg_attr(not(miri), case(Either::Left(test_file!("hugr-3.hugr"))))]
706 #[case(Either::Right(hugr_failing_2262()))]
708 fn canonicalize_entrypoint(#[case] file_or_hugr: Either<&str, Hugr>) {
709 let hugr = match file_or_hugr {
710 Either::Left(file) => {
711 Hugr::load(BufReader::new(File::open(file).unwrap()), None).unwrap()
712 }
713 Either::Right(hugr) => hugr,
714 };
715 hugr.validate().unwrap();
716
717 for n in hugr.nodes() {
718 let mut h2 = hugr.clone();
719 h2.set_entrypoint(n);
720 if h2.validate().is_ok() {
721 h2.canonicalize_nodes(|_, _| {});
722 assert_eq!(hugr.get_optype(n), h2.entrypoint_optype());
723 }
724 }
725 }
726}