BasicsΒΆ

Qubits is the mode type for qubit-based types, like Pauli strings, computational basis states, and collections of such objects

from zixy.qubit import Qubits
qubits = Qubits.from_count(4)
assert len(qubits) == 4
assert qubits == Qubits.from_inds([0, 1, 2, 3])
assert qubits != Qubits.from_inds([0, 1, 3, 2])

Lists are the primary containers in Zixy. Their components are stored contiguously in Rust-owned memory buffers, and manipulated by routines written in Rust.

There are a few options for instantiation; one is parsing from sparse strings.

from zixy.qubit.pauli import Strings
inp_str = "X0 Y1 Z3, X1 Y2 Z3, , X3, Y2 Z3"
strings = Strings.from_str(inp_str, qubits)
assert len(strings) == 5
assert str(strings) == inp_str

An exception is raised if any qubit index in the input string is out of bounds.

Strings.from_str("X0 Y1 Z3, X1 Y2 Z3, X7, X3, Y2 Z3", qubits)
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[3], line 1
----> 1 Strings.from_str("X0 Y1 Z3, X1 Y2 Z3, X7, X3, Y2 Z3", qubits)

File ~/Desktop/zixy/zixy-py/docs/.venv/lib/python3.14/site-packages/zixy/qubit/pauli/_strings.py:270, in Strings.from_str(cls, source, qubits)
    267 if isinstance(qubits, int):
    268     qubits = Qubits.from_count(qubits)
    269 return cls._create(
--> 270     cls.cmpnt_type.impl_type(qubits if qubits is not None else None, PauliSprings(source))
    271 )

IndexError: Mode index 7 is out-of-bounds for component list with 4 modes per component.

All Strings instances store a qubit space, but it can be implicitly created based on the largest mode index in the sparse string. Explicit identity matrices count in determining this qubit number.

assert len(Strings.from_str("X0 Y1 Z3, X1 Y2 Z3, X7, X3, Y2 Z3").qubits) == 8
assert len(Strings.from_str("X0 Y1 Z3, X1 Y2 Z3, , X3 I7, Y2 Z3").qubits) == 8

The sparse string parsing functionality knows how to combine coincidently-indexed Pauli matrices

assert str(Strings.from_str("X0 X0 Y1 Z3")) == "Y1 Z3"
assert str(Strings.from_str("X0 Y0 Y1 X1 Z3")) == "Z0 Z1 Z3"

This Strings type has unit coefficient type, and so it cannot absorb phase factors different from 1.

Strings.from_str("X0 Y0 Y1 Z3")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[6], line 1
----> 1 Strings.from_str("X0 Y0 Y1 Z3")

File ~/Desktop/zixy/zixy-py/docs/.venv/lib/python3.14/site-packages/zixy/qubit/pauli/_strings.py:270, in Strings.from_str(cls, source, qubits)
    267 if isinstance(qubits, int):
    268     qubits = Qubits.from_count(qubits)
    269 return cls._create(
--> 270     cls.cmpnt_type.impl_type(qubits if qubits is not None else None, PauliSprings(source))
    271 )

ValueError: Value +i of type "ComplexSign" is not representable as type "Unity"

For this parse to succeed, we have to use a container type that is capable of representing the phase.

ComplexSignList is the list type with coefficients among the fourth roots of unity.

from zixy.qubit.pauli import ComplexSignTerms
assert str(ComplexSignTerms.from_str("X0 Y0 Y1 Z3")) == "(+i, Z0 Y1 Z3)"

A single Pauli string is realised as a List with only one element

from zixy.qubit.pauli import String
string = String(qubits)
assert str(string) == ""

Strings and elements of lists can be accessed and modified using Python containers such as dict and list

from zixy.qubit.pauli import I, X, Y, Z
string.set([X, Y, X, Z])
array = Strings.from_str("X0, Y0, Z0 X2, X0 Y1 X2 Z3")
assert array[3] == string
assert array[-1] == string
assert array[2].get_dict() == {0: Z, 2: X}
assert array[2].get_tuple() == (Z, I, X, I)

Multiplication of terms in-place by other terms can be attempted, but raises an exception if the coefficient is not capable of absorbing the phase.

array = Strings.from_str("X0, Y0, Z0 X2, X0 Y1 X2 Z3")
array[0] *= array[1]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[10], line 2
      1 array = Strings.from_str("X0, Y0, Z0 X2, X0 Y1 X2 Z3")
----> 2 array[0] *= array[1]

File ~/Desktop/zixy/zixy-py/docs/.venv/lib/python3.14/site-packages/zixy/qubit/pauli/_strings.py:185, in String.__imul__(self, rhs)
    183 phase = self.phase_of_mul(rhs)
    184 if phase != ComplexSign():
--> 185     raise _imul_factor_error(self, rhs, phase)
    186 self.imul_ignore_phase(rhs)
    187 return self

ValueError: Cannot multiply-assign left-hand operand X0 with type <class 'zixy.qubit.pauli._strings.String'> by value Y0. Result with factor +i is not representable by the left-hand operand type.

Of course, ComplexSignList is infallible with respect to Pauli term multiplication

terms = ComplexSignTerms.from_str("X0, Y0, Z0 X2, X0 Y1 X2 Z3")
terms[0] *= terms[1]
assert str(terms) == "(+i, Z0), (+1, Y0), (+1, Z0 X2), (+1, X0 Y1 X2 Z3)"

Qubit-based objects defined on different numbers of qubits are not arithmetically compatible

from zixy.qubit.pauli import ComplexSignTerm
terms_6qbt = ComplexSignTerms.from_str("X0 Z2 X5")
terms_4qbt = ComplexSignTerms.from_str("Y0 X2, Z0 X3, X0 Y1 Z2")
assert len(terms_6qbt.qubits) == 6
assert len(terms_4qbt.qubits) == 4
terms_6qbt[0] * terms_4qbt[0]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[12], line 6
      4 assert len(terms_6qbt.qubits) == 6
      5 assert len(terms_4qbt.qubits) == 4
----> 6 terms_6qbt[0] * terms_4qbt[0]

File ~/Desktop/zixy/zixy-py/docs/.venv/lib/python3.14/site-packages/zixy/container/mixins.py:536, in TermMulMixin.__mul__(self, rhs)
    533     return self.cmpnt * rhs * self.coeff
    534 else:
    535     # Undefined behaviour for base Cmpnt, but may be defined by derived classes
--> 536     return self.cmpnt * rhs.cmpnt * self.coeff * rhs.coeff

File ~/Desktop/zixy/zixy-py/docs/.venv/lib/python3.14/site-packages/zixy/qubit/pauli/_strings.py:173, in String.__mul__(self, rhs)
    171     return NotImplemented
    172 if isinstance(rhs, String):
--> 173     product, phase = self._impl.cmpnt_mul(self.index, rhs._impl, rhs.index)
    174     phases = ComplexSignCoeffs.from_scalar(ComplexSign(phase))
    175     term_type = self._term_registry[ComplexSign]

ValueError: Qubits-based objects are based on different qubit spaces.

Moving to a different number of qubits and/or ordering of the qubit space brings qubits-based objects into arithmetic compatibility

assert len(terms_4qbt.standardized(6).qubits) == 6
terms_6qbt[0] * terms_4qbt.standardized(6)[0]
(-1, Z0 Y2 X5)

Strings and the Terms types can have duplicates removed on command.

Whenever uniqueness is to be enforced by construction, the Set types are required.

from zixy.qubit.pauli import StringSet
string_set = StringSet.from_strings(terms.strings)
assert len(string_set) == 4
string_set.insert((X, Y, Z, X))
assert len(string_set) == 5
string_set.insert((X, Y, Z, X))
assert len(string_set) == 5
string_set
Z0, Y0, Z0 X2, X0 Y1 X2 Z3, X0 Y1 Z2 X3