diff --git a/src/qldpc/circuits/transversal.py b/src/qldpc/circuits/transversal.py index 09c26cb8d..1b535e580 100644 --- a/src/qldpc/circuits/transversal.py +++ b/src/qldpc/circuits/transversal.py @@ -96,6 +96,9 @@ def get_transversal_automorphism_group( Uses the methods of https://arxiv.org/abs/2409.18175. """ + if not local_gates: + return abstract.Group(abstract.GroupMember(range(len(code)))) + local_gates = _validate_local_gates(local_gates) allow_swaps = "SWAP" in local_gates local_gates.discard("SWAP") @@ -122,10 +125,31 @@ def get_transversal_automorphism_group( # we are looking for transversal gates involving only two-qubit SWAPs matrix: npt.NDArray[np.int_] = parity_checks _code = codes.QuditCode(matrix).maybe_to_css() - if isinstance(_code, codes.CSSCode) and np.array_equal( - (canonicalized_code := _code.canonicalized).matrix_x, canonicalized_code.matrix_z - ): - matrix = canonicalized_code.matrix_x + if isinstance(_code, codes.CSSCode): + canonicalized_code = _code.canonicalized + if np.array_equal(canonicalized_code.matrix_x, canonicalized_code.matrix_z): + # Self-dual CSS: column permutations preserving H_x automatically preserve H_z + matrix = canonicalized_code.matrix_x + else: + # Non-self-dual CSS: the joint parity-check matrix has block-diagonal support, so + # its column-permutation automorphism group factors as Aut(H_x) x Aut(H_z) on + # disjoint column sets. SWAPs act diagonally on X- and Z-columns, so the SWAP + # transversal group is Aut(H_x) ∩ Aut(H_z). Recurse on fake self-dual codes built + # from H_x and H_z so the self-dual branch above handles each half, then intersect. + fake_code_x = codes.CSSCode( + canonicalized_code.matrix_x, canonicalized_code.matrix_x + ) + fake_code_z = codes.CSSCode( + canonicalized_code.matrix_z, canonicalized_code.matrix_z + ) + group_x = get_transversal_automorphism_group( + fake_code_x, ["SWAP"], deform_code=False, with_magma=with_magma + ) + group_z = get_transversal_automorphism_group( + fake_code_z, ["SWAP"], deform_code=False, with_magma=with_magma + ) + generators = _sympy_group_intersection_generators(group_x, group_z) + return abstract.Group(*map(abstract.GroupMember, generators)) else: # we are looking for transversal gates involving two-qubit SWAPs and single-qubit Cliffords diff --git a/src/qldpc/circuits/transversal_test.py b/src/qldpc/circuits/transversal_test.py index 005afb490..fe9528c62 100644 --- a/src/qldpc/circuits/transversal_test.py +++ b/src/qldpc/circuits/transversal_test.py @@ -28,17 +28,22 @@ def test_transversal_ops() -> None: """Construct SWAP-transversal logical Cliffords of a code.""" - code = codes.ToricCode(2) + code: codes.CSSCode - for local_gates in [ - ["SWAP"], - ["SWAP", "H"], - ["SWAP", "S"], - ["SWAP", "SQRT_X"], - ["SWAP", "H", "S"], - ]: - transversal_ops = circuits.get_transversal_ops(code, local_gates) - assert len(transversal_ops) == len(local_gates) + 1 + code = codes.ToricCode(2) + assert len(circuits.get_transversal_ops(code, [])) == 0 + assert len(circuits.get_transversal_ops(code, ["SWAP"])) == 2 + assert len(circuits.get_transversal_ops(code, ["SWAP", "H"])) == 3 + assert len(circuits.get_transversal_ops(code, ["SWAP", "S"])) == 3 + assert len(circuits.get_transversal_ops(code, ["SWAP", "SQRT_X"])) == 3 + assert len(circuits.get_transversal_ops(code, ["SWAP", "H", "S"])) == 4 + + # non-self-dual [[8, 3, 2]] CSS code with nontrivial permutation automorphisms + code = codes.CSSCode( + [[1, 1, 1, 1, 0, 0, 0, 0], [1, 1, 0, 0, 1, 1, 0, 0], [0, 0, 1, 1, 0, 0, 1, 1]], + [[1, 0, 1, 0, 1, 0, 1, 0], [0, 1, 0, 1, 0, 1, 0, 1]], + ) + assert len(circuits.get_transversal_ops(code, ["SWAP"])) == 3 with pytest.raises(ValueError, match="Local Clifford gates"): circuits.get_transversal_automorphism_group(code, ["SQRT_Y"]) diff --git a/src/qldpc/external/groups.py b/src/qldpc/external/groups.py index fe72cd6a3..d728de555 100644 --- a/src/qldpc/external/groups.py +++ b/src/qldpc/external/groups.py @@ -371,13 +371,15 @@ def get_primitive_central_idempotents(group: str, field: int) -> IdempotentsList "SmallGroup(1,1)": [[]], "Group(())": [[]], "SmallGroup(21,1)": [[(1, 2, 4), (3, 6, 5)], [(0, 1, 2, 3, 4, 5, 6)]], + # FiveQubitCode automorphism group (SWAP only) "AutomorphismGroup(CheckMatCode([[1,0,0,0,1,1,1,0,1,1],[0,1,0,0,1,0,0,1,1,0],[0,0,1,0,1,1,1,0,0,0],[0,0,0,1,1,1,0,1,1,1]],GF(2)))": [ [(3, 7), (4, 5), (8, 9)], [(1, 5), (2, 7), (3, 9), (6, 8)], [(2, 6), (3, 8), (4, 5), (7, 9)], [(0, 9, 2, 8), (1, 6), (3, 5, 4, 7)], [(0, 7, 3), (1, 9, 8), (2, 4, 5)], - ], # FiveQubitCode automorphism group (SWAP only) + ], + # FiveQubitCode automorphism group (SWAP + Cliffords) "AutomorphismGroup(CheckMatCode([[1,0,0,0,1,1,1,0,1,1,0,1,0,1,0],[0,1,0,0,1,0,0,1,1,0,0,1,1,1,1],[0,0,1,0,1,1,1,0,0,0,1,1,1,0,1],[0,0,0,1,1,1,0,1,1,1,1,0,1,0,0]],GF(2)))": [ [(0, 1), (5, 12), (6, 14), (7, 9)], [(2, 3), (6, 9), (7, 14), (8, 11)], @@ -388,12 +390,14 @@ def get_primitive_central_idempotents(group: str, field: int) -> IdempotentsList [(2, 11), (3, 8), (6, 14), (7, 9)], [(3, 8), (4, 10), (5, 12), (7, 9)], [(3, 9), (4, 12), (5, 10), (7, 8)], - ], # FiveQubitCode automorphism group (SWAP + Cliffords) + ], + # ToricCode(2) automorphism group (SWAP only) "AutomorphismGroup(CheckMatCode([[1,1,1,1]],GF(2)))": [ [(0, 1)], [(1, 2)], [(2, 3)], - ], # ToricCode(2) automorphism group (SWAP only) + ], + # ToricCode(2) automorphism group (SWAP + H/S/SQRT_X) "AutomorphismGroup(CheckMatCode([[1,1,1,1,0,0,0,0],[0,0,0,0,1,1,1,1]],GF(2)))": [ [(2, 3), (4, 7), (5, 6)], [(4, 7, 6, 5)], @@ -403,7 +407,8 @@ def get_primitive_central_idempotents(group: str, field: int) -> IdempotentsList [(1, 3), (4, 6), (5, 7)], [(0, 6, 3, 7), (1, 5), (2, 4)], [(0, 7), (1, 4), (2, 5, 3, 6)], - ], # ToricCode(2) automorphism group (SWAP + H/S/SQRT_X) + ], + # ToricCode(2) automorphism group (SWAP + Cliffords) "AutomorphismGroup(CheckMatCode([[1,1,1,1,0,0,0,0,1,1,1,1],[0,0,0,0,1,1,1,1,1,1,1,1]],GF(2)))": [ [(4, 9), (5, 8, 6, 11), (7, 10)], [(4, 7, 6, 5), (9, 11, 10)], @@ -419,7 +424,28 @@ def get_primitive_central_idempotents(group: str, field: int) -> IdempotentsList [(0, 4, 10, 1, 5, 9, 2, 7, 8), (3, 6, 11)], [(2, 3), (4, 7), (8, 11)], [(0, 10, 2, 11, 1, 9), (3, 8), (4, 7)], - ], # ToricCode(2) automorphism group (SWAP + Cliffords) + ], + # [[8, 3, 2]] X check automorphism group + "AutomorphismGroup(CheckMatCode([[1,1,0,0,0,0,1,1],[0,0,1,1,0,0,1,1],[0,0,0,0,1,1,1,1]],GF(2)))": [ + [(6, 7)], + [(4, 5), (6, 7)], + [(4, 7, 5, 6)], + [(2, 3), (6, 7)], + [(2, 6, 4), (3, 7, 5)], + [(0, 3, 5, 1, 2, 4)], + [(0, 7), (1, 6), (2, 3), (4, 5)], + ], + # [[8, 3, 2]] Z check automorphism group + "AutomorphismGroup(CheckMatCode([[1,0,1,0,1,0,1,0],[0,1,0,1,0,1,0,1]],GF(2)))": [ + [(1, 7), (3, 5), (4, 6)], + [(1, 7, 5, 3)], + [(5, 7)], + [(3, 5, 7)], + [(1, 3, 5), (2, 4, 6)], + [(1, 5), (2, 6), (3, 7)], + [(0, 5, 6, 7), (1, 4), (2, 3)], + [(0, 7), (1, 2), (3, 6, 5, 4)], + ], } KNOWN_PRIMITIVE_CENTRAL_IDEMPOTENTS: dict[tuple[str, int], IdempotentsList] = {