Skip to content

Commit e133a67

Browse files
committed
Add new Subcircuit
1 parent 1aaefa0 commit e133a67

10 files changed

Lines changed: 1612 additions & 99 deletions

File tree

tket/src/circuit.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,10 @@ lazy_static! {
7676
set
7777
};
7878
}
79-
/// The [IGNORED_EXTENSION_OPS] definition depends on the buggy behaviour of [`NamedOp::name`], which returns bare names instead of scoped names on some cases.
80-
/// Once this test starts failing it should be time to drop the `format!("prelude.{}", ...)`.
81-
/// https://github.com/CQCL/hugr/issues/1496
79+
/// The [IGNORED_EXTENSION_OPS] definition depends on the buggy behaviour of
80+
/// [`NamedOp::name`], which returns bare names instead of scoped names on some
81+
/// cases. Once this test starts failing it should be time to drop the
82+
/// `format!("prelude.{}", ...)`. https://github.com/CQCL/hugr/issues/1496
8283
#[test]
8384
fn issue_1496_remains() {
8485
assert_eq!("Noop", NoopDef.opdef_id())
@@ -134,8 +135,8 @@ impl<T: HugrView> Circuit<T> {
134135
/// If the circuit is a function definition, returns the name of the
135136
/// function.
136137
///
137-
/// If the name is empty or the circuit is not a function definition, returns
138-
/// `None`.
138+
/// If the name is empty or the circuit is not a function definition,
139+
/// returns `None`.
139140
#[inline]
140141
pub fn name(&self) -> Option<&str> {
141142
let op = self.hugr.get_optype(self.parent());
@@ -269,12 +270,14 @@ impl<T: HugrView> Circuit<T> {
269270
.sum()
270271
}
271272

272-
/// Return the graphviz representation of the underlying graph and hierarchy side by side.
273+
/// Return the graphviz representation of the underlying graph and hierarchy
274+
/// side by side.
273275
///
274-
/// For a simpler representation, use the [`Circuit::mermaid_string`] format instead.
276+
/// For a simpler representation, use the [`Circuit::mermaid_string`] format
277+
/// instead.
275278
pub fn dot_string(&self) -> String {
276-
// TODO: This will print the whole HUGR without identifying the circuit container.
277-
// Should we add some extra formatting for that?
279+
// TODO: This will print the whole HUGR without identifying the circuit
280+
// container. Should we add some extra formatting for that?
278281
self.hugr.dot_string()
279282
}
280283

@@ -333,12 +336,14 @@ impl<T: HugrView<Node = Node>> Circuit<T> {
333336
})
334337
}
335338

336-
/// Extracts the circuit into a new owned HUGR containing the circuit at the root.
337-
/// Replaces the circuit container operation with an [`OpType::DFG`].
339+
/// Extracts the circuit into a new owned HUGR containing the circuit at the
340+
/// root. Replaces the circuit container operation with an
341+
/// [`OpType::DFG`].
338342
///
339-
/// Regions that are not descendants of the parent node are not included in the new HUGR.
340-
/// This may invalidate calls to functions defined elsewhere. Make sure to inline any
341-
/// external functions before calling this method.
343+
/// Regions that are not descendants of the parent node are not included in
344+
/// the new HUGR. This may invalidate calls to functions defined
345+
/// elsewhere. Make sure to inline any external functions before calling
346+
/// this method.
342347
pub fn extract_dfg(&self) -> Result<Circuit<Hugr>, CircuitMutError> {
343348
let circ = self.to_owned();
344349
// TODO: Can we just ignore this now?

tket/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,4 @@ pub use circuit::{Circuit, CircuitError, CircuitMutError};
6868
pub use hugr;
6969
pub use hugr::Hugr;
7070
pub use ops::{op_matches, symbolic_constant_op, Pauli, TketOp};
71+
pub use subcircuit::Subcircuit;

tket/src/resource.rs

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,90 @@
4545
4646
// Public API exports
4747
pub use flow::{DefaultResourceFlow, ResourceFlow, UnsupportedOp};
48-
pub use scope::{ResourceScope, ResourceScopeConfig};
48+
pub use scope::ResourceScope;
4949
pub use types::{CircuitUnit, Position, ResourceAllocator, ResourceId};
5050

51+
use crate::extension::rotation::{ConstRotation, RotationOp};
52+
53+
use hugr::{
54+
extension::simple_op::MakeExtensionOp,
55+
ops::{constant, OpType},
56+
std_extensions::arithmetic::{conversions::ConvertOpDef, float_types::ConstF64},
57+
HugrView, IncomingPort, PortIndex,
58+
};
59+
5160
// Internal modules
5261
mod flow;
5362
mod scope;
5463
mod types;
5564

65+
impl<H: HugrView> ResourceScope<H> {
66+
/// The constant value of a circuit unit.
67+
pub fn as_const_value(&self, unit: CircuitUnit<H::Node>) -> Option<&constant::Value> {
68+
let (mut curr_node, outport) = match unit {
69+
CircuitUnit::Resource(..) => None,
70+
CircuitUnit::Copyable(wire) => Some((wire.node(), wire.source())),
71+
}?;
72+
73+
if outport.index() > 0 {
74+
return None;
75+
}
76+
77+
fn is_const_conversion_op(op: &OpType) -> bool {
78+
if matches!(op, OpType::LoadConstant(..)) {
79+
true
80+
} else if let Some(op) = op.as_extension_op() {
81+
if let Ok(op) = ConvertOpDef::from_extension_op(op) {
82+
op == ConvertOpDef::itousize
83+
} else if let Ok(op) = RotationOp::from_extension_op(op) {
84+
matches!(
85+
op,
86+
RotationOp::from_halfturns_unchecked | RotationOp::from_halfturns
87+
)
88+
} else {
89+
false
90+
}
91+
} else {
92+
false
93+
}
94+
}
95+
96+
let mut op;
97+
while {
98+
op = self.hugr().get_optype(curr_node);
99+
is_const_conversion_op(op)
100+
} {
101+
(curr_node, _) = self
102+
.hugr()
103+
.single_linked_output(curr_node, IncomingPort::from(0))
104+
.expect("invalid signature for conversion op");
105+
}
106+
107+
if let OpType::Const(const_op) = op {
108+
Some(&const_op.value)
109+
} else {
110+
None
111+
}
112+
}
113+
114+
/// The constant f64 value of a circuit unit (if it is a constant f64).
115+
pub fn as_const_f64(&self, unit: CircuitUnit<H::Node>) -> Option<f64> {
116+
let const_val = self.as_const_value(unit)?;
117+
if let Some(const_rot) = const_val.get_custom_value::<ConstRotation>() {
118+
Some(const_rot.half_turns())
119+
} else if let Some(const_f64) = const_val.get_custom_value::<ConstF64>() {
120+
Some(const_f64.value())
121+
} else {
122+
panic!("unknown constant type: {:?}", const_val);
123+
}
124+
}
125+
}
126+
56127
#[cfg(test)]
57128
pub(crate) mod tests {
58129
use hugr::{
59130
builder::{DFGBuilder, Dataflow, DataflowHugr},
60131
extension::prelude::qb_t,
61-
hugr::views::SiblingSubgraph,
62-
ops::handle::DataflowParentID,
63132
types::Signature,
64133
CircuitUnit, Hugr,
65134
};
@@ -71,7 +140,7 @@ pub(crate) mod tests {
71140
extension::rotation::{rotation_type, ConstRotation},
72141
resource::scope::tests::ResourceScopeReport,
73142
utils::build_simple_circuit,
74-
TketOp,
143+
Circuit, TketOp,
75144
};
76145

77146
use super::ResourceScope;
@@ -89,7 +158,7 @@ pub(crate) mod tests {
89158
}
90159

91160
// Gate being commuted has a non-linear input
92-
fn circ(n_qubits: usize, add_rz: bool, add_const_rz: bool) -> Hugr {
161+
pub fn cx_rz_circuit(n_qubits: usize, add_rz: bool, add_const_rz: bool) -> Hugr {
93162
let build = || {
94163
let out_qb_row = vec![qb_t(); n_qubits];
95164
let mut inp_qb_row = out_qb_row.clone();
@@ -154,9 +223,8 @@ pub(crate) mod tests {
154223
#[case] add_rz: bool,
155224
#[case] add_const_rz: bool,
156225
) {
157-
let circ = circ(n_qubits, add_rz, add_const_rz);
158-
let subgraph =
159-
SiblingSubgraph::try_new_dataflow_subgraph::<_, DataflowParentID>(&circ).unwrap();
226+
let circ = cx_rz_circuit(n_qubits, add_rz, add_const_rz);
227+
let subgraph = Circuit::from(&circ).subgraph().unwrap();
160228
let scope = ResourceScope::new(&circ, subgraph);
161229
let info = ResourceScopeReport::from(&scope);
162230

tket/src/resource/scope.rs

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,6 @@ impl<H: HugrView> ResourceScope<H> {
160160
Some(*port_map.get(port))
161161
}
162162

163-
/// Get the [`ResourceId`] for a given port.
164-
///
165-
/// Return None if the port is not a resource port.
166-
pub fn get_resource_id(&self, node: H::Node, port: impl Into<Port>) -> Option<ResourceId> {
167-
let unit = self.get_circuit_unit(node, port)?;
168-
unit.as_resource()
169-
}
170-
171163
/// Get all [`CircuitUnit`]s for either the incoming or outgoing ports of a
172164
/// node.
173165
pub fn get_circuit_units_slice(
@@ -179,15 +171,47 @@ impl<H: HugrView> ResourceScope<H> {
179171
Some(port_map.get_slice(direction))
180172
}
181173

182-
/// Get the port of node on the given resource path.
174+
/// Get the ports of node with the given opvalue in the given direction.
183175
///
184176
/// The returned port will have the direction `dir`.
185-
pub fn get_port(&self, node: H::Node, resource_id: ResourceId, dir: Direction) -> Option<Port> {
186-
let units = self.get_circuit_units_slice(node, dir)?;
187-
let offset = units
188-
.iter()
189-
.position(|unit| unit.as_resource() == Some(resource_id))?;
190-
Some(Port::new(dir, offset))
177+
pub fn get_ports(
178+
&self,
179+
node: H::Node,
180+
unit: impl Into<CircuitUnit<H::Node>>,
181+
dir: Direction,
182+
) -> impl Iterator<Item = Port> + '_ {
183+
let exp_unit = unit.into();
184+
let units = self.get_circuit_units_slice(node, dir);
185+
let offsets = units
186+
.into_iter()
187+
.flatten()
188+
.positions(move |unit| unit == &exp_unit);
189+
offsets.map(move |offset| Port::new(dir, offset))
190+
}
191+
192+
/// Get the port of node with the given resource in the given direction.
193+
pub fn get_resource_port(
194+
&self,
195+
node: H::Node,
196+
resource_id: ResourceId,
197+
dir: Direction,
198+
) -> Option<Port> {
199+
self.get_ports(node, resource_id, dir)
200+
.at_most_one()
201+
.ok()
202+
.expect("linear resource")
203+
}
204+
205+
/// Get the resource ID at the given port of the given node.
206+
pub fn get_resource_id(&self, node: H::Node, port: impl Into<Port>) -> Option<ResourceId> {
207+
let unit = self.get_circuit_unit(node, port)?;
208+
unit.as_resource()
209+
}
210+
211+
/// Get the copyable wire at the given port of the given node.
212+
pub fn get_copyable_wire(&self, node: H::Node, port: impl Into<Port>) -> Option<Wire<H::Node>> {
213+
let unit = self.get_circuit_unit(node, port)?;
214+
unit.as_copyable_wire()
191215
}
192216

193217
/// Get the position of the given node.
@@ -215,24 +239,21 @@ impl<H: HugrView> ResourceScope<H> {
215239
.filter_map(|unit| unit.as_resource())
216240
}
217241

218-
/// All resource IDs on the ports of `node`, in both directions.
219-
pub fn get_all_resources(&self, node: H::Node) -> Vec<ResourceId> {
242+
/// All resource IDs on the ports of `node`, in both directions, in the
243+
/// order that they appear along the ports of `node`.
244+
pub fn get_all_resources(&self, node: H::Node) -> impl Iterator<Item = ResourceId> + '_ {
220245
let in_resources = self.get_resources(node, Direction::Incoming);
221246
let out_resources = self.get_resources(node, Direction::Outgoing);
222-
let mut all_resources = in_resources.chain(out_resources).collect_vec();
223-
all_resources.sort_unstable();
224-
all_resources.dedup();
225-
all_resources.shrink_to_fit();
226-
all_resources
247+
in_resources.chain(out_resources).unique()
227248
}
228249

229250
/// Whether the given node is the first node on the path of the given
230251
/// resource.
231252
pub fn is_resource_start(&self, node: H::Node, resource_id: ResourceId) -> bool {
232-
self.get_port(node, resource_id, Direction::Outgoing)
253+
self.get_resource_port(node, resource_id, Direction::Outgoing)
233254
.is_some()
234255
&& self
235-
.get_port(node, resource_id, Direction::Incoming)
256+
.get_resource_port(node, resource_id, Direction::Incoming)
236257
.is_none()
237258
}
238259

@@ -246,7 +267,7 @@ impl<H: HugrView> ResourceScope<H> {
246267
}
247268

248269
/// All copyable wires on the ports of `node` in the given direction.
249-
pub fn get_copyable_wires(
270+
pub fn all_copyable_wires(
250271
&self,
251272
node: H::Node,
252273
dir: Direction,
@@ -258,6 +279,18 @@ impl<H: HugrView> ResourceScope<H> {
258279
})
259280
}
260281

282+
/// All ports of `node` in the given direction that are copyable.
283+
pub fn get_copyable_ports(
284+
&self,
285+
node: H::Node,
286+
dir: Direction,
287+
) -> impl Iterator<Item = Port> + '_ {
288+
let units = self.get_circuit_units_slice(node, dir);
289+
let ports = self.hugr().node_ports(node, dir);
290+
let units_ports = units.into_iter().flatten().zip(ports);
291+
units_ports.filter_map(|(unit, port)| unit.is_copyable().then_some(port))
292+
}
293+
261294
/// Iterate over the nodes on the resource path starting from the given
262295
/// node in the given direction.
263296
pub fn resource_path_iter(
@@ -267,7 +300,7 @@ impl<H: HugrView> ResourceScope<H> {
267300
direction: Direction,
268301
) -> impl Iterator<Item = H::Node> + '_ {
269302
iter::successors(Some(start_node), move |&curr_node| {
270-
let port = self.get_port(curr_node, resource_id, direction)?;
303+
let port = self.get_resource_port(curr_node, resource_id, direction)?;
271304
let (next_node, _) = self
272305
.hugr()
273306
.single_linked_port(curr_node, port)
@@ -734,11 +767,7 @@ pub(crate) mod tests {
734767
.map(|(n, _)| n);
735768

736769
for h in first_hadamards {
737-
let res = scope
738-
.get_all_resources(h)
739-
.into_iter()
740-
.exactly_one()
741-
.unwrap();
770+
let res = scope.get_all_resources(h).exactly_one().ok().unwrap();
742771
let nodes_on_path = scope.resource_path_iter(res, h, Direction::Outgoing);
743772
let pos_on_path = nodes_on_path.map(|n| scope.get_position(n).unwrap());
744773

0 commit comments

Comments
 (0)