Skip to content

Commit 48c25f3

Browse files
committed
wip: Decode unsupported subgraphs
1 parent b6b0bb7 commit 48c25f3

File tree

9 files changed

+697
-135
lines changed

9 files changed

+697
-135
lines changed

tket/src/serialize/pytket.rs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -130,18 +130,7 @@ impl TKETDecode for SerialCircuit {
130130
target: DecodeInsertionTarget,
131131
options: DecodeOptions<impl HugrView>,
132132
) -> Result<Node, Self::DecodeError> {
133-
let config = options
134-
.config
135-
.unwrap_or_else(|| default_decoder_config().into());
136-
137-
let mut decoder = PytketDecoderContext::new(
138-
self,
139-
hugr,
140-
target,
141-
options.signature,
142-
options.input_params,
143-
config,
144-
)?;
133+
let mut decoder = PytketDecoderContext::new(self, hugr, target, options)?;
145134
decoder.run_decoder(&self.commands)?;
146135
Ok(decoder.finish()?.node())
147136
}

tket/src/serialize/pytket/circuit.rs

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@ use std::collections::{HashMap, VecDeque};
44
use std::ops::{Index, IndexMut};
55

66
use hugr::core::HugrNode;
7+
use hugr::hugr::hugrmut::HugrMut;
8+
use hugr::ops::handle::NodeHandle;
79
use hugr::ops::{OpTag, OpTrait};
8-
use hugr::{Hugr, HugrView};
10+
use hugr::{Hugr, HugrView, Node};
911
use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator};
1012
use tket_json_rs::circuit_json::{Command as PytketCommand, SerialCircuit};
1113

14+
use crate::serialize::pytket::decoder::PytketDecoderContext;
1215
use crate::serialize::pytket::{
13-
default_encoder_config, EncodeOptions, PytketEncodeError, PytketEncoderContext,
16+
default_encoder_config, DecodeInsertionTarget, DecodeOptions, EncodeOptions, PytketDecodeError,
17+
PytketDecodeErrorInner, PytketEncodeError, PytketEncoderContext,
1418
};
1519
use crate::Circuit;
1620

@@ -149,6 +153,86 @@ impl<'a, H: HugrView> EncodedCircuit<'a, H> {
149153
Ok(())
150154
}
151155

156+
/// Reassemble the encoded circuits into a new [`Hugr`], containing a
157+
/// function defining the [`Self::head_region`] and expanding any opaque
158+
/// hugrs in pytket barrier operations back into Hugr subgraphs.
159+
///
160+
/// Functions called by the internal hugrs may be added to the hugr module
161+
/// as well.
162+
///
163+
/// # Arguments
164+
///
165+
/// - `fn_name`: The name of the function to create. If `None`, we will use
166+
/// the name of the circuit, or "main" if the circuit has no name.
167+
/// - `options`: The options for the decoder.
168+
///
169+
/// # Errors
170+
///
171+
/// Returns a [`PytketDecodeErrorInner::NonDataflowHeadRegion`] error if
172+
/// [`Self::head_region`] is not a dataflow container in the hugr.
173+
///
174+
/// Returns an error if a circuit being decoded is invalid. See
175+
/// [`PytketDecodeErrorInner`][super::error::PytketDecodeErrorInner] for
176+
/// more details.
177+
pub fn reassemble(
178+
&self,
179+
fn_name: Option<String>,
180+
options: DecodeOptions<H>,
181+
) -> Result<Hugr, PytketDecodeError> {
182+
let mut hugr = Hugr::new();
183+
let main_func = self.reassemble_inline(
184+
&mut hugr,
185+
DecodeInsertionTarget::Function { fn_name },
186+
options,
187+
)?;
188+
hugr.set_entrypoint(main_func);
189+
Ok(hugr)
190+
}
191+
192+
/// Reassemble the encoded circuits inside an existing [`Hugr`], containing
193+
/// the [`Self::head_region`] at the given insertion target.
194+
///
195+
/// Functions called by the internal hugrs may be added to the hugr module
196+
/// as well.
197+
///
198+
/// # Arguments
199+
///
200+
/// - `hugr`: The [`Hugr`] to reassemble the circuits in.
201+
/// - `target`: The target to insert the function at.
202+
/// - `options`: The options for the decoder.
203+
///
204+
/// # Errors
205+
///
206+
/// Returns a [`PytketDecodeErrorInner::NonDataflowHeadRegion`] error if
207+
/// [`Self::head_region`] is not a dataflow container in the hugr.
208+
///
209+
/// Returns an error if a circuit being decoded is invalid. See
210+
/// [`PytketDecodeErrorInner`][super::error::PytketDecodeErrorInner] for
211+
/// more details.
212+
pub fn reassemble_inline(
213+
&self,
214+
hugr: &mut Hugr,
215+
target: DecodeInsertionTarget,
216+
options: DecodeOptions<H>,
217+
) -> Result<Node, PytketDecodeError> {
218+
if !self.check_dataflow_head_region() {
219+
let head_op = self.hugr.get_optype(self.head_region).to_string();
220+
return Err(PytketDecodeErrorInner::NonDataflowHeadRegion { head_op }.wrap());
221+
};
222+
let serial_circuit = &self[self.head_region];
223+
224+
if self.len() > 1 {
225+
unimplemented!(
226+
"Reassembling an `EncodedCircuit` with nested subcircuits is not yet implemented."
227+
);
228+
};
229+
230+
let mut decoder = PytketDecoderContext::<H>::new(serial_circuit, hugr, target, options)?;
231+
decoder.register_opaque_subgraphs(&self.opaque_subgraphs, self.hugr);
232+
decoder.run_decoder(&serial_circuit.commands)?;
233+
Ok(decoder.finish()?.node())
234+
}
235+
152236
/// Extract the top-level pytket circuit as a standalone definition
153237
/// containing the whole original HUGR.
154238
///

tket/src/serialize/pytket/decoder.rs

Lines changed: 75 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub use param::{LoadedParameter, ParameterType};
99
pub use tracked_elem::{TrackedBit, TrackedQubit};
1010
pub use wires::TrackedWires;
1111

12+
pub(super) use wires::FindTypedWireResult;
13+
1214
use std::sync::Arc;
1315

1416
use hugr::builder::{
@@ -34,8 +36,8 @@ use crate::extension::rotation::rotation_type;
3436
use crate::serialize::pytket::config::PytketDecoderConfig;
3537
use crate::serialize::pytket::decoder::wires::WireTracker;
3638
use crate::serialize::pytket::extension::{build_opaque_tket_op, RegisterCount};
37-
use crate::serialize::pytket::opaque::OpaqueSubgraphs;
38-
use crate::serialize::pytket::{DecodeInsertionTarget, PytketDecodeErrorInner};
39+
use crate::serialize::pytket::opaque::{OpaqueSubgraphPayloadType, OpaqueSubgraphs};
40+
use crate::serialize::pytket::{DecodeInsertionTarget, DecodeOptions, PytketDecodeErrorInner};
3941
use crate::TketOp;
4042

4143
/// State of the tket circuit being decoded.
@@ -46,19 +48,17 @@ use crate::TketOp;
4648
/// The generic parameter `H` is the HugrView type of the Hugr that was encoded
4749
/// into the circuit, if any. This is required when the encoded pytket circuit
4850
/// contains opaque barriers that reference subgraphs in the original HUGR. See
49-
/// [`OpaqueSubgraphPayload`][super::opaque::OpaqueSubgraphPayload]
50-
/// for more details.
51+
/// [`OpaqueSubgraphPayload`][super::opaque::OpaqueSubgraphPayload] for more details.
5152
#[derive(Debug)]
5253
pub struct PytketDecoderContext<'h, H: HugrView> {
5354
/// The Hugr being built.
5455
pub builder: DFGBuilder<&'h mut Hugr>,
5556
/// A tracker keeping track of the generated wires and their corresponding types.
56-
pub(super) wire_tracker: WireTracker,
57-
/// Configuration for decoding commands.
57+
pub(super) wire_tracker: Box<WireTracker>,
58+
/// Options used when decoding the circuit.
5859
///
59-
/// Contains custom operation decoders, that define translation of legacy tket
60-
/// commands into HUGR operations.
61-
config: Arc<PytketDecoderConfig<H>>,
60+
/// `DecodeOptions::config` is
61+
options: DecodeOptions<H>,
6262
/// The HugrView type of the Hugr that originated the circuit, if any.
6363
original_hugr: Option<&'h H>,
6464
/// A registry of opaque subgraphs from `original_hugr`, that are referenced by opaque barriers in the pytket circuit
@@ -107,12 +107,16 @@ impl<'h, H: HugrView> PytketDecoderContext<'h, H> {
107107
serialcirc: &SerialCircuit,
108108
hugr: &'h mut Hugr,
109109
target: DecodeInsertionTarget,
110-
signature: Option<Signature>,
111-
input_params: impl IntoIterator<Item = String>,
112-
config: impl Into<Arc<PytketDecoderConfig<H>>>,
110+
mut options: DecodeOptions<H>,
113111
) -> Result<Self, PytketDecodeError> {
114-
let config: Arc<PytketDecoderConfig<H>> = config.into();
115-
let signature = signature.unwrap_or_else(|| {
112+
// Ensure that the set of decoders is present, use a default one if not.
113+
if options.config.is_none() {
114+
options.with_default_config();
115+
}
116+
117+
// Compute the signature of the decoded region, if not provided, and
118+
// initialize the DFG builder.
119+
let signature = options.signature.clone().unwrap_or_else(|| {
116120
let num_qubits = serialcirc.qubits.len();
117121
let num_bits = serialcirc.bits.len();
118122
let types: TypeRow = [vec![qb_t(); num_qubits], vec![bool_t(); num_bits]]
@@ -143,8 +147,8 @@ impl<'h, H: HugrView> PytketDecoderContext<'h, H> {
143147
serialcirc,
144148
&mut dfg,
145149
&signature.input,
146-
input_params,
147-
&config,
150+
options.input_params.iter().cloned(),
151+
options.get_config(),
148152
)?;
149153

150154
if !serialcirc.phase.is_empty() {
@@ -156,8 +160,8 @@ impl<'h, H: HugrView> PytketDecoderContext<'h, H> {
156160

157161
Ok(PytketDecoderContext {
158162
builder: dfg,
159-
wire_tracker,
160-
config,
163+
wire_tracker: Box::new(wire_tracker),
164+
options,
161165
original_hugr: None,
162166
opaque_subgraphs: None,
163167
})
@@ -354,22 +358,32 @@ impl<'h, H: HugrView> PytketDecoderContext<'h, H> {
354358
/// `original_hugr`, that are referenced by opaque barriers in the pytket
355359
/// circuit via their [`SubgraphId`].
356360
/// - `original_hugr`: The [`Hugr`] that originated the circuit, if any.
357-
#[expect(unused)]
358361
pub(super) fn register_opaque_subgraphs(
359362
&mut self,
360363
opaque_subgraphs: &'h OpaqueSubgraphs<H::Node>,
361364
original_hugr: &'h H,
362365
) {
363366
self.opaque_subgraphs = Some(opaque_subgraphs);
364367
self.original_hugr = Some(original_hugr);
368+
369+
// Add the hugr's extensions to the extension registry used when loading
370+
// subcircuits.
371+
if self.options.extensions.is_none() {
372+
self.options.extensions = Some(self.options.extension_registry().clone());
373+
}
374+
self.options
375+
.extensions
376+
.as_mut()
377+
.unwrap()
378+
.extend(original_hugr.extensions());
365379
}
366380

367381
/// Decode a list of pytket commands.
368382
pub(super) fn run_decoder(
369383
&mut self,
370384
commands: &[circuit_json::Command],
371385
) -> Result<(), PytketDecodeError> {
372-
let config = self.config.clone();
386+
let config = self.config().clone();
373387
for com in commands {
374388
let op_type = com.op.op_type;
375389
self.process_command(com, config.as_ref())
@@ -411,9 +425,32 @@ impl<'h, H: HugrView> PytketDecoderContext<'h, H> {
411425
Ok(())
412426
}
413427

414-
/// Returns the configuration used by the decoder.
415-
pub fn config(&self) -> &Arc<PytketDecoderConfig<H>> {
416-
&self.config
428+
/// Returns a tracked opaque subgraph encoded in an opaque barrier in the pytket circuit.
429+
///
430+
/// See [`OpaqueSubgraphPayload`][super::opaque::OpaqueSubgraphPayload]
431+
/// for more details.
432+
pub(super) fn get_opaque_subgraph(
433+
&self,
434+
payload: &OpaqueSubgraphPayloadType,
435+
) -> Result<Hugr, PytketDecodeError> {
436+
match payload {
437+
OpaqueSubgraphPayloadType::Inline { hugr_envelope } => {
438+
let hugr = Hugr::load_str(hugr_envelope, Some(self.options.extension_registry()))
439+
.map_err(|e| PytketDecodeErrorInner::UnsupportedSubgraphPayload {
440+
source: e,
441+
})?;
442+
Ok(hugr)
443+
}
444+
OpaqueSubgraphPayloadType::External { id } => {
445+
match (self.opaque_subgraphs, self.original_hugr) {
446+
(Some(subgraphs), Some(original_hugr)) if subgraphs.contains(*id) => {
447+
let hugr = subgraphs[*id].extract_subgraph(original_hugr, id.to_string());
448+
Ok(hugr)
449+
}
450+
_ => Err(PytketDecodeErrorInner::OpaqueSubgraphNotFound { id: *id }.wrap()),
451+
}
452+
}
453+
}
417454
}
418455
}
419456

@@ -452,7 +489,7 @@ impl<'h, H: HugrView> PytketDecoderContext<'h, H> {
452489
params: &[LoadedParameter],
453490
) -> Result<TrackedWires, PytketDecodeError> {
454491
self.wire_tracker
455-
.find_typed_wires(&self.config, types, qubit_args, bit_args, params)
492+
.find_typed_wires(self.config(), types, qubit_args, bit_args, params)
456493
}
457494

458495
/// Connects the input ports of a node using a list of input qubits, bits,
@@ -518,12 +555,12 @@ impl<'h, H: HugrView> PytketDecoderContext<'h, H> {
518555
let op_input_count: RegisterCount = sig
519556
.input_types()
520557
.iter()
521-
.map(|ty| self.config.type_to_pytket(ty).unwrap_or_default())
558+
.map(|ty| self.config().type_to_pytket(ty).unwrap_or_default())
522559
.sum();
523560
let op_output_count: RegisterCount = sig
524561
.output_types()
525562
.iter()
526-
.map(|ty| self.config.type_to_pytket(ty).unwrap_or_default())
563+
.map(|ty| self.config().type_to_pytket(ty).unwrap_or_default())
527564
.sum();
528565

529566
// Validate input counts
@@ -675,7 +712,7 @@ impl<'h, H: HugrView> PytketDecoderContext<'h, H> {
675712
let mut port_types = sig.output_ports().zip(sig.output_types().iter());
676713
while let Some((port, ty)) = port_types.next() {
677714
let wire = Wire::new(node, port);
678-
let counts = self.config.type_to_pytket(ty).unwrap_or_default();
715+
let counts = self.config().type_to_pytket(ty).unwrap_or_default();
679716
reg_count += counts;
680717

681718
// Get the qubits and bits for this wire.
@@ -685,7 +722,7 @@ impl<'h, H: HugrView> PytketDecoderContext<'h, H> {
685722
let expected_qubits = reg_count.qubits - counts.qubits + wire_qubits.len();
686723
let expected_bits = reg_count.bits - counts.bits + wire_bits.len();
687724
return Err(make_unexpected_node_out_error(
688-
&self.config,
725+
self.config(),
689726
port_types,
690727
reg_count,
691728
expected_qubits,
@@ -732,6 +769,16 @@ impl<'h, H: HugrView> PytketDecoderContext<'h, H> {
732769
.load_half_turns_parameter(&mut self.builder, param, Some(typ))
733770
.with_type(typ, &mut self.builder)
734771
}
772+
773+
/// Returns the configuration used by the decoder.
774+
pub fn config(&self) -> &Arc<PytketDecoderConfig<H>> {
775+
self.options.get_config()
776+
}
777+
778+
/// Returns the options used by the decoder.
779+
pub fn options(&self) -> &DecodeOptions<H> {
780+
&self.options
781+
}
735782
}
736783

737784
/// Result of trying to decode pytket operation into a HUGR definition.

0 commit comments

Comments
 (0)