Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
1723b48
Add HugrMut::insert_forest / insert_view_forest, implement insert_hug…
acl-cqc Aug 11, 2025
2e66542
insert_subgraph delegates to insert_forest (copying nodes list)
acl-cqc Aug 11, 2025
02fea73
errors; docs
acl-cqc Aug 11, 2025
fdee944
Tests
acl-cqc Aug 12, 2025
0eaf3eb
fix test - edges removed rather than delocalized
acl-cqc Aug 12, 2025
05072b6
Merge remote-tracking branch 'origin/main' into acl/insert_forest
acl-cqc Aug 12, 2025
f55c288
More docs/links
acl-cqc Aug 13, 2025
3621a7f
WIP
acl-cqc Aug 12, 2025
0f3883b
Dedup dfg_calling_defn_decl, start
acl-cqc Aug 12, 2025
865fdb8
Implement LinkHugr, split old insert_link_nodes -> check_directives+l…
acl-cqc Aug 12, 2025
54e87a0
Fix test, and note duplication
acl-cqc Aug 12, 2025
b522ff1
Common up insertion-checking in tests; expand doc
acl-cqc Aug 12, 2025
5774665
fix doclinks
acl-cqc Aug 13, 2025
7c41f73
Error on overlapping Add::replace; handle Add overlapping w/ UseExist…
acl-cqc Aug 13, 2025
e70015f
test multi-replace / replace+add
acl-cqc Aug 13, 2025
7001f9c
fix doc
acl-cqc Aug 13, 2025
698283c
NodeLinkingDirective::replace takes impl IntoIterator
acl-cqc Aug 13, 2025
18e1cf7
Avoid test nondeterminism; clippy
acl-cqc Aug 13, 2025
10778ce
Optimize by turning HashMap into impl IntoIterator, avoid size_hint
acl-cqc Aug 12, 2025
826990d
struct InsertedForest; use InsertForestResult alias more widely
acl-cqc Aug 15, 2025
f9eee44
N -> SN
acl-cqc Aug 15, 2025
caecc43
Make DoubleCopy contain a named field
acl-cqc Aug 15, 2025
bd12196
Revert "Make DoubleCopy contain a named field"
acl-cqc Aug 16, 2025
bdc0ff1
Use rstest::fixture
acl-cqc Aug 16, 2025
58dccdd
test tidy
acl-cqc Aug 16, 2025
b1c0d2a
add benchmark
acl-cqc Aug 17, 2025
88a4987
lint benchmark and add to criterion group
acl-cqc Aug 17, 2025
cb92e0c
comments
acl-cqc Aug 17, 2025
a73ce4f
Better benchmarks via iter_batched
acl-cqc Aug 19, 2025
481648e
Always convert to HashMap; update insert_forest_internal; roots->root…
acl-cqc Aug 19, 2025
9a9c325
Do not require HashMap, so insert_view_forest can avoid building one
acl-cqc Aug 19, 2025
ec01c36
Separate errors: DoubleCopy => DuplicateNode + SubtreeAlreadyCopied
acl-cqc Aug 19, 2025
8426498
docs
acl-cqc Aug 20, 2025
f8bdaa1
Builder docs for #2496
acl-cqc Aug 21, 2025
17a8de4
remove not-yet-existent doclink
acl-cqc Aug 21, 2025
191119f
Improve doc of dfg_calling_defn_decl
acl-cqc Aug 22, 2025
a3341df
Merge branch 'acl/insert_forest' into acl/insert_link_Nodes
acl-cqc Aug 25, 2025
351fa78
Rename Builder add_(hugr_=>)view_with_wires_link_nodes
acl-cqc Aug 25, 2025
871e26e
Rename LinkHugr => HugrLinking
acl-cqc Aug 25, 2025
ccc57ba
Rename insert_(hugr/from_view)_link_nodes => add_(hugr/view)_link_nodes
acl-cqc Aug 25, 2025
f3d0d84
Review suggestions (doc improvements)
acl-cqc Aug 25, 2025
3438e5e
doc: HugrLinking: add _static_ edges between old and inserted nodes
acl-cqc Aug 25, 2025
2a6f33a
Improve docs: module-children, not necessarily funcdefn/decl
acl-cqc Aug 25, 2025
685e19f
insert_view_forest uses iter_batched and recreates host each time
acl-cqc Sep 1, 2025
57e58e2
oops, fix borrow in bench
acl-cqc Sep 1, 2025
6850d9d
Merge remote-tracking branch 'origin/main' into acl/insert_forest
acl-cqc Sep 2, 2025
eac8d74
Merge branch 'acl/insert_forest' into acl/insert_link_nodes
acl-cqc Sep 2, 2025
5979bf6
Add ModuleBuilder::add_{hugr,view}_link_nodes
acl-cqc Sep 2, 2025
1a3e37a
ModuleBuilder::add_(hugr/view)_link_nodes => link_(hugr/view)_by_node
acl-cqc Sep 3, 2025
cc861fa
HugrMut methods are insert_link_(hugr/view)_by_node
acl-cqc Sep 4, 2025
0de1326
Dataflow builder: add_link_(hugr/view)_by_node_with_wires
acl-cqc Sep 4, 2025
97b3bf1
doclinks
acl-cqc Sep 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions hugr-core/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ use thiserror::Error;
use crate::extension::SignatureError;
use crate::extension::simple_op::OpLoadError;
use crate::hugr::ValidationError;
use crate::hugr::linking::NodeLinkingError;
use crate::ops::handle::{BasicBlockID, CfgID, ConditionalID, DfgID, FuncID, TailLoopID};
use crate::ops::{NamedOp, OpType};
use crate::types::Type;
Expand Down Expand Up @@ -177,6 +178,16 @@ pub enum BuildError {
node: Node,
},

/// From [Dataflow::add_link_hugr_by_node_with_wires]
#[error{"In inserting Hugr: {0}"}]
HugrInsertionError(#[from] NodeLinkingError<Node, Node>),

/// From [Dataflow::add_link_view_by_node_with_wires].
/// Note that because the type of node in the [NodeLinkingError] depends
/// upon the view being inserted, we convert the error to a string here.
#[error("In inserting HugrView: {0}")]
HugrViewInsertionError(String),

/// Wire not found in Hugr
#[error("Wire not found in Hugr: {0}.")]
WireNotFound(Wire),
Expand Down Expand Up @@ -352,4 +363,35 @@ pub(crate) mod test {
);
hugr
}

/// Builds a DFG-entrypoint Hugr (no inputs, one bool_t output) containing two calls,
/// to a FuncDefn and a FuncDecl each bool_t->bool_t.
/// Returns the Hugr and both function handles.
#[fixture]
pub(crate) fn dfg_calling_defn_decl() -> (Hugr, FuncID<true>, FuncID<false>) {
let mut dfb = DFGBuilder::new(Signature::new(vec![], bool_t())).unwrap();
let new_defn = {
let mut mb = dfb.module_root_builder();
let fb = mb
.define_function("helper_id", Signature::new_endo(bool_t()))
.unwrap();
let [f_inp] = fb.input_wires_arr();
fb.finish_with_outputs([f_inp]).unwrap()
};
let new_decl = dfb
.module_root_builder()
.declare("helper2", Signature::new_endo(bool_t()).into())
.unwrap();
let cst = dfb.add_load_value(ops::Value::true_val());
let [c1] = dfb
.call(new_defn.handle(), &[], [cst])
.unwrap()
.outputs_arr();
let [c2] = dfb.call(&new_decl, &[], [c1]).unwrap().outputs_arr();
(
dfb.finish_hugr_with_outputs([c2]).unwrap(),
*new_defn.handle(),
new_decl,
)
}
}
79 changes: 58 additions & 21 deletions hugr-core/src/builder/build_traits.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use crate::extension::prelude::MakeTuple;
use crate::hugr::hugrmut::InsertionResult;
use crate::hugr::linking::{HugrLinking, NodeLinkingDirective};
use crate::hugr::views::HugrView;
use crate::hugr::{NodeMetadata, ValidationError};
use crate::ops::{self, OpTag, OpTrait, OpType, Tag, TailLoop};
use crate::utils::collect_array;
use crate::{Extension, IncomingPort, Node, OutgoingPort};

use std::collections::HashMap;
use std::iter;
use std::sync::Arc;

Expand Down Expand Up @@ -99,9 +101,8 @@ pub trait Container {
}

/// Insert a copy of a HUGR as a child of the container.
///
/// Only the portion below the entrypoint will be inserted, with any incoming
/// edges broken.
/// (Only the portion below the entrypoint will be inserted, with any incoming
/// edges broken; see [Dataflow::add_link_view_by_node_with_wires])
fn add_hugr_view<H: HugrView>(&mut self, child: &H) -> InsertionResult<H::Node, Node> {
let parent = self.container_node();
self.hugr_mut().insert_from_view(parent, child)
Expand Down Expand Up @@ -246,22 +247,34 @@ pub trait Dataflow: Container {
region: Node,
input_wires: impl IntoIterator<Item = Wire>,
) -> Result<BuildHandle<DataflowOpID>, BuildError> {
let optype = hugr.get_optype(region).clone();
let num_outputs = optype.value_output_count();
let node = self.add_hugr_region(hugr, region).inserted_entrypoint;

wire_up_inputs(input_wires, node, self).map_err(|error| BuildError::OperationWiring {
op: Box::new(optype),
error,
})?;
wire_ins_return_outs(input_wires, node, self)
}

Ok((node, num_outputs).into())
/// Insert a hugr, adding its entrypoint to the sibling graph and wiring up the
/// `input_wires` to the incoming ports of the resulting root node. `defns` may
/// contain other children of the module root of `hugr`, which will be added to
/// the module root being built.
fn add_link_hugr_by_node_with_wires(
&mut self,
hugr: Hugr,
input_wires: impl IntoIterator<Item = Wire>,
defns: HashMap<Node, NodeLinkingDirective>,
) -> Result<BuildHandle<DataflowOpID>, BuildError> {
let parent = Some(self.container_node());
let ep = hugr.entrypoint();
let node = self
.hugr_mut()
.insert_link_hugr_by_node(parent, hugr, defns)?
.node_map[&ep];
wire_ins_return_outs(input_wires, node, self)
}

/// Copy a hugr-defined op into the sibling graph, wiring up the
/// Copy a hugr's entrypoint-subtree (only) into the sibling graph, wiring up the
/// `input_wires` to the incoming ports of the node that was the entrypoint.
/// (Note, any part of `hugr` outside the entrypoint is not copied;
/// this may lead to new input ports being disconnected.)
/// (Note that any wires from outside the entrypoint-subtree are disconnected in the copy;
/// see [Self::add_link_view_by_node_with_wires] for an alternative.)
///
/// # Errors
///
Expand All @@ -273,15 +286,25 @@ pub trait Dataflow: Container {
input_wires: impl IntoIterator<Item = Wire>,
) -> Result<BuildHandle<DataflowOpID>, BuildError> {
let node = self.add_hugr_view(hugr).inserted_entrypoint;
let optype = hugr.get_optype(hugr.entrypoint()).clone();
let num_outputs = optype.value_output_count();

wire_up_inputs(input_wires, node, self).map_err(|error| BuildError::OperationWiring {
op: Box::new(optype),
error,
})?;
wire_ins_return_outs(input_wires, node, self)
}

Ok((node, num_outputs).into())
/// Copy a Hugr, adding its entrypoint into the sibling graph and wiring up the
/// `input_wires` to the incoming ports. `defns` may contain other children of
/// the module root of `hugr`, which will be added to the module root being built.
fn add_link_view_by_node_with_wires<H: HugrView>(
&mut self,
hugr: &H,
input_wires: impl IntoIterator<Item = Wire>,
defns: HashMap<H::Node, NodeLinkingDirective>,
) -> Result<BuildHandle<DataflowOpID>, BuildError> {
let parent = Some(self.container_node());
let node = self
.hugr_mut()
.insert_link_view_by_node(parent, hugr, defns)
.map_err(|ins_err| BuildError::HugrViewInsertionError(ins_err.to_string()))?
.node_map[&hugr.entrypoint()];
wire_ins_return_outs(input_wires, node, self)
}

/// Wire up the `output_wires` to the input ports of the Output node.
Expand Down Expand Up @@ -731,6 +754,20 @@ fn wire_up_inputs<T: Dataflow + ?Sized>(
Ok(())
}

fn wire_ins_return_outs<T: Dataflow + ?Sized>(
inputs: impl IntoIterator<Item = Wire>,
node: Node,
data_builder: &mut T,
) -> Result<BuildHandle<DataflowOpID>, BuildError> {
let op = data_builder.hugr().get_optype(node).clone();
let num_outputs = op.value_output_count();
wire_up_inputs(inputs, node, data_builder).map_err(|error| BuildError::OperationWiring {
op: Box::new(op),
error,
})?;
Ok((node, num_outputs).into())
}

/// Add edge from src to dst.
///
/// # Errors
Expand Down
66 changes: 62 additions & 4 deletions hugr-core/src/builder/dataflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,20 +339,21 @@ impl<T> HugrBuilder for DFGWrapper<Hugr, T> {
#[cfg(test)]
pub(crate) mod test {
use cool_asserts::assert_matches;
use ops::OpParent;
use rstest::rstest;
use serde_json::json;
use std::collections::HashMap;

use crate::builder::build_traits::DataflowHugr;
use crate::builder::test::dfg_calling_defn_decl;
use crate::builder::{
BuilderWiringError, DataflowSubContainer, ModuleBuilder, endo_sig, inout_sig,
};
use crate::extension::SignatureError;
use crate::extension::prelude::Noop;
use crate::extension::prelude::{bool_t, qb_t, usize_t};
use crate::hugr::linking::NodeLinkingDirective;
use crate::hugr::validate::InterGraphEdgeError;
use crate::ops::{OpTag, handle::NodeHandle};
use crate::ops::{OpTrait, Value};
use crate::ops::{FuncDecl, FuncDefn, OpParent, OpTag, OpTrait, Value, handle::NodeHandle};

use crate::std_extensions::logic::test::and_op;
use crate::types::type_param::TypeParam;
Expand Down Expand Up @@ -545,7 +546,7 @@ pub(crate) mod test {
}

#[test]
fn insert_hugr() -> Result<(), BuildError> {
fn add_hugr() -> Result<(), BuildError> {
// Create a simple DFG
let mut dfg_builder = DFGBuilder::new(Signature::new(vec![bool_t()], vec![bool_t()]))?;
let [i1] = dfg_builder.input_wires_arr();
Expand Down Expand Up @@ -576,6 +577,63 @@ pub(crate) mod test {
Ok(())
}

#[rstest]
fn add_hugr_link_nodes(
#[values(false, true)] replace: bool,
#[values(true, false)] view: bool,
) {
let mut fb = FunctionBuilder::new("main", Signature::new_endo(bool_t())).unwrap();
let my_decl = fb
.module_root_builder()
.declare("func1", Signature::new_endo(bool_t()).into())
.unwrap();
let (insert, ins_defn, ins_decl) = dfg_calling_defn_decl();
let ins_defn_name = insert
.get_optype(ins_defn.node())
.as_func_defn()
.unwrap()
.func_name()
.clone();
let ins_decl_name = insert
.get_optype(ins_decl.node())
.as_func_decl()
.unwrap()
.func_name()
.clone();
let decl_mode = if replace {
NodeLinkingDirective::UseExisting(my_decl.node())
} else {
NodeLinkingDirective::add()
};
let link_spec = HashMap::from([
(ins_defn.node(), NodeLinkingDirective::add()),
(ins_decl.node(), decl_mode),
]);
let inserted = if view {
fb.add_link_view_by_node_with_wires(&insert, [], link_spec)
.unwrap()
} else {
fb.add_link_hugr_by_node_with_wires(insert, [], link_spec)
.unwrap()
};
let h = fb.finish_hugr_with_outputs(inserted.outputs()).unwrap();
let defn_names = h
.nodes()
.filter_map(|n| h.get_optype(n).as_func_defn().map(FuncDefn::func_name))
.collect_vec();
assert_eq!(defn_names, [&"main".to_string(), &ins_defn_name]);
let decl_names = h
.nodes()
.filter_map(|n| h.get_optype(n).as_func_decl().map(FuncDecl::func_name))
.cloned()
.collect_vec();
let mut expected_decl_names = vec!["func1".to_string()];
if !replace {
expected_decl_names.push(ins_decl_name)
}
assert_eq!(decl_names, expected_decl_names);
}

#[test]
fn barrier_node() -> Result<(), BuildError> {
let mut parent = DFGBuilder::new(endo_sig(bool_t()))?;
Expand Down
Loading
Loading