diff --git a/Cargo.lock b/Cargo.lock index 4d8719b4b..3dbdc6293 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -521,6 +521,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cool_asserts" version = "2.0.3" @@ -758,7 +767,7 @@ version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version", @@ -767,21 +776,23 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" dependencies = [ + "convert_case 0.10.0", "proc-macro2", "quote", + "rustc_version", "syn 2.0.108", "unicode-xid", ] @@ -1085,7 +1096,7 @@ dependencies = [ "clap", "clap-verbosity-flag", "clio", - "derive_more 2.0.1", + "derive_more 2.1.0", "hugr", "schemars 1.0.5", "serde", @@ -1105,7 +1116,7 @@ dependencies = [ "base64", "cgmath", "delegate 0.13.5", - "derive_more 2.0.1", + "derive_more 2.1.0", "downcast-rs", "enum_dispatch", "html-escape", @@ -1143,7 +1154,7 @@ dependencies = [ "anyhow", "cc", "delegate 0.13.5", - "derive_more 2.0.1", + "derive_more 2.1.0", "hugr-core", "inkwell", "insta", @@ -1163,7 +1174,7 @@ dependencies = [ "base64", "bumpalo", "capnp", - "derive_more 2.0.1", + "derive_more 2.1.0", "indexmap 2.12.1", "itertools 0.14.0", "ordered-float", @@ -1183,7 +1194,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e170464e6c94176cd39099f52bac95552bf360b971959cd8a4a27d76e645ab9" dependencies = [ "ascent", - "derive_more 2.0.1", + "derive_more 2.1.0", "hugr-core", "itertools 0.14.0", "pastey", @@ -2646,7 +2657,7 @@ dependencies = [ "crossbeam-channel", "csv", "delegate 0.13.5", - "derive_more 2.0.1", + "derive_more 2.1.0", "fxhash", "hugr", "hugr-core", @@ -2681,7 +2692,7 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1c424855b86bde6c2d1651241abf829f1bfcd8e5428356576ac11bd825357bc" dependencies = [ - "derive_more 2.0.1", + "derive_more 2.1.0", "pyo3", "pythonize", "serde", @@ -2695,7 +2706,7 @@ name = "tket-py" version = "0.0.0" dependencies = [ "cool_asserts", - "derive_more 2.0.1", + "derive_more 2.1.0", "hugr", "itertools 0.14.0", "num_cpus", @@ -2720,7 +2731,7 @@ dependencies = [ "clap", "cool_asserts", "delegate 0.13.5", - "derive_more 2.0.1", + "derive_more 2.1.0", "hugr", "hugr-cli", "indexmap 2.12.1", @@ -2932,6 +2943,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 50d977254..e0b04fc16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ clap = "4.5.53" crossbeam-channel = "0.5.15" csv = "1.4.0" delegate = "0.13.5" -derive_more = "2.0.1" +derive_more = "2.1.0" fxhash = "0.2.1" indexmap = "2.12.1" lazy_static = "1.5.0" diff --git a/tket/src/serialize/pytket.rs b/tket/src/serialize/pytket.rs index 07af6e238..cfeb23474 100644 --- a/tket/src/serialize/pytket.rs +++ b/tket/src/serialize/pytket.rs @@ -19,6 +19,8 @@ pub use error::{ PytketDecodeError, PytketDecodeErrorInner, PytketEncodeError, PytketEncodeOpError, }; pub use extension::PytketEmitter; +use hugr::std_extensions::arithmetic::float_types::float64_type; +use hugr::types::Type; pub use options::{DecodeInsertionTarget, DecodeOptions, EncodeOptions}; use hugr::hugr::hugrmut::HugrMut; @@ -30,6 +32,7 @@ mod tests; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::path::Path; +use std::sync::LazyLock; use std::{fs, io}; use tket_json_rs::circuit_json::SerialCircuit; @@ -38,6 +41,7 @@ use tket_json_rs::register::{Bit, ElementId, Qubit}; use self::decoder::PytketDecoderContext; use crate::circuit::Circuit; +use crate::extension::rotation::rotation_type; pub use crate::passes::pytket::lower_to_pytket; /// Prefix used for storing metadata in the hugr nodes. @@ -271,3 +275,6 @@ impl From<&Bit> for RegisterHash { } } } + +/// A list of types we translate as pytket parameters. +static PARAMETER_TYPES: LazyLock<[Type; 2]> = LazyLock::new(|| [float64_type(), rotation_type()]); diff --git a/tket/src/serialize/pytket/circuit.rs b/tket/src/serialize/pytket/circuit.rs index 100eb6ba6..670a2bd05 100644 --- a/tket/src/serialize/pytket/circuit.rs +++ b/tket/src/serialize/pytket/circuit.rs @@ -75,13 +75,10 @@ pub(super) struct EncodedCircuitInfo { /// pytket circuit, as they cannot be attached to a pytket command. #[derive(Debug, Clone)] pub(super) struct AdditionalNodesAndWires { - /// A subgraph of the region that does not contain any operation encodable - /// as a pytket command, and has no qubit/bits in its boundary that could be - /// used to emit an opaque barrier command in the [`serial_circuit`]. - pub extra_subgraph: Option, - /// Parameter expression inputs to the `extra_subgraph`. - /// These cannot be encoded either if there's no pytket command to attach them to. - pub extra_subgraph_params: Vec, + /// Subgraphs of the region that could not be encoded as a pytket commands, + /// and have no qubit/bits in their boundary that could be used to emit an + /// opaque barrier command in the [`serial_circuit`]. + pub additional_subgraphs: Vec, /// List of wires that directly connected the input node to the output node in the encoded region, /// and were not encoded in [`serial_circuit`]. /// @@ -89,6 +86,17 @@ pub(super) struct AdditionalNodesAndWires { pub straight_through_wires: Vec, } +/// A subgraph of the encoded circuit that could not be associated to any qubit or bit register in the pytket circuit. +#[derive(Debug, Clone)] +pub(super) struct AdditionalSubgraph { + /// The subgraph of the region that could not be encoded as a pytket command, + /// and has no qubit/bits in its boundary that could be used to emit an opaque + /// barrier command in the [`serial_circuit`]. + pub id: SubgraphId, + /// Parameter expression inputs to the `subgraph`. + pub params: Vec, +} + /// A wire stored in the [`EncodedCircuitInfo`] that directly connected the /// input node to the output node in the encoded region, and was not encoded in /// the pytket circuit. diff --git a/tket/src/serialize/pytket/decoder.rs b/tket/src/serialize/pytket/decoder.rs index b15849a6a..7df595639 100644 --- a/tket/src/serialize/pytket/decoder.rs +++ b/tket/src/serialize/pytket/decoder.rs @@ -467,24 +467,17 @@ impl<'h> PytketDecoderContext<'h> { commands: &[circuit_json::Command], extra_nodes_and_wires: Option<&AdditionalNodesAndWires>, ) -> Result<(), PytketDecodeError> { - let config = self.config().clone(); - for com in commands { - let op_type = com.op.op_type; - self.process_command(com, config.as_ref()) - .map_err(|e| e.pytket_op(&op_type))?; - } - // Add additional subgraphs and wires not encoded in commands. let [input_node, output_node] = self.builder.io(); if let Some(extras) = extra_nodes_and_wires { - if let Some(subgraph_id) = extras.extra_subgraph { - let params = extras - .extra_subgraph_params + for extra_subgraph in &extras.additional_subgraphs { + let params = extra_subgraph + .params .iter() .map(|p| self.load_half_turns(p)) .collect_vec(); - self.insert_external_subgraph(subgraph_id, &[], &[], ¶ms) + self.insert_external_subgraph(extra_subgraph.id, &[], &[], ¶ms) .map_err(|e| e.hugr_op("External subgraph"))?; } @@ -502,6 +495,15 @@ impl<'h> PytketDecoderContext<'h> { ); } } + + // Decode the pytket commands. + let config = self.config().clone(); + for com in commands { + let op_type = com.op.op_type; + self.process_command(com, config.as_ref()) + .map_err(|e| e.pytket_op(&op_type))?; + } + Ok(()) } diff --git a/tket/src/serialize/pytket/decoder/subgraph.rs b/tket/src/serialize/pytket/decoder/subgraph.rs index b74d2a2ec..b09a1999c 100644 --- a/tket/src/serialize/pytket/decoder/subgraph.rs +++ b/tket/src/serialize/pytket/decoder/subgraph.rs @@ -11,6 +11,7 @@ use hugr::{Hugr, HugrView, Node, OutgoingPort, PortIndex, Wire}; use hugr_core::hugr::internal::HugrMutInternals; use itertools::Itertools; +use crate::extension::rotation::rotation_type; use crate::serialize::pytket::decoder::{ DecodeStatus, FoundWire, LoadedParameter, PytketDecoderContext, TrackedBit, TrackedQubit, }; @@ -18,7 +19,9 @@ use crate::serialize::pytket::extension::RegisterCount; use crate::serialize::pytket::opaque::{ EncodedEdgeID, OpaqueSubgraph, OpaqueSubgraphPayload, SubgraphId, }; -use crate::serialize::pytket::{PytketDecodeError, PytketDecodeErrorInner, PytketDecoderConfig}; +use crate::serialize::pytket::{ + PytketDecodeError, PytketDecodeErrorInner, PytketDecoderConfig, PARAMETER_TYPES, +}; impl<'h> PytketDecoderContext<'h> { /// Insert a subgraph encoded in the payload of a pytket barrier operation into @@ -81,7 +84,7 @@ impl<'h> PytketDecoderContext<'h> { subgraph, qubits, bits, params, old_parent, new_parent, )?; - self.rewire_external_subgraph_outputs(subgraph, qubits, bits, old_parent, new_parent)?; + self.rewire_external_subgraph_outputs(id, subgraph, qubits, bits, old_parent, new_parent)?; Ok(DecodeStatus::Success) } @@ -159,6 +162,7 @@ impl<'h> PytketDecoderContext<'h> { /// Helper for [`Self::insert_external_subgraph`]. fn rewire_external_subgraph_outputs( &mut self, + id: SubgraphId, subgraph: &OpaqueSubgraph, qubits: &[TrackedQubit], bits: &[TrackedBit], @@ -171,10 +175,11 @@ impl<'h> PytketDecoderContext<'h> { let mut output_qubits = qubits; let mut output_bits = bits; - for (ty, (src, src_port)) in subgraph + for ((out_idx, ty), (src, src_port)) in subgraph .signature() .output() .iter() + .enumerate() .zip_eq(subgraph.outgoing_ports()) { // Output wire from the subgraph. Depending on the type, we may need @@ -182,8 +187,7 @@ impl<'h> PytketDecoderContext<'h> { // leave it untouched. let wire = Wire::new(*src, *src_port); if let Some(counts) = self.config().type_to_pytket(ty).filter(|c| c.params == 0) { - // This port declares new outputs to be tracked by the decoder. - // Output parameters from a subgraph are always marked as not supported (they don't map to any pytket argument variable). + // This port declares new bit/qubit outputs to be tracked by the decoder. // Make sure to disconnect the old wire. self.builder.hugr_mut().disconnect(*src, *src_port); @@ -204,6 +208,15 @@ impl<'h> PytketDecoderContext<'h> { wire_qubits.unwrap().iter().cloned(), wire_bits.unwrap().iter().cloned(), )?; + } else if PARAMETER_TYPES.contains(ty) { + let param_name = id.output_parameter(out_idx); + let param = if ty == &rotation_type() { + LoadedParameter::rotation(wire) + } else { + LoadedParameter::float_half_turns(wire) + }; + self.wire_tracker + .register_input_parameter(param, param_name)?; } else { // This is an unsupported wire. If it was connected to the old // region's output, rewire it to the new region's output. diff --git a/tket/src/serialize/pytket/decoder/wires.rs b/tket/src/serialize/pytket/decoder/wires.rs index 5cc6402f3..d3f524064 100644 --- a/tket/src/serialize/pytket/decoder/wires.rs +++ b/tket/src/serialize/pytket/decoder/wires.rs @@ -25,7 +25,7 @@ use crate::serialize::pytket::decoder::{ use crate::serialize::pytket::extension::RegisterCount; use crate::serialize::pytket::opaque::EncodedEdgeID; use crate::serialize::pytket::{ - PytketDecodeError, PytketDecodeErrorInner, PytketDecoderConfig, RegisterHash, + PytketDecodeError, PytketDecodeErrorInner, PytketDecoderConfig, RegisterHash, PARAMETER_TYPES, }; use crate::{symbolic_constant_op, TketOp}; @@ -661,7 +661,7 @@ impl WireTracker { } // Return a parameter input if the type is a float or rotation. - if [float64_type(), rotation_type()].contains(ty) { + if PARAMETER_TYPES.contains(ty) { let Some(param) = split_off_first(params) else { return Err( PytketDecodeErrorInner::NoMatchingParameter { ty: ty.to_string() }.wrap(), diff --git a/tket/src/serialize/pytket/encoder.rs b/tket/src/serialize/pytket/encoder.rs index 17594376f..a3dd3c0fd 100644 --- a/tket/src/serialize/pytket/encoder.rs +++ b/tket/src/serialize/pytket/encoder.rs @@ -16,7 +16,7 @@ use hugr::ops::{OpTrait, OpType}; use hugr::types::EdgeKind; use std::borrow::Cow; -use std::collections::{BTreeSet, HashMap}; +use std::collections::HashMap; use std::ops::RangeTo; use std::sync::{Arc, RwLock}; @@ -30,7 +30,9 @@ use super::{ PytketEncodeError, PytketEncodeOpError, METADATA_OPGROUP, METADATA_PHASE, METADATA_Q_REGISTERS, }; use crate::circuit::Circuit; -use crate::serialize::pytket::circuit::{AdditionalNodesAndWires, EncodedCircuitInfo}; +use crate::serialize::pytket::circuit::{ + AdditionalNodesAndWires, AdditionalSubgraph, EncodedCircuitInfo, +}; use crate::serialize::pytket::config::PytketEncoderConfig; use crate::serialize::pytket::extension::RegisterCount; use crate::serialize::pytket::opaque::{OpaqueSubgraph, OpaqueSubgraphPayload}; @@ -56,6 +58,11 @@ pub struct PytketEncoderContext { unsupported: UnsupportedTracker, /// A registry of already-encoded opaque subgraphs. opaque_subgraphs: OpaqueSubgraphs, + /// Subgraphs in `opaque_subgraphs` that could not be emitted as opaque + /// barriers, and must be stored in the [`EncodedCircuitInfo`] instead when + /// finishing the encoding. Identified by their + /// [`super::opaque::SubgraphId`] in `opaque_subgraphs`. + non_emitted_subgraphs: Vec, /// Configuration for the encoding. /// /// Contains custom operation/type/const emitters. @@ -176,6 +183,7 @@ impl PytketEncoderContext { values: ValueTracker::new(circ, region, &config)?, unsupported: UnsupportedTracker::new(circ), opaque_subgraphs, + non_emitted_subgraphs: vec![], config, function_cache: Arc::new(RwLock::new(HashMap::new())), }) @@ -224,30 +232,11 @@ impl PytketEncoderContext { region: H::Node, ) -> Result<(EncodedCircuitInfo, OpaqueSubgraphs), PytketEncodeError> { // Add any remaining unsupported nodes - let mut extra_subgraph: Option> = None; - let mut extra_subgraph_params = Vec::new(); while !self.unsupported.is_empty() { let node = self.unsupported.iter().next().unwrap(); let opaque_subgraphs = self.unsupported.extract_component(node, circ.hugr())?; - match self.emit_unsupported(&opaque_subgraphs, circ) { - Ok(()) => (), - Err(PytketEncodeError::UnsupportedSubgraphHasNoRegisters { params }) => { - // We'll store the nodes in the `extra_subgraph` field of the `EncodedCircuitInfo`. - // So the decoder can reconstruct the original subgraph. - extra_subgraph - .get_or_insert_default() - .extend(opaque_subgraphs.nodes().iter().cloned()); - extra_subgraph_params.extend(params); - } - Err(e) => return Err(e), - } + self.emit_unsupported(&opaque_subgraphs, circ)?; } - let extra_subgraph = extra_subgraph - .map(|nodes| -> Result<_, PytketEncodeError> { - let subgraph = OpaqueSubgraph::try_from_nodes(nodes, circ.hugr())?; - Ok(self.opaque_subgraphs.register_opaque_subgraph(subgraph)) - }) - .transpose()?; let tracker_result = self.values.finish(circ, region)?; @@ -264,8 +253,7 @@ impl PytketEncoderContext { input_params: tracker_result.input_params, output_params: tracker_result.params, additional_nodes_and_wires: AdditionalNodesAndWires { - extra_subgraph, - extra_subgraph_params, + additional_subgraphs: self.non_emitted_subgraphs, straight_through_wires: tracker_result.straight_through_wires, }, }; @@ -646,31 +634,40 @@ impl PytketEncoderContext { |p| { let range = out_param_count..out_param_count + p.expected_count; out_param_count += p.expected_count; - range.map(|i| format!("{subgraph_id}_out{i}")).collect_vec() + range.map(|i| subgraph_id.output_parameter(i)).collect_vec() }, )?; op_values.append(new_outputs); } // Check that we have qubits or bits to attach the barrier command to. - // - // This should only fail when looking at the "leftover" unsupported nodes at the end of the decoding process. if op_values.qubits.is_empty() && op_values.bits.is_empty() { - return Err(PytketEncodeError::UnsupportedSubgraphHasNoRegisters { + // We cannot associate this subgraph to any qubit or bit register in + // the pytket circuit, so we'll store it in the + // [`AdditionalSubgraph`]s instead when finishing the encoding. + // + // That list contains a list of subgraphs so we don't need to do any + // additional handling, but if we preferred in the future we could + // instead merge a single list of nodes if we wanted. + self.non_emitted_subgraphs.push(AdditionalSubgraph { + id: subgraph_id, params: input_param_exprs.clone(), }); - } + } else { + // If there are registers to which to attach, emit it as a barrier command. + + // Create the pytket operation, with an external reference to the subgraph. + let args = MakeOperationArgs { + num_qubits: op_values.qubits.len(), + num_bits: op_values.bits.len(), + params: Cow::Borrowed(&input_param_exprs), + }; + let mut pytket_op = make_tk1_operation(tket_json_rs::OpType::Barrier, args); + pytket_op.data = Some(serde_json::to_string(&payload).unwrap()); - // Create pytket operation, and add the subcircuit as hugr - let args = MakeOperationArgs { - num_qubits: op_values.qubits.len(), - num_bits: op_values.bits.len(), - params: Cow::Borrowed(&input_param_exprs), - }; - let mut pytket_op = make_tk1_operation(tket_json_rs::OpType::Barrier, args); - pytket_op.data = Some(serde_json::to_string(&payload).unwrap()); + self.emit_command(pytket_op, &op_values.qubits, &op_values.bits, None); + } - self.emit_command(pytket_op, &op_values.qubits, &op_values.bits, None); Ok(()) } diff --git a/tket/src/serialize/pytket/error.rs b/tket/src/serialize/pytket/error.rs index 26d1fc342..c04235ef7 100644 --- a/tket/src/serialize/pytket/error.rs +++ b/tket/src/serialize/pytket/error.rs @@ -104,6 +104,8 @@ pub enum PytketEncodeError { #[display("An unsupported subgraph has no qubits or bits to attach the barrier command to{}", if params.is_empty() {"".to_string()} else {format!(" alongside its parameters [{}]", params.iter().join(", "))} )] + #[deprecated(since = "0.16.1", note = "No longer emitted since 0.16.1")] + #[from(ignore)] UnsupportedSubgraphHasNoRegisters { /// Parameter inputs to the unsupported subgraph. params: Vec, diff --git a/tket/src/serialize/pytket/opaque.rs b/tket/src/serialize/pytket/opaque.rs index 00068311d..e86b23b27 100644 --- a/tket/src/serialize/pytket/opaque.rs +++ b/tket/src/serialize/pytket/opaque.rs @@ -28,6 +28,17 @@ pub struct SubgraphId { local_id: usize, } +impl SubgraphId { + /// Returns a unique parameter name for the `i`-th output parameter of the subgraph. + pub(crate) fn output_parameter(&self, i: usize) -> String { + format!( + "p{tracker}_{local}_out{i}", + tracker = self.tracker_id, + local = self.local_id, + ) + } +} + impl serde::Serialize for SubgraphId { fn serialize(&self, s: S) -> Result { (&self.tracker_id, &self.local_id).serialize(s) diff --git a/tket/src/serialize/pytket/tests.rs b/tket/src/serialize/pytket/tests.rs index 4611be7f8..8fb798219 100644 --- a/tket/src/serialize/pytket/tests.rs +++ b/tket/src/serialize/pytket/tests.rs @@ -752,6 +752,55 @@ fn circ_complex_param_type() -> Circuit { hugr.into() } +/// A program with two unsupported subgraphs not associated to any qubit or bit. +/// +#[fixture] +fn circ_unsupported_subgraph_no_registers() -> Circuit { + let input_t = vec![qb_t()]; + let output_t = vec![qb_t(), rotation_type()]; + let mut h = FunctionBuilder::new( + "unsupported_subgraph_no_registers", + Signature::new(input_t, output_t), + ) + .unwrap(); + let [q] = h.input_wires_arr(); + + // Declare two functions to call. + let func1 = { + let call_input_t = vec![]; + let call_output_t = vec![float64_type()]; + h.module_root_builder() + .declare("func1", Signature::new(call_input_t, call_output_t).into()) + .unwrap() + }; + + let func2 = { + h.module_root_builder() + .declare("func2", Signature::new_endo(vec![rotation_type()]).into()) + .unwrap() + }; + + // An unsupported call that'll require an opaque subgraph to encode. + let call = h.call(&func1, &[], []).unwrap(); + let [f] = call.outputs_arr(); + + // An operation that must be marked as unsupported, since it's input cannot be encoded. + let [rot] = h + .add_dataflow_op(RotationOp::from_halfturns_unchecked, [f]) + .unwrap() + .outputs_arr(); + let [q] = h + .add_dataflow_op(TketOp::Rz, [q, rot]) + .unwrap() + .outputs_arr(); + + // A separate call that will generate a second opaque subgraph. + let [rot2] = h.call(&func2, &[], [rot]).unwrap().outputs_arr(); + + let hugr = h.finish_hugr_with_outputs([q, rot2]).unwrap(); + hugr.into() +} + /// Check that all circuit ops have been translated to a native gate. /// /// Panics if there are tk1 ops in the circuit. @@ -1005,6 +1054,11 @@ fn fail_on_modified_hugr(circ_tk1_ops: Circuit) { )] #[case::output_parameter_wire(circ_output_parameter_wire(), 1, CircuitRoundtripTestConfig::Default)] #[case::non_local(circ_non_local(), 2, CircuitRoundtripTestConfig::Default)] +#[case::unsupported_subgraph_no_registers( + circ_unsupported_subgraph_no_registers(), + 1, + CircuitRoundtripTestConfig::Default +)] fn encoded_circuit_roundtrip( #[case] circ: Circuit,