Skip to content
24 changes: 7 additions & 17 deletions src/qldpc/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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__(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)
15 changes: 14 additions & 1 deletion src/qldpc/objects_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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]])])
Expand Down