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