From 52129da6cbbb8eccf43395e0f65f89f08d854014 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Thu, 31 Jul 2025 10:38:53 -0600 Subject: [PATCH 1/6] Adding non-active flagcilla QEC --- .../pecos/qeclib/steane/qec/qec_3parallel.py | 90 ++++++++++++++++++- .../src/pecos/qeclib/steane/steane_class.py | 42 ++++++++- 2 files changed, 130 insertions(+), 2 deletions(-) diff --git a/python/quantum-pecos/src/pecos/qeclib/steane/qec/qec_3parallel.py b/python/quantum-pecos/src/pecos/qeclib/steane/qec/qec_3parallel.py index 788147e4f..dbbf0bc4f 100644 --- a/python/quantum-pecos/src/pecos/qeclib/steane/qec/qec_3parallel.py +++ b/python/quantum-pecos/src/pecos/qeclib/steane/qec/qec_3parallel.py @@ -17,7 +17,7 @@ from pecos.qeclib.steane.decoders.lookup import ( FlagLookupQASMActiveCorrectionX, - FlagLookupQASMActiveCorrectionZ, + FlagLookupQASMActiveCorrectionZ, FlagLookupQASM, ) from pecos.qeclib.steane.syn_extract.six_check_nonflagging import SixUnflaggedSyn from pecos.qeclib.steane.syn_extract.three_parallel_flagging import ( @@ -111,3 +111,91 @@ def __init__( scratch, ), ) + +class ParallelFlagQEC(Block): + """Defining QEC Block that does adaptive syndrome extraction, decodes, and updates the Paul frame.""" + + def __init__( + self, + q: QReg, + a: QReg, + flag_x: CReg, + flag_z: CReg, + flags: CReg, + syn_x: CReg, + syn_z: CReg, + last_raw_syn_x: CReg, + last_raw_syn_z: CReg, + syndromes: CReg, + pf_x: Bit, + pf_z: Bit, + scratch: CReg, + ) -> None: + """Initialize ParallelFlagQECActiveCorrection block for error correction. + + Args: + q: Data register containing the 7 qubits of the Steane code. + a: Ancilla register for syndrome extraction. + flag_x: Classical register for X stabilizer flags. + flag_z: Classical register for Z stabilizer flags. + flags: Combined flags register. + syn_x: Classical register for X syndromes. + syn_z: Classical register for Z syndromes. + last_raw_syn_x: Previous X syndrome measurements. + last_raw_syn_z: Previous Z syndrome measurements. + syndromes: Classical register for syndrome storage. + pf_x: Pauli frame bit for X errors. + pf_z: Pauli frame bit for Z errors. + scratch: Scratch classical register for intermediate calculations. + """ + super().__init__( + # flagging XZZ checks + ThreeParallelFlaggingXZZ( + q, + a, + flag_x, + flag_z, + flags, + last_raw_syn_x, + last_raw_syn_z, + ), + # flagging ZXX checks + If(flags == 0).Then( + ThreeParallelFlaggingZXX( + q, + a, + flag_x, + flag_z, + flags, + last_raw_syn_x, + last_raw_syn_z, + ), + ), + # Remeasure all the checks unflagged + If(flags != 0).Then( + SixUnflaggedSyn(q, a, syn_x, syn_z), + ), + + FlagLookupQASM( + basis="X", + syn=syn_x, + syndromes=syndromes, + raw_syn=last_raw_syn_x, + pf=pf_z, + flag=flag_x, + flags=flags, + scratch=scratch, + ), + + FlagLookupQASM( + basis="Z", + syn=syn_z, + syndromes=syndromes, + raw_syn=last_raw_syn_z, + pf=pf_x, + flag=flag_z, + flags=flags, + scratch=scratch, + ), + ) + diff --git a/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py b/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py index 81ed0fc4e..77a3eeed5 100644 --- a/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py +++ b/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py @@ -33,7 +33,7 @@ PrepEncodeTPlusFTRUS, PrepEncodeTPlusNonFT, ) -from pecos.qeclib.steane.qec.qec_3parallel import ParallelFlagQECActiveCorrection +from pecos.qeclib.steane.qec.qec_3parallel import ParallelFlagQECActiveCorrection, ParallelFlagQEC from pecos.slr import Block, CReg, If, Permute, QReg, Vars if TYPE_CHECKING: @@ -545,6 +545,46 @@ def qec(self, flag: Bit | None = None) -> Block: block.extend(If(self.flags != 0).Then(flag.set(1))) return block + def qec_not_active(self, flag: Bit | None = None, pf_x: Bit | None = None, pf_z: Bit | None = None,) -> Block: + """Perform quantum error correction using parallel flag-based without active correction. + + Args: + flag: Optional flag bit for conditional execution. + pf_x: Optional Pauli frame bit for X corrections + pf_z: Optional Pauli frame bit for Z corrections + + Returns: + Block containing the quantum error correction operations. + """ + + block = Block() + + if pf_x is None: + pf_x = self.pf_x + + if pf_z is None: + pf_z = self.pf_z + + block.extend(ParallelFlagQEC( + q=self.d, + a=self.a, + flag_x=self.flag_x, + flag_z=self.flag_z, + flags=self.flags, + syn_x=self.syn_x, + syn_z=self.syn_z, + last_raw_syn_x=self.last_raw_syn_x, + last_raw_syn_z=self.last_raw_syn_z, + syndromes=self.syndromes, + pf_x=pf_x, + pf_z=pf_z, + scratch=self.scratch, + )) + if flag is not None: + block.extend(If(self.flags != 0).Then(flag.set(1))) + + return block + def qec_steane( self, aux: Steane, From 2844bb88091b262ec7ff334f4dba02ebf2710513 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Thu, 31 Jul 2025 11:40:48 -0600 Subject: [PATCH 2/6] exporting more info for qec_not_active --- .../src/pecos/qeclib/steane/steane_class.py | 63 +++++++++++++++---- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py b/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py index 77a3eeed5..b2676fbf0 100644 --- a/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py +++ b/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py @@ -545,13 +545,34 @@ def qec(self, flag: Bit | None = None) -> Block: block.extend(If(self.flags != 0).Then(flag.set(1))) return block - def qec_not_active(self, flag: Bit | None = None, pf_x: Bit | None = None, pf_z: Bit | None = None,) -> Block: + def qec_not_active(self, flag: Bit | None = None, pf_x: Bit | None = None, pf_z: Bit | None = None, + flag_x: CReg | None = None, flag_z: CReg | None = None, + syn_x: CReg | None = None, syn_z: CReg | None = None,) -> Block: """Perform quantum error correction using parallel flag-based without active correction. + There are potentially three syndrome extraction paths take: + 0: XZZ flag_x = 000, flag_z = 000 -> ZXX flag_x = 000, flag_z = 000 -> Done + 1: XZZ flag_x = 00*, flag_z = **0 -> measure XXXZZZ (syn_x, syn_z) + 2: XZZ flag_x = 000, flag_z = 000 -> ZXX flag_x = **0, flag_z = 00* -> measure XXXZZZ (syn_x, syn_z) + (where at least one of the *s is 1) + + Therefore: + if flag_x & flag_z == 0, we went down path 0 + if flag_x[0] | flag_z[1] | flag_z[2] == 1, we went down path 1 + if flag_x[1] | flag_z[2] | flag_z[0] == 1, we went down path 2 + Args: flag: Optional flag bit for conditional execution. - pf_x: Optional Pauli frame bit for X corrections - pf_z: Optional Pauli frame bit for Z corrections + flag_x: Optional CReg of the syndrome measured by the X checks for the first two of flagged syndrome + extractions. It is a raw syndrome made during the first two rounds of syndrome extraction. + flag_z: Optional CReg of the syndrome measured by the X checks for the first two of flagged syndrome + extractions. It is a raw syndrome made during the first two rounds of syndrome extraction. + syn_x: Optional CReg of the syndrome measured by the X checks for the last round of non-flagged syndrome + extraction. It is a raw syndrome made during the final round of syndrome extraction. + syn_z: Optional CReg of the syndrome measured by the Z checks for the last round of non-flagged syndrome + extraction. It is a raw syndrome made during the final round of syndrome extraction. + pf_x: Optional Pauli frame bit for logical X corrections determined by lookup table decoder + pf_z: Optional Pauli frame bit for logical Z corrections determined by lookup table decoder Returns: Block containing the quantum error correction operations. @@ -559,12 +580,6 @@ def qec_not_active(self, flag: Bit | None = None, pf_x: Bit | None = None, pf_z: block = Block() - if pf_x is None: - pf_x = self.pf_x - - if pf_z is None: - pf_z = self.pf_z - block.extend(ParallelFlagQEC( q=self.d, a=self.a, @@ -576,12 +591,36 @@ def qec_not_active(self, flag: Bit | None = None, pf_x: Bit | None = None, pf_z: last_raw_syn_x=self.last_raw_syn_x, last_raw_syn_z=self.last_raw_syn_z, syndromes=self.syndromes, - pf_x=pf_x, - pf_z=pf_z, + pf_x=self.pf_x, + pf_z=self.pf_z, scratch=self.scratch, )) if flag is not None: - block.extend(If(self.flags != 0).Then(flag.set(1))) + block.extend( + If(self.flags != 0).Then(flag.set(1)) + ) + + if flag_x is not None: + assert(len(flag_x) == 3) + block.extend(flag_x.set(self.flag_x)) + + if flag_z is not None: + assert (len(flag_z) == 3) + block.extend(flag_z.set(self.flag_z)) + + if syn_x is not None: + assert (len(syn_x) == 3) + block.extend(syn_x.set(self.syn_x)) + + if syn_z is not None: + assert (len(syn_z) == 3) + block.extend(syn_z.set(self.syn_z)) + + if pf_x is not None: + block.extend(pf_x.set(self.pf_x)) + + if pf_z is not None: + block.extend(pf_z.set(self.pf_z)) return block From 21597a05a3bfa0008594c98c8e99da04d7771b94 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Thu, 31 Jul 2025 14:44:56 -0600 Subject: [PATCH 3/6] Adding bare and flagged syndrome extraction for Steane --- pyproject.toml | 1 - .../src/pecos/qeclib/generic/check.py | 14 +++- .../src/pecos/qeclib/generic/check_1flag.py | 45 +++++++++-- .../src/pecos/qeclib/steane/steane_class.py | 32 ++++++++ .../pecos/qeclib/steane/syn_extract/bare.py | 65 ++++++++++++++++ .../qeclib/steane/syn_extract/flagged.py | 75 +++++++++++++++++++ 6 files changed, 222 insertions(+), 10 deletions(-) create mode 100644 python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/bare.py create mode 100644 python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/flagged.py diff --git a/pyproject.toml b/pyproject.toml index 82f00524e..9ae2585bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,6 @@ members = [ [dependency-groups] dev = [ - "nanobind", "maturin>=1.2,<2.0", # For building (should match build requirements) "setuptools>=62.6", # Build system "pre-commit", # Git hooks diff --git a/python/quantum-pecos/src/pecos/qeclib/generic/check.py b/python/quantum-pecos/src/pecos/qeclib/generic/check.py index a2afe6883..1b52b043c 100644 --- a/python/quantum-pecos/src/pecos/qeclib/generic/check.py +++ b/python/quantum-pecos/src/pecos/qeclib/generic/check.py @@ -41,6 +41,8 @@ def __init__( paulis: str, a: Qubit, out: Bit, + *, + with_barriers: bool = False, ) -> None: """Initialize a stabilizer check measurement. @@ -84,12 +86,20 @@ def __init__( ) for i in range(n): + if with_barriers: + self.extend( + Barrier(a, d[i]), # to preserve order + ) + self.extend( - Barrier(a, d[i]), # to preserve order self.cp(ps[i], a, d[i]), - Barrier(a, d[i]), # to preserve order ) + if with_barriers: + self.extend( + Barrier(a, d[i]), # to preserve order + ) + self.extend( H(a), Measure(a) > out, diff --git a/python/quantum-pecos/src/pecos/qeclib/generic/check_1flag.py b/python/quantum-pecos/src/pecos/qeclib/generic/check_1flag.py index d98035a33..fed28b737 100644 --- a/python/quantum-pecos/src/pecos/qeclib/generic/check_1flag.py +++ b/python/quantum-pecos/src/pecos/qeclib/generic/check_1flag.py @@ -43,6 +43,8 @@ def __init__( flag: Qubit, out: Bit, out_flag: Bit, + *, + with_barriers: bool = False, ) -> None: """Initialize a stabilizer check measurement with flag qubit. @@ -83,25 +85,54 @@ def __init__( Comment(f"Measure check {ops}"), Prep(a, flag), H(a), - Barrier(a, d[0]), + ) + if with_barriers: + self.extend( + Barrier(a, d[0]), + ) + self.extend( self.cu(ops[0], a, d[0]), - Barrier(a, flag), + ) + if with_barriers: + self.extend( + Barrier(a, flag), + ) + self.extend( CX(a, flag), - Barrier(a, flag), ) + if with_barriers: + self.extend( + Barrier(a, flag), + ) for i in range(1, n - 1): self.extend( self.cu(ops[i], a, d[i]), - Barrier(a, d[i]), # To preserve order ) + if with_barriers: + self.extend( + Barrier(a, d[i]), # To preserve order + ) + if with_barriers: + self.extend( + Barrier(a, flag), + ) self.extend( - Barrier(a, flag), CX(a, flag), - Barrier(a, flag), + ) + if with_barriers: + self.extend( + Barrier(a, flag), + ) + self.extend( self.cu(ops[-1], a, d[-1]), - Barrier(a, d[-1]), + ) + if with_barriers: + self.extend( + Barrier(a, d[-1]), + ) + self.extend( H(a), Measure(a) > out, Measure(flag) > out_flag, diff --git a/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py b/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py index b2676fbf0..23e0d8afa 100644 --- a/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py +++ b/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py @@ -34,6 +34,8 @@ PrepEncodeTPlusNonFT, ) from pecos.qeclib.steane.qec.qec_3parallel import ParallelFlagQECActiveCorrection, ParallelFlagQEC +from pecos.qeclib.steane.syn_extract.bare import SynExtractBare +from pecos.qeclib.steane.syn_extract.flagged import SynExtractFlagged from pecos.slr import Block, CReg, If, Permute, QReg, Vars if TYPE_CHECKING: @@ -52,6 +54,7 @@ def __init__( name: str, default_rus_limit: int = 3, ancillas: QReg | None = None, + flag_qubits: QReg | None = None, ) -> None: """Initialize a Steane code instance with associated quantum and classical registers. @@ -65,8 +68,12 @@ def __init__( ValueError: If provided ancilla register has fewer than 3 qubits. """ super().__init__() + self.check_indices = [[3, 2, 4, 1], [6, 3, 2, 5], [7, 6, 3, 4]] + self.d = QReg(f"{name}_d", 7) self.a = ancillas or QReg(f"{name}_a", 3) + if flag_qubits is not None: + self.f = flag_qubits or QReg(f"{name}_f", 3) self.c = CReg(f"{name}_c", 32) if self.a.size < 3: @@ -98,6 +105,9 @@ def __init__( if ancillas is None: self.vars.append(self.a) + if flag_qubits is None: + self.vars.append(self.f) + self.vars.extend( [ self.c, @@ -778,12 +788,34 @@ def qec_tel_z( block.extend(If(self.syn_meas != 0).Then(flag.set(1))) return block + def qec_knill(self): + """prepare a Bell state and then teleport""" + # TODO: ... + ... + + def syn_bare(self, syn: CReg) -> Block: + """One single syndrome bit per check using bare syndrome extraction""" + return SynExtractBare(self.d, self.a, self.check_indices, syn) + + def syn_flagged(self, syn: CReg, flags: CReg) -> Block: + """One single syndrome bit and one single flag bit per check""" + return SynExtractFlagged(self.d, self.a, self.f, self.check_indices, syn, flags) + + def syn_2para_v1_flagged(self): + # TODO: ... + ... + + def syn_2para_v2_flagged(self): + # TODO: ... + ... + def permute(self, other: Steane) -> Block: """Permute this code block (including both quantum and classical registers) with another.""" block = Block( Permute(self.d, other.d), Permute(self.a, other.a), ) + # TODO: Use Permute on classical variables rather that a custom solution for var_a, var_b in zip(self.vars, other.vars, strict=False): if isinstance(var_a, CReg): block.extend( diff --git a/python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/bare.py b/python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/bare.py new file mode 100644 index 000000000..c59706de8 --- /dev/null +++ b/python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/bare.py @@ -0,0 +1,65 @@ +from itertools import cycle +from typing import Any + +from pecos.qeclib.generic.check import Check +from pecos.slr import (Block, Comment, CReg, QReg) + +def poly2qubits(poly: list[Any], data: QReg) -> list[Any]: + """Convert polygon node IDs to qubit references. + + Args: + poly: Polygon representation with node IDs and color. + data: Quantum register containing the data qubits. + + Returns: + List of qubit references corresponding to the polygon nodes. + """ + return [data[q] for q in poly] + +class SynExtractBare(Block): + + def __init__(self, data: QReg, ancillas: QReg, checks: list, syn: CReg) -> None: + """Initialize bare syndrome extraction. + + Args: + data: Data qubit register. + ancillas: Ancilla qubit register. + syn: Classical register for syndrome storage. + """ + assert(len(syn) == 2*len(checks) == 6) + a = cycle(range(len(ancillas))) + s = iter(range(len(syn))) + + super().__init__() + + pauli = "Z" + for c in checks: + data_ids = c[:-1] + syn_id = next(s) + anc_id = next(a) + self.extend( + Comment(f"Check['{pauli}', {data_ids}] -> {syn}[{syn_id}]"), + Check( + d=poly2qubits(c, data), + paulis=pauli, + a=ancillas[anc_id], + out=syn[syn_id], + with_barriers=False, + ), + ) + + pauli = "X" + for c in checks: + data_ids = c[:-1] + syn_id = next(s) + anc_id = next(a) + self.extend( + Comment(f"Check['{pauli}', {data_ids}] -> {syn}[{syn_id}]"), + Check( + d=poly2qubits(c, data), + paulis=pauli, + a=ancillas[anc_id], + out=syn[syn_id], + with_barriers=False, + ), + ) diff --git a/python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/flagged.py b/python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/flagged.py new file mode 100644 index 000000000..196059d49 --- /dev/null +++ b/python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/flagged.py @@ -0,0 +1,75 @@ +from itertools import cycle +from typing import Any + +from pecos.qeclib.generic.check_1flag import Check1Flag +from pecos.slr import (Block, Comment, CReg, QReg) + +def poly2qubits(poly: list[Any], data: QReg) -> list[Any]: + """Convert polygon node IDs to qubit references. + + Args: + poly: Polygon representation with node IDs and color. + data: Quantum register containing the data qubits. + + Returns: + List of qubit references corresponding to the polygon nodes. + """ + return [data[q] for q in poly] + +class SynExtractFlagged(Block): + + def __init__(self, data: QReg, ancillas: QReg, flag_qubits: QReg, checks: list, syn: CReg, flag_bits: CReg) -> None: + """Initialize flagged syndrome extraction. + + Args: + data: Data qubit register. + ancillas: Ancilla qubit register. + syn: Classical register for syndrome storage. + """ + assert(len(syn) == len(flag_bits) == 2*len(checks) == 6) + a = cycle(range(len(ancillas))) + f = cycle(range(len(flag_qubits))) + s = iter(range(len(syn))) + fb = iter(range(len(flag_bits))) + + super().__init__() + + pauli = "Z" + for c in checks: + data_ids = c[:-1] + syn_id = next(s) + anc_id = next(a) + flag_qubit_id = next(f) + flag_bit_id = next(fb) + self.extend( + Comment(f"Check['{pauli}', {data_ids}] -> {syn}[{syn_id}]"), + Check1Flag( + d=poly2qubits(c, data), + ops=pauli, + a=ancillas[anc_id], + flag=flag_qubits[flag_qubit_id], + out=syn[syn_id], + out_flag=flag_bits[flag_bit_id], + with_barriers=False, + ), + ) + + pauli = "X" + for c in checks: + data_ids = c[:-1] + syn_id = next(s) + anc_id = next(a) + flag_qubit_id = next(f) + flag_bit_id = next(fb) + self.extend( + Comment(f"Check['{pauli}', {data_ids}] -> {syn}[{syn_id}]"), + Check1Flag( + d=poly2qubits(c, data), + ops=pauli, + a=ancillas[anc_id], + flag=flag_qubits[flag_qubit_id], + out=syn[syn_id], + out_flag=flag_bits[flag_bit_id], + with_barriers=False, + ), + ) From 2c1c25785628356dfda6b182ca16c3f3835a9fdb Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Sun, 17 Aug 2025 18:58:13 -0600 Subject: [PATCH 4/6] Adding not implemented error for some steane code functions --- python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py b/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py index 23e0d8afa..1528ca5bb 100644 --- a/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py +++ b/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py @@ -792,6 +792,7 @@ def qec_knill(self): """prepare a Bell state and then teleport""" # TODO: ... ... + raise NotImplementedError("qec_knill not implemented.") def syn_bare(self, syn: CReg) -> Block: """One single syndrome bit per check using bare syndrome extraction""" @@ -803,11 +804,11 @@ def syn_flagged(self, syn: CReg, flags: CReg) -> Block: def syn_2para_v1_flagged(self): # TODO: ... - ... + raise NotImplementedError("syn_2para_v1_flagged not implemented.") def syn_2para_v2_flagged(self): # TODO: ... - ... + raise NotImplementedError("syn_2para_v2_flagged not implemented.") def permute(self, other: Steane) -> Block: """Permute this code block (including both quantum and classical registers) with another.""" From fb28dbbd0c2be08aa8c4f761fa83b0a09e65f055 Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Sun, 17 Aug 2025 19:48:09 -0600 Subject: [PATCH 5/6] fix --- .../pecos/qeclib/steane/preps/plus_h_state.py | 1 + .../src/pecos/qeclib/steane/steane_class.py | 3 ++- .../src/pecos/slr/gen_codes/gen_qasm.py | 8 ++++++-- uv.lock | 17 +++-------------- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/python/quantum-pecos/src/pecos/qeclib/steane/preps/plus_h_state.py b/python/quantum-pecos/src/pecos/qeclib/steane/preps/plus_h_state.py index cc035671a..c8a933b40 100644 --- a/python/quantum-pecos/src/pecos/qeclib/steane/preps/plus_h_state.py +++ b/python/quantum-pecos/src/pecos/qeclib/steane/preps/plus_h_state.py @@ -89,6 +89,7 @@ def __init__( flag=a[1], out=out[0], out_flag=out[1], + with_barriers=True, ), ) diff --git a/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py b/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py index 1528ca5bb..4124ac744 100644 --- a/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py +++ b/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py @@ -74,6 +74,8 @@ def __init__( self.a = ancillas or QReg(f"{name}_a", 3) if flag_qubits is not None: self.f = flag_qubits or QReg(f"{name}_f", 3) + else: + self.f = None self.c = CReg(f"{name}_c", 32) if self.a.size < 3: @@ -791,7 +793,6 @@ def qec_tel_z( def qec_knill(self): """prepare a Bell state and then teleport""" # TODO: ... - ... raise NotImplementedError("qec_knill not implemented.") def syn_bare(self, syn: CReg) -> Block: diff --git a/python/quantum-pecos/src/pecos/slr/gen_codes/gen_qasm.py b/python/quantum-pecos/src/pecos/slr/gen_codes/gen_qasm.py index 204a6ab77..61eb0dcef 100644 --- a/python/quantum-pecos/src/pecos/slr/gen_codes/gen_qasm.py +++ b/python/quantum-pecos/src/pecos/slr/gen_codes/gen_qasm.py @@ -53,17 +53,21 @@ def enter_block(self, block): self.write(f"// Generated using: PECOS version {__version__}") for var in block.vars: var_def = self.process_var_def(var) - self.write(var_def) + if var_def: + self.write(var_def) for op in block.ops: op_name = type(op).__name__ if op_name == "Vars": for var in op.vars: var_def = self.process_var_def(var) - self.write(var_def) + if var_def: + self.write(var_def) return previous_scope def process_var_def(self, var) -> str: + if var is None: + return "" var_type = type(var).__name__ return f"{var_type.lower()} {var.sym}[{var.size}];" diff --git a/uv.lock b/uv.lock index f9266783e..7d3648e69 100644 --- a/uv.lock +++ b/uv.lock @@ -920,15 +920,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] -[[package]] -name = "nanobind" -version = "2.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/7c/cc6e525b89cca06b9fab7ecb91a11c3fd8258b158897cc804679fcc7b203/nanobind-2.8.0.tar.gz", hash = "sha256:94e7bf6aa1d7dff9566eddc15252aba94fdadbf67a99a169bfab34b708427cd8", size = 979153, upload-time = "2025-07-16T05:52:17.547Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/3f/d81fa4c0d1450c6f58f5b5708082617949c12c5e98c1fae0d94a9e3a9b8f/nanobind-2.8.0-py3-none-any.whl", hash = "sha256:80fd403cfe4c8553b237ba5fbb62971921e2c5d1e6eb4a2fd457c67f987ab56f", size = 242647, upload-time = "2025-07-16T05:52:15.848Z" }, -] - [[package]] name = "networkx" version = "3.4.2" @@ -1117,12 +1108,12 @@ wheels = [ [[package]] name = "pecos-rslib" -version = "0.7.0.dev3" +version = "0.7.0.dev4" source = { editable = "python/pecos-rslib" } [[package]] name = "pecos-workspace" -version = "0.7.0.dev3" +version = "0.7.0.dev4" source = { virtual = "." } dependencies = [ { name = "wasmer" }, @@ -1138,7 +1129,6 @@ dev = [ { name = "mkdocs" }, { name = "mkdocs-material" }, { name = "mkdocstrings", extra = ["python"] }, - { name = "nanobind" }, { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "networkx", version = "3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -1172,7 +1162,6 @@ dev = [ { name = "mkdocs" }, { name = "mkdocs-material" }, { name = "mkdocstrings", extras = ["python"] }, - { name = "nanobind" }, { name = "networkx", specifier = ">=2.1.0" }, { name = "numpy", specifier = ">=1.15.0" }, { name = "phir", specifier = ">=0.3.3" }, @@ -1619,7 +1608,7 @@ wheels = [ [[package]] name = "quantum-pecos" -version = "0.7.0.dev3" +version = "0.7.0.dev4" source = { editable = "python/quantum-pecos" } dependencies = [ { name = "matplotlib" }, From dae9c4c0c78ba770f65478c3021ace40cd71004a Mon Sep 17 00:00:00 2001 From: Ciaran Ryan-Anderson Date: Sun, 17 Aug 2025 22:27:28 -0600 Subject: [PATCH 6/6] lint --- .../src/pecos/qeclib/generic/check.py | 6 +- .../src/pecos/qeclib/generic/check_1flag.py | 2 + .../pecos/qeclib/steane/qec/qec_3parallel.py | 7 +- .../src/pecos/qeclib/steane/steane_class.py | 110 ++++++++++++------ .../pecos/qeclib/steane/syn_extract/bare.py | 19 ++- .../qeclib/steane/syn_extract/flagged.py | 35 +++++- 6 files changed, 130 insertions(+), 49 deletions(-) diff --git a/python/quantum-pecos/src/pecos/qeclib/generic/check.py b/python/quantum-pecos/src/pecos/qeclib/generic/check.py index 1b52b043c..fe9b7694a 100644 --- a/python/quantum-pecos/src/pecos/qeclib/generic/check.py +++ b/python/quantum-pecos/src/pecos/qeclib/generic/check.py @@ -52,6 +52,8 @@ def __init__( Can be a single character (applied to all qubits) or one character per qubit. a: Ancilla qubit used for the check measurement. out: Classical bit to store the measurement result. + with_barriers: Whether to insert barrier instructions between operations to prevent + gate reordering. Defaults to False. Raises: Exception: If check weight is less than 2. @@ -88,7 +90,7 @@ def __init__( for i in range(n): if with_barriers: self.extend( - Barrier(a, d[i]), # to preserve order + Barrier(a, d[i]), # to preserve order ) self.extend( @@ -97,7 +99,7 @@ def __init__( if with_barriers: self.extend( - Barrier(a, d[i]), # to preserve order + Barrier(a, d[i]), # to preserve order ) self.extend( diff --git a/python/quantum-pecos/src/pecos/qeclib/generic/check_1flag.py b/python/quantum-pecos/src/pecos/qeclib/generic/check_1flag.py index fed28b737..85897fb4b 100644 --- a/python/quantum-pecos/src/pecos/qeclib/generic/check_1flag.py +++ b/python/quantum-pecos/src/pecos/qeclib/generic/check_1flag.py @@ -56,6 +56,8 @@ def __init__( flag: Flag qubit used to detect hook errors. out: Classical bit to store the measurement result. out_flag: Classical bit to store the flag measurement result. + with_barriers: Whether to insert barrier instructions between operations to prevent + gate reordering. Defaults to False. Raises: Exception: If check weight is less than 3. diff --git a/python/quantum-pecos/src/pecos/qeclib/steane/qec/qec_3parallel.py b/python/quantum-pecos/src/pecos/qeclib/steane/qec/qec_3parallel.py index dbbf0bc4f..980d4f1f3 100644 --- a/python/quantum-pecos/src/pecos/qeclib/steane/qec/qec_3parallel.py +++ b/python/quantum-pecos/src/pecos/qeclib/steane/qec/qec_3parallel.py @@ -16,8 +16,9 @@ # specific language governing permissions and limitations under the License. from pecos.qeclib.steane.decoders.lookup import ( + FlagLookupQASM, FlagLookupQASMActiveCorrectionX, - FlagLookupQASMActiveCorrectionZ, FlagLookupQASM, + FlagLookupQASMActiveCorrectionZ, ) from pecos.qeclib.steane.syn_extract.six_check_nonflagging import SixUnflaggedSyn from pecos.qeclib.steane.syn_extract.three_parallel_flagging import ( @@ -112,6 +113,7 @@ def __init__( ), ) + class ParallelFlagQEC(Block): """Defining QEC Block that does adaptive syndrome extraction, decodes, and updates the Paul frame.""" @@ -175,7 +177,6 @@ def __init__( If(flags != 0).Then( SixUnflaggedSyn(q, a, syn_x, syn_z), ), - FlagLookupQASM( basis="X", syn=syn_x, @@ -186,7 +187,6 @@ def __init__( flags=flags, scratch=scratch, ), - FlagLookupQASM( basis="Z", syn=syn_z, @@ -198,4 +198,3 @@ def __init__( scratch=scratch, ), ) - diff --git a/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py b/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py index 4124ac744..3593154a9 100644 --- a/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py +++ b/python/quantum-pecos/src/pecos/qeclib/steane/steane_class.py @@ -17,7 +17,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, NoReturn from warnings import warn from pecos.qeclib.steane.decoders.lookup import ( @@ -33,7 +33,10 @@ PrepEncodeTPlusFTRUS, PrepEncodeTPlusNonFT, ) -from pecos.qeclib.steane.qec.qec_3parallel import ParallelFlagQECActiveCorrection, ParallelFlagQEC +from pecos.qeclib.steane.qec.qec_3parallel import ( + ParallelFlagQEC, + ParallelFlagQECActiveCorrection, +) from pecos.qeclib.steane.syn_extract.bare import SynExtractBare from pecos.qeclib.steane.syn_extract.flagged import SynExtractFlagged from pecos.slr import Block, CReg, If, Permute, QReg, Vars @@ -63,6 +66,8 @@ def __init__( default_rus_limit: Default limit for repeat-until-success procedures. Defaults to 3. ancillas: Optional pre-existing ancilla register. If None, creates a new 3-qubit ancilla register. Must have at least 3 qubits if provided. + flag_qubits: Optional pre-existing flag qubit register. If None, creates a new 3-qubit + flag register when needed. Raises: ValueError: If provided ancilla register has fewer than 3 qubits. @@ -557,9 +562,16 @@ def qec(self, flag: Bit | None = None) -> Block: block.extend(If(self.flags != 0).Then(flag.set(1))) return block - def qec_not_active(self, flag: Bit | None = None, pf_x: Bit | None = None, pf_z: Bit | None = None, - flag_x: CReg | None = None, flag_z: CReg | None = None, - syn_x: CReg | None = None, syn_z: CReg | None = None,) -> Block: + def qec_not_active( + self, + flag: Bit | None = None, + pf_x: Bit | None = None, + pf_z: Bit | None = None, + flag_x: CReg | None = None, + flag_z: CReg | None = None, + syn_x: CReg | None = None, + syn_z: CReg | None = None, + ) -> Block: """Perform quantum error correction using parallel flag-based without active correction. There are potentially three syndrome extraction paths take: @@ -589,46 +601,55 @@ def qec_not_active(self, flag: Bit | None = None, pf_x: Bit | None = None, pf_z: Returns: Block containing the quantum error correction operations. """ - block = Block() - block.extend(ParallelFlagQEC( - q=self.d, - a=self.a, - flag_x=self.flag_x, - flag_z=self.flag_z, - flags=self.flags, - syn_x=self.syn_x, - syn_z=self.syn_z, - last_raw_syn_x=self.last_raw_syn_x, - last_raw_syn_z=self.last_raw_syn_z, - syndromes=self.syndromes, - pf_x=self.pf_x, - pf_z=self.pf_z, - scratch=self.scratch, - )) + block.extend( + ParallelFlagQEC( + q=self.d, + a=self.a, + flag_x=self.flag_x, + flag_z=self.flag_z, + flags=self.flags, + syn_x=self.syn_x, + syn_z=self.syn_z, + last_raw_syn_x=self.last_raw_syn_x, + last_raw_syn_z=self.last_raw_syn_z, + syndromes=self.syndromes, + pf_x=self.pf_x, + pf_z=self.pf_z, + scratch=self.scratch, + ), + ) if flag is not None: block.extend( - If(self.flags != 0).Then(flag.set(1)) + If(self.flags != 0).Then(flag.set(1)), ) - if flag_x is not None: - assert(len(flag_x) == 3) + if flag_x is not None: + if len(flag_x) != 3: + msg = f"flag_x must have length 3, got {len(flag_x)}" + raise ValueError(msg) block.extend(flag_x.set(self.flag_x)) if flag_z is not None: - assert (len(flag_z) == 3) + if len(flag_z) != 3: + msg = f"flag_z must have length 3, got {len(flag_z)}" + raise ValueError(msg) block.extend(flag_z.set(self.flag_z)) - if syn_x is not None: - assert (len(syn_x) == 3) + if syn_x is not None: + if len(syn_x) != 3: + msg = f"syn_x must have length 3, got {len(syn_x)}" + raise ValueError(msg) block.extend(syn_x.set(self.syn_x)) if syn_z is not None: - assert (len(syn_z) == 3) + if len(syn_z) != 3: + msg = f"syn_z must have length 3, got {len(syn_z)}" + raise ValueError(msg) block.extend(syn_z.set(self.syn_z)) - if pf_x is not None: + if pf_x is not None: block.extend(pf_x.set(self.pf_x)) if pf_z is not None: @@ -790,26 +811,39 @@ def qec_tel_z( block.extend(If(self.syn_meas != 0).Then(flag.set(1))) return block - def qec_knill(self): - """prepare a Bell state and then teleport""" + def qec_knill(self) -> NoReturn: + """Prepare a Bell state and then teleport.""" # TODO: ... - raise NotImplementedError("qec_knill not implemented.") + msg = "qec_knill not implemented." + raise NotImplementedError(msg) def syn_bare(self, syn: CReg) -> Block: - """One single syndrome bit per check using bare syndrome extraction""" + """One single syndrome bit per check using bare syndrome extraction.""" return SynExtractBare(self.d, self.a, self.check_indices, syn) def syn_flagged(self, syn: CReg, flags: CReg) -> Block: - """One single syndrome bit and one single flag bit per check""" + """One single syndrome bit and one single flag bit per check.""" return SynExtractFlagged(self.d, self.a, self.f, self.check_indices, syn, flags) - def syn_2para_v1_flagged(self): + def syn_2para_v1_flagged(self) -> NoReturn: + """Two-parallel syndrome extraction version 1 with flagging (not implemented). + + Raises: + NotImplementedError: This method is not yet implemented. + """ # TODO: ... - raise NotImplementedError("syn_2para_v1_flagged not implemented.") + msg = "syn_2para_v1_flagged not implemented." + raise NotImplementedError(msg) + + def syn_2para_v2_flagged(self) -> NoReturn: + """Two-parallel syndrome extraction version 2 with flagging (not implemented). - def syn_2para_v2_flagged(self): + Raises: + NotImplementedError: This method is not yet implemented. + """ # TODO: ... - raise NotImplementedError("syn_2para_v2_flagged not implemented.") + msg = "syn_2para_v2_flagged not implemented." + raise NotImplementedError(msg) def permute(self, other: Steane) -> Block: """Permute this code block (including both quantum and classical registers) with another.""" diff --git a/python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/bare.py b/python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/bare.py index c59706de8..63c0a3ced 100644 --- a/python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/bare.py +++ b/python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/bare.py @@ -1,8 +1,15 @@ +"""Bare syndrome extraction implementations for the Steane code. + +This module provides syndrome extraction without flag qubits for detecting +errors in the Steane 7-qubit quantum error correction code. +""" + from itertools import cycle from typing import Any from pecos.qeclib.generic.check import Check -from pecos.slr import (Block, Comment, CReg, QReg) +from pecos.slr import Block, Comment, CReg, QReg + def poly2qubits(poly: list[Any], data: QReg) -> list[Any]: """Convert polygon node IDs to qubit references. @@ -16,7 +23,9 @@ def poly2qubits(poly: list[Any], data: QReg) -> list[Any]: """ return [data[q] for q in poly] + class SynExtractBare(Block): + """Bare syndrome extraction for Steane code without flag qubits.""" def __init__(self, data: QReg, ancillas: QReg, checks: list, syn: CReg) -> None: """Initialize bare syndrome extraction. @@ -24,9 +33,15 @@ def __init__(self, data: QReg, ancillas: QReg, checks: list, syn: CReg) -> None: Args: data: Data qubit register. ancillas: Ancilla qubit register. + checks: List of check operators to apply. syn: Classical register for syndrome storage. + + Raises: + ValueError: If syndrome register length doesn't match expected size. """ - assert(len(syn) == 2*len(checks) == 6) + if len(syn) != 2 * len(checks) or len(syn) != 6: + msg = f"Expected syndrome register of length 6 (2 * {len(checks)} checks), got {len(syn)}" + raise ValueError(msg) a = cycle(range(len(ancillas))) s = iter(range(len(syn))) diff --git a/python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/flagged.py b/python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/flagged.py index 196059d49..305a46a72 100644 --- a/python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/flagged.py +++ b/python/quantum-pecos/src/pecos/qeclib/steane/syn_extract/flagged.py @@ -1,8 +1,16 @@ +"""Flagged syndrome extraction implementations for the Steane code. + +This module provides syndrome extraction with flag qubits for detecting +and diagnosing errors during the syndrome extraction process in the +Steane 7-qubit quantum error correction code. +""" + from itertools import cycle from typing import Any from pecos.qeclib.generic.check_1flag import Check1Flag -from pecos.slr import (Block, Comment, CReg, QReg) +from pecos.slr import Block, Comment, CReg, QReg + def poly2qubits(poly: list[Any], data: QReg) -> list[Any]: """Convert polygon node IDs to qubit references. @@ -16,17 +24,38 @@ def poly2qubits(poly: list[Any], data: QReg) -> list[Any]: """ return [data[q] for q in poly] + class SynExtractFlagged(Block): + """Flagged syndrome extraction for Steane code with flag qubits for error detection.""" - def __init__(self, data: QReg, ancillas: QReg, flag_qubits: QReg, checks: list, syn: CReg, flag_bits: CReg) -> None: + def __init__( + self, + data: QReg, + ancillas: QReg, + flag_qubits: QReg, + checks: list, + syn: CReg, + flag_bits: CReg, + ) -> None: """Initialize flagged syndrome extraction. Args: data: Data qubit register. ancillas: Ancilla qubit register. + flag_qubits: Flag qubit register for hook error detection. + checks: List of check operators to apply. syn: Classical register for syndrome storage. + flag_bits: Classical register for flag bit storage. + + Raises: + ValueError: If register lengths don't match expected sizes. """ - assert(len(syn) == len(flag_bits) == 2*len(checks) == 6) + if not (len(syn) == len(flag_bits) == 2 * len(checks) == 6): + msg = ( + f"Expected syndrome and flag registers of length 6 (2 * {len(checks)} checks), " + f"got syn={len(syn)}, flag_bits={len(flag_bits)}" + ) + raise ValueError(msg) a = cycle(range(len(ancillas))) f = cycle(range(len(flag_qubits))) s = iter(range(len(syn)))