Skip to content

feat!: Avoid eagerly cloning SerialCircuits when decoding from pytket #1048

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 2 additions & 8 deletions tket-qsystem/src/pytket/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,7 @@ fn json_roundtrip(#[case] circ_s: &str, #[case] num_commands: usize, #[case] num
let ser: circuit_json::SerialCircuit = serde_json::from_str(circ_s).unwrap();
assert_eq!(ser.commands.len(), num_commands);

let circ: Circuit = ser
.clone()
.decode_with_config(qsystem_decoder_config())
.unwrap();
let circ: Circuit = ser.decode_with_config(qsystem_decoder_config()).unwrap();

assert_eq!(circ.qubit_count(), num_qubits);

Expand All @@ -189,10 +186,7 @@ fn json_roundtrip(#[case] circ_s: &str, #[case] num_commands: usize, #[case] num
fn circuit_roundtrip(#[case] circ: Circuit, #[case] decoded_sig: Signature) {
let ser: SerialCircuit =
SerialCircuit::encode_with_config(&circ, qsystem_encoder_config()).unwrap();
let deser: Circuit = ser
.clone()
.decode_with_config(qsystem_decoder_config())
.unwrap();
let deser: Circuit = ser.decode_with_config(qsystem_decoder_config()).unwrap();

let deser_sig = deser.circuit_signature();
assert_eq!(
Expand Down
12 changes: 6 additions & 6 deletions tket/src/serialize/pytket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ pub trait TKETDecode: Sized {
/// Convert the serialized circuit to a circuit.
///
/// Uses a default set of extension decoders to translate operations.
fn decode(self) -> Result<Circuit, Self::DecodeError>;
fn decode(&self) -> Result<Circuit, Self::DecodeError>;
/// Convert the serialized circuit to a circuit.
fn decode_with_config(
self,
&self,
config: impl Into<Arc<PytketDecoderConfig>>,
) -> Result<Circuit, Self::DecodeError>;
/// Convert a circuit to a serialized pytket circuit.
Expand All @@ -95,20 +95,20 @@ impl TKETDecode for SerialCircuit {
type DecodeError = PytketDecodeError;
type EncodeError = PytketEncodeError;

fn decode(self) -> Result<Circuit, Self::DecodeError> {
fn decode(&self) -> Result<Circuit, Self::DecodeError> {
let config = default_decoder_config();
Self::decode_with_config(self, config)
}

fn decode_with_config(
self,
&self,
config: impl Into<Arc<PytketDecoderConfig>>,
) -> Result<Circuit, Self::DecodeError> {
let mut hugr = Hugr::new();

let mut decoder =
PytketDecoderContext::new(&self, &mut hugr, None, None, Vec::new(), config)?;
decoder.run_decoder(self.commands)?;
PytketDecoderContext::new(self, &mut hugr, None, None, Vec::new(), config)?;
decoder.run_decoder(&self.commands)?;
let main_func = decoder.finish()?;
hugr.set_entrypoint(main_func.node());
Ok(hugr.into())
Expand Down
10 changes: 5 additions & 5 deletions tket/src/serialize/pytket/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ impl<'h> PytketDecoderContext<'h> {
/// Decode a list of pytket commands.
pub(super) fn run_decoder(
&mut self,
commands: Vec<circuit_json::Command>,
commands: &[circuit_json::Command],
) -> Result<(), PytketDecodeError> {
let config = self.config.clone();
for com in commands {
Expand All @@ -348,13 +348,13 @@ impl<'h> PytketDecoderContext<'h> {
/// decoder.
pub(super) fn process_command(
&mut self,
command: circuit_json::Command,
command: &circuit_json::Command,
config: &PytketDecoderConfig,
) -> Result<(), PytketDecodeError> {
let circuit_json::Command { op, args, opgroup } = command;

// Find the latest [`TrackedQubit`] and [`TrackedBit`] for the command registers.
let (qubits, bits) = self.wire_tracker.pytket_args_to_tracked_elems(&args)?;
let (qubits, bits) = self.wire_tracker.pytket_args_to_tracked_elems(args)?;

// Collect the parameters used in the command.
let params: Vec<Arc<LoadedParameter>> = match &op.params {
Expand All @@ -366,12 +366,12 @@ impl<'h> PytketDecoderContext<'h> {
};

// Try to decode the command with the configured decoders.
match config.op_to_hugr(&op, &qubits, &bits, &params, &opgroup, self)? {
match config.op_to_hugr(op, &qubits, &bits, &params, opgroup, self)? {
DecodeStatus::Success => {}
DecodeStatus::Unsupported => {
// The command couldn't be translated into a native HUGR counterpart, so
// we generate an opaque `Tk1Op` instead.
build_opaque_tket_op(op, &qubits, &bits, &params, &opgroup, self)?;
build_opaque_tket_op(op, &qubits, &bits, &params, opgroup, self)?;
}
}
Ok(())
Expand Down
5 changes: 0 additions & 5 deletions tket/src/serialize/pytket/extension/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,6 @@ impl PytketDecoder for PreludeEmitter {
opgroup: Option<&str>,
decoder: &mut PytketDecoderContext<'h>,
) -> Result<DecodeStatus, PytketDecodeError> {
// Qubits, bits and parameters that will be used to register the node outputs.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drive-by: Leftover comment

//
// These should be modified by the match branches if the node does not have all
// its input registers in the outputs.

let op: OpType = match op.op_type {
PytketOptype::noop => Noop::new(qb_t()).into(),
PytketOptype::Barrier => {
Expand Down
10 changes: 4 additions & 6 deletions tket/src/serialize/pytket/extension/tk1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ impl<H: HugrView> PytketEmitter<H> for Tk1Emitter {
/// We should accept arbitrary wires, but the opaque extension op needs to be modified (or replaced with a new one)
/// since it currently has a limited signature definition.
pub(crate) fn build_opaque_tket_op<'h>(
op: tket_json_rs::circuit_json::Operation,
op: &tket_json_rs::circuit_json::Operation,
qubits: &[TrackedQubit],
bits: &[TrackedBit],
params: &[Arc<LoadedParameter>],
Expand Down Expand Up @@ -164,11 +164,9 @@ impl OpaqueTk1Op {
///
/// If the operation does not define a signature, one is generated with the
/// given amounts.
pub fn new_from_op(
mut op: circuit_json::Operation,
num_qubits: usize,
num_bits: usize,
) -> Self {
pub fn new_from_op(op: &circuit_json::Operation, num_qubits: usize, num_bits: usize) -> Self {
let mut op = op.clone();

if op.signature.is_none() {
op.signature =
Some([vec!["Q".into(); num_qubits], vec!["B".into(); num_bits]].concat());
Expand Down
8 changes: 4 additions & 4 deletions tket/src/serialize/pytket/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ fn json_roundtrip(#[case] circ_s: &str, #[case] num_commands: usize, #[case] num
let ser: circuit_json::SerialCircuit = serde_json::from_str(circ_s).unwrap();
assert_eq!(ser.commands.len(), num_commands);

let circ: Circuit = ser.clone().decode().unwrap();
let circ: Circuit = ser.decode().unwrap();

assert_eq!(circ.qubit_count(), num_qubits);

Expand All @@ -412,7 +412,7 @@ fn json_roundtrip(#[case] circ_s: &str, #[case] num_commands: usize, #[case] num
fn json_file_roundtrip(#[case] circ: impl AsRef<std::path::Path>) {
let reader = BufReader::new(std::fs::File::open(circ).unwrap());
let ser: circuit_json::SerialCircuit = serde_json::from_reader(reader).unwrap();
let circ: Circuit = ser.clone().decode().unwrap();
let circ: Circuit = ser.decode().unwrap();
let reser: SerialCircuit = SerialCircuit::encode(&circ).unwrap();
validate_serial_circ(&reser);
compare_serial_circs(&ser, &reser);
Expand All @@ -427,7 +427,7 @@ fn json_file_roundtrip(#[case] circ: impl AsRef<std::path::Path>) {
#[case::preset_parameterized(circ_parameterized(), Signature::new(vec![qb_t(), rotation_type(), rotation_type(), rotation_type()], vec![qb_t()]))]
fn circuit_roundtrip(#[case] circ: Circuit, #[case] decoded_sig: Signature) {
let ser: SerialCircuit = SerialCircuit::encode(&circ).unwrap();
let deser: Circuit = ser.clone().decode().unwrap();
let deser: Circuit = ser.decode().unwrap();

let deser_sig = deser.circuit_signature();
assert_eq!(
Expand Down Expand Up @@ -463,7 +463,7 @@ fn test_add_angle_serialise(#[case] circ_add_angles: (Circuit, String)) {
assert_eq!(ser.commands[0].op.op_type, optype::OpType::Rx);
assert_eq!(ser.commands[0].op.params, Some(vec![expected]));

let deser: Circuit = ser.clone().decode().unwrap();
let deser: Circuit = ser.decode().unwrap();
let reser = SerialCircuit::encode(&deser).unwrap();
validate_serial_circ(&reser);
compare_serial_circs(&ser, &reser);
Expand Down
Loading