diff --git a/src/qldpc/objects.py b/src/qldpc/objects.py index 369b1c1cd..f380b30f0 100644 --- a/src/qldpc/objects.py +++ b/src/qldpc/objects.py @@ -57,7 +57,7 @@ def swap_xz(self) -> PauliXZ: if self is Pauli.Z: return Pauli.X raise ValueError( - f"Pauli.dual_xz only converts between Pauli.X and Pauli.Z (provided: {self})" + f"Pauli.swap_xz only converts between Pauli.X and Pauli.Z (provided: {self})" ) def __str__(self) -> str: @@ -144,10 +144,10 @@ def from_string(string: str) -> QuditPauli: raise ValueError(invalid_op) for factor in factors: - pauli = factor[0] + pauli = factor[:1] val_str = factor[2:-1] _factor = f"{pauli}({val_str})" - if pauli not in "XYZ" or not val_str.isnumeric() or factor != _factor: + if pauli not in "XYZ" or not val_str.isdecimal() or factor != _factor: raise ValueError(invalid_op) val = int(val_str) @@ -172,9 +172,6 @@ class Node: index: int is_data: bool = True - def __hash__(self) -> int: - return hash((self.index, self.is_data)) - def __lt__(self, other: Node) -> bool: if self.is_data == other.is_data: return self.index < other.index @@ -267,9 +264,6 @@ class CayleyComplex: subset_b: set[abstract.GroupMember] bipartite: bool - # geometric data - _graph: nx.Graph | None = None - def __init__( self, subset_a: Collection[abstract.GroupMember], @@ -396,8 +390,7 @@ class ChainComplex: _field: type[galois.FieldArray] _ops: tuple[npt.NDArray[np.int_] | abstract.RingArray, ...] - # if boundary operators are defined over a group algebra, keep track of the base group and ring - _group: abstract.Group | None + # if boundary operators are defined over a group algebra, keep track of the base ring _ring: abstract.GroupRing | None def __init__( @@ -435,7 +428,7 @@ def __init__( self._validate_ops() def _validate_ops(self) -> None: - """Validate the consistency of this the boundary operators in this chain complex.""" + """Validate the consistency of the boundary operators in this chain complex.""" for op_a, op_b in zip(self.ops, self.ops[1:]): if op_a.shape[1] != op_b.shape[0] or np.any(op_a @ op_b): raise ValueError( @@ -574,9 +567,6 @@ def get_zero_block( if chain_a.ring is None: return ChainComplex([op.view(chain_field) for op in matrices], skip_validation=True) - ops = [] - for matrix in matrices: - op = matrix.view(abstract.RingArray) - op._ring = chain_a.ring - ops.append(op) + # rebuild each operator through RingArray.build to coerce every entry into a ring member + ops = [abstract.RingArray.build(matrix, chain_a.ring) for matrix in matrices] return ChainComplex(ops, skip_validation=True) diff --git a/src/qldpc/objects_test.py b/src/qldpc/objects_test.py index f473ac58f..f3e728bdc 100644 --- a/src/qldpc/objects_test.py +++ b/src/qldpc/objects_test.py @@ -56,7 +56,7 @@ def test_qudit_operator() -> None: assert -objects.QuditPauli((0, 1)) == objects.QuditPauli((0, -1)) for op in ["I", "Y(1)", "X(1)*Z(2)"]: assert str(objects.QuditPauli.from_string(op)) == op - for op in ["a*b*c", "a(1)"]: + for op in ["a*b*c", "a(1)", "*", "X(1)*", "", "X(²)"]: with pytest.raises(ValueError, match="Invalid qudit operator"): objects.QuditPauli.from_string(op) @@ -137,6 +137,19 @@ def test_chain_complex(field: int = 3) -> None: assert not np.any(two_chain.op(0)) assert not np.any(two_chain.op(two_chain.num_links + 1)) + # a tensor product over a nontrivial commutative group algebra must yield boundary operators + # whose entries are all ring members (and can therefore be lifted to matrices), including the + # three-or-more-link case where some operator blocks are entirely zero + cyclic_ring = abstract.GroupRing(abstract.CyclicGroup(3), field) + cyclic_matrix = abstract.RingArray.build(matrix, cyclic_ring) + ring_chain = objects.ChainComplex.tensor_product(cyclic_matrix, cyclic_matrix) + ring_chain = objects.ChainComplex.tensor_product(ring_chain, cyclic_matrix) + ring_chain._validate_ops() + for ring_op in ring_chain.ops: + # every op of a ring-valued chain is a RingArray; assert narrows the union type for .lift() + assert isinstance(ring_op, abstract.RingArray) + ring_op.lift() + # invalid chain complex constructions with pytest.raises(ValueError, match="inconsistent operator types"): objects.ChainComplex([matrix, abstract.RingArray.build([[0]])])