States¶
ParityOS supports various concepts of states on qubits as abstract base classes in the module parityos.states.state.
The fundamental concept is a State, which represents any object that describes a state of qubits. It implements HasFrozenQubits which defines the qubits and ordered_qubits attributes. It also defines an abstract
method flip_qubits that flips all or a selected subset of the state qubits.
A priori, state qubits are not considered to be ordered. However, implementing classes can decide to introduce a definite qubit order (cf. Pauli States).
Abstract State Classes¶
ParityOS defines the following abstract classes, which can be extended to implement custom quantum state classes:
Basis States: Computational basis states.
Basis State Counts: Counting statistics from e.g. repeated QPU runs.
Quantum States: General quantum states as superpositions of basis states with arbitrary complex or symbolic amplitudes.
For a concrete built-in implementation of these interfaces, see the Pauli States section.
Basis States¶
A BasisState is an element of a computational basis defined on a Hilbert space spanned by a collection of qubits.
Basis states are assumed to be product states that supply a qubit_to_value mapping, i.e. they can be represented by bitstrings.
Basis State Counts¶
A BasisStateCounts object is a statistical ensemble of individual basis states and their respective (positive integer) counts. Such counts arise e.g. from qubit measurements in repeated QPU runs of a particular quantum circuit.
It defines an abstract state_to_count mapping, which gives access to the number of times a particular basis state has been measured.
From this mapping, the following properties are derived:
A
total_countproperty (summing up all counts fromstate_to_count)A
state_to_probabilitymapping. Here, the probabilities are defined as the counts divided bytotal_count.
Quantum States¶
A QuantumState is a general superposition of BasisStates with arbitrary complex amplitudes. It is defined by the attributes abstract state_to_amplitude mapping. As such a QuantumState represents a sparse quantum state representation.
Derived from this mapping, it also implements a norm property and a state_to_probability mapping. Here the probabilities are the squared amplitude norms divided by the state norm.
It also defines an abstract to_normalized method, which returns a normalized copy of the state.
Note
QuantumState amplitudes are Coefficients and can be symbolic Parameters or ParameterExpressions too.
Pauli States¶
ParityOS supplies a built-in implementation of the abstract base classes in terms of states in the Pauli bases (represented by the Pauli operator classes X, Y and Z). They can be imported from the module parityos.states.pauli_state.
Pauli states work with bitstrings and introduce a definite qubit order by using ordered_qubits. Here, the n-th bit in the bitstring describes the state of the n-th qubit in the defined Pauli basis. They also supply a possibility to reorder qubits, by implementing the OrderedPauliState interface.
Caution
Bitstrings and ordered qubit sequences must be of same length.
Pauli Basis State¶
A PauliBasisState is a product state in one of the three Pauli bases (X, Y or Z). It is defined by the attributes
See also section Basis State Counts.
from parityos.bits import Qubit
from parityos.operators.elementary_operator import X, Z
from parityos.states.pauli_state import PauliBasisState
q0, q1 = Qubit(0), Qubit(1)
qa, qb = Qubit("a"), Qubit("b")
# basis states in different pauli bases
basis_01_z = PauliBasisState("01", (qa, qb))
basis_10_x = PauliBasisState("10", basis=X)
assert basis_01_z.ordered_qubits == (qa, qb)
assert basis_01_z.basis == Z
assert basis_01_z.qubit_to_value == {qa: False, qb: True}
assert str(basis_01_z) == "|01⟩"
assert basis_10_x.ordered_qubits == (q0, q1)
assert basis_10_x.basis == X
assert basis_10_x.qubit_to_value == {q0: True, q1: False}
assert str(basis_10_x) == "|10⟩"
# reorder
assert basis_01_z.reorder((qb, qa)) == PauliBasisState("10", (qb, qa))
# flip qubits
assert basis_01_z.flip_qubits() == PauliBasisState("10", (qa, qb))
Pauli Basis State Counts¶
A PauliBasisStateCounts is defined by its attributes
bitstring_count_pairs, which maps basis states represented by bitstrings to their counts
from parityos.bits import Qubit
from parityos.operators.elementary_operator import X, Z
from parityos.states.pauli_state import PauliBasisState, PauliBasisStateCounts
q0, q1 = Qubit(0), Qubit(1)
qa, qb = Qubit("a"), Qubit("b")
# basis state counts in Z basis
counts_z = PauliBasisStateCounts({"00": 10, "11": 11, "01": 1}, (qa, qb))
assert counts_z.basis == Z
assert counts_z.ordered_qubits == (qa, qb)
assert counts_z.state_to_count == {
PauliBasisState("00", (qa, qb)): 10,
PauliBasisState("11", (qa, qb)): 11,
PauliBasisState("01", (qa, qb)): 1,
}
assert counts_z.total_count == 22
assert counts_z.state_to_probability == {
PauliBasisState("00", (qa, qb)): 5 / 11,
PauliBasisState("11", (qa, qb)): 1 / 2,
PauliBasisState("01", (qa, qb)): 1 / 22,
}
# basis state counts in X basis
counts_x = PauliBasisStateCounts({"10": 10, "01": 2}, basis=X)
assert counts_x.basis == X
assert counts_x.ordered_qubits == (q0, q1)
assert counts_x.state_to_count == {
PauliBasisState("10", basis=X): 10,
PauliBasisState("01", basis=X): 2,
}
assert counts_x.total_count == 12
assert counts_x.state_to_probability == {
PauliBasisState("10", basis=X): 5 / 6,
PauliBasisState("01", basis=X): 1 / 6,
}
## reorder
assert counts_x.reorder((q1, q0)) == PauliBasisStateCounts({"01": 10, "10": 2}, (q1, q0), X)
## flip first qubit
assert counts_x.flip_qubits(q0) == PauliBasisStateCounts({"00": 10, "11": 2}, basis=X)
Pauli Quantum States¶
A PauliQuantumState is defined by its attributes
bitstring_amplitude_pairswhich maps basis states represented by bitstrings to their amplitudes
from parityos.bits import Qubit
from parityos.operators.elementary_operator import X, Z
from parityos.states.pauli_state import PauliBasisState, PauliQuantumState
from parityos.utils.functions import sqrt
from parityos.utils.parameter import Parameter
q0, q1 = Qubit(0), Qubit(1)
qa, qb, qc = Qubit("a"), Qubit("b"), Qubit("c")
# (unnormalized) GHZ state in Z basis
ghz_z = PauliQuantumState({"000": 1, "111": 1}, (qa, qb, qc))
assert ghz_z.basis == Z
assert ghz_z.ordered_qubits == (qa, qb, qc)
assert ghz_z.state_to_amplitude == {
PauliBasisState("000", (qa, qb, qc)): 1,
PauliBasisState("111", (qa, qb, qc)): 1,
}
assert ghz_z.norm == sqrt(2)
assert ghz_z.state_to_probability == {
PauliBasisState("000", (qa, qb, qc)): 1 / 2,
PauliBasisState("111", (qa, qb, qc)): 1 / 2,
}
## normalize
assert ghz_z.to_normalized().state_to_amplitude == {
PauliBasisState("000", (qa, qb, qc)): 1 / sqrt(2),
PauliBasisState("111", (qa, qb, qc)): 1 / sqrt(2),
}
# state with symbolic amplitudes (symbolic parameters are real per definition).
symbolic_state = PauliQuantumState({"01": -1j, "10": Parameter("alpha")}, basis=X)
assert symbolic_state.basis == X
assert symbolic_state.norm == sqrt(Parameter("alpha") ** 2 + 1.0)
assert symbolic_state.state_to_probability == {
PauliBasisState("01", basis=X): 1.0 / (Parameter("alpha") ** 2 + 1.0),
PauliBasisState("10", basis=X): Parameter("alpha") ** 2 / (Parameter("alpha") ** 2 + 1.0),
}
## reorder
assert symbolic_state.reorder((q1, q0)) == PauliQuantumState(
{"10": -1j, "01": Parameter("alpha")}, (q1, q0), basis=X
)
## flip second qubit
assert symbolic_state.flip_qubits(q1) == PauliQuantumState(
{"00": -1j, "11": Parameter("alpha")}, basis=X
)
Pauli State Arithmetic¶
Pauli states support arithmetic: + and - for state addition, subtraction and negation, and * for scalar multiplication or tensor products.
from parityos.bits import Qubit
from parityos.states.pauli_state import PauliBasisState, PauliQuantumState
from parityos.utils.functions import sqrt
# bell states
## from arithmetic
phi_plus = (PauliBasisState("00") + PauliBasisState("11")) / sqrt(2)
psi_minus = (
PauliBasisState("01", (Qubit("a"), Qubit("b")))
- PauliBasisState("10", (Qubit("a"), Qubit("b")))
) / sqrt(2)
## from constructor
assert phi_plus == PauliQuantumState({"00": 1.0 / sqrt(2), "11": 1.0 / sqrt(2)})
assert psi_minus == PauliQuantumState(
{"01": 1.0 / sqrt(2), "10": -1.0 / sqrt(2)}, (Qubit("a"), Qubit("b"))
)
## flipping psi_minus's qubits is equivalent to a minus sign:
## (|01⟩ - |10⟩) / sqrt(2) --> (|10⟩ - |01⟩) / sqrt(2) = - (|01⟩ - |10⟩) / sqrt(2)
assert psi_minus.flip_qubits() == -psi_minus
# tensor product of bell states
product = phi_plus * psi_minus
expected = PauliQuantumState(
{"0001": 1 / 2, "0010": -1 / 2, "1101": 1 / 2, "1110": -1 / 2},
(Qubit(0), Qubit(1), Qubit("a"), Qubit("b")),
)
## Don't use simple `product == expected` due to numerical imprecision
assert product.basis == expected.basis
assert product.ordered_qubits == expected.ordered_qubits
assert product.state_to_amplitude.keys() == expected.state_to_amplitude.keys()
assert all(
[
abs(amplitude - expected.state_to_amplitude[state]) < 1e-14
for state, amplitude in product.state_to_amplitude.items()
]
)