Skip to content

Commit 228ff52

Browse files
aborgna-qcqc-alec
andauthored
fix(pytket-decoder): Avoid QAllocating and immediately freeing qubits (#1256)
When decoding a pytket circuit, when a qubit register is not present in the region's inputs we delay `QAlloc`ating it until it is consumed. On the other side, at the end of the decoding we insert `QFree`s on each qubit that didn't get consumed and is not part of the region's outputs. When a qubit falls in both categories (i.e. it didn't get used on pytket commands and didn't appear in the input/outputs) we were allocating it when requesting the wires to QFree. This PR adds a simple check to avoid the extra operations. --------- Co-authored-by: Alec Edgington <[email protected]>
1 parent d5b8dfc commit 228ff52

File tree

4 files changed

+44
-1
lines changed

4 files changed

+44
-1
lines changed

tket-py/tket/passes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from tket import optimiser
88

9-
# Re-export native bindings
9+
# Re-export native bindings.
1010
from ._tket.passes import (
1111
CircuitChunks,
1212
greedy_depth_reduce,

tket/src/serialize/pytket/decoder.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,11 @@ impl<'h> PytketDecoderContext<'h> {
426426
let mut bit_args: &[TrackedBit] = &[];
427427
let mut params: &[LoadedParameter] = &[];
428428
for q in qubits.iter() {
429+
// Ignore qubits that didn't get initialized
430+
if !self.wire_tracker.qubit_is_initialized(q) {
431+
continue;
432+
}
433+
429434
let mut qubit_args: &[TrackedQubit] = std::slice::from_ref(q);
430435
let Ok(FoundWire::Register(wire)) = self.wire_tracker.find_typed_wire(
431436
&self.config,

tket/src/serialize/pytket/decoder/wires.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,16 @@ impl WireTracker {
442442
&self.bits[id.0]
443443
}
444444

445+
/// Returns `true` if the tracked qubit has been initialized.
446+
///
447+
/// Qubits that have been registered but not associated to any
448+
/// wires do not need to be consumed at the end of the decoding.
449+
pub(super) fn qubit_is_initialized(&self, qubit: &TrackedQubit) -> bool {
450+
self.qubit_wires
451+
.get(&qubit.id())
452+
.is_some_and(|ws| !ws.is_empty())
453+
}
454+
445455
/// Returns the list of known pytket registers, in the order we expect to
446456
/// see them at the output.
447457
///

tket/src/serialize/pytket/tests.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ use crate::serialize::pytket::{
4040
};
4141
use crate::TketOp;
4242

43+
const EMPTY_CIRCUIT: &str = r#"{
44+
"phase": "0",
45+
"bits": [["c", [0]]],
46+
"qubits": [["q", [0]], ["q", [1]]],
47+
"commands": [],
48+
"implicit_permutation": [[["q", [0]], ["q", [0]]], [["q", [1]], ["q", [1]]]]
49+
}"#;
50+
4351
const SIMPLE_JSON: &str = r#"{
4452
"phase": "0",
4553
"bits": [],
@@ -1109,3 +1117,23 @@ fn test_inplace_decoding() {
11091117
assert!(hugr.get_optype(func1).is_func_defn());
11101118
assert!(hugr.get_optype(dfg).is_dfg());
11111119
}
1120+
1121+
/// Test the lazy qubit/bit decoding behaviour when the registers are not
1122+
/// present in the decoded region signature.
1123+
///
1124+
/// If the registers are never consumed, they won't be initialized at all.
1125+
#[rstest]
1126+
fn test_qubit_elision() {
1127+
let ser: circuit_json::SerialCircuit = serde_json::from_str(EMPTY_CIRCUIT).unwrap();
1128+
assert_eq!(ser.commands.len(), 0);
1129+
1130+
let circ: Circuit = ser
1131+
.decode(DecodeOptions::new().with_signature(Signature::new_endo(vec![])))
1132+
.unwrap();
1133+
assert_eq!(circ.qubit_count(), 0);
1134+
1135+
check_no_tk1_ops(&circ);
1136+
1137+
// The circuit should have no alloc/frees or const definitions
1138+
assert_eq!(circ.num_operations(), 0);
1139+
}

0 commit comments

Comments
 (0)