Observables
What to measure is set with the observables= argument of Circuit. It is not a
special block. There are three ways to build an observable, each suited to a
different level of detail.
Default: Z on every qubit
If you do not pass observables=, the circuit measures Z on every qubit:
from eigenkit import Circuit, Encoding, Variational
circuit = Circuit(
blocks=[Encoding(in_features=4), Variational(depth=2)],
# observables= defaults to all-Z; output is shape (..., n_qubits).
)
Typed constructors
Observable.x(qubit), Observable.y(qubit) and Observable.z(qubit) build
observables made of a single Pauli operator:
from eigenkit import Observable, all_z
# Measure Z on every qubit.
observables = all_z(4)
# Measure X on qubit 0 only.
observables = [Observable.x(0)]
Pauli algebra
The X, Y and Z constructors return a PauliExpr, which you can combine with
arithmetic. Use this to write Hamiltonians:
from eigenkit import Circuit, Encoding, Variational, X, Y, Z
H = 0.7 * Z(0) + 0.3 * X(1) * Z(2) - 0.5 * Y(0) * Y(1)
# H acts on qubits 0-2, so the circuit needs at least three wires.
circuit = Circuit(
blocks=[Encoding(in_features=3), Variational(depth=2)],
observables=H,
)
A PauliExpr passed as a single observable gives a scalar output, which is one
expectation value. Pass a list of PauliExpr to get a vector output.
A measured observable holds only Pauli terms. So an expression with a constant term,
such as the cost of an edge in MaxCut, 0.5 * (1 - Z(0) * Z(1)), raises a
ValueError, instead of losing the constant without an error. Measure the Pauli
part, and add the constant back:
cost = 0.5 * (1 - Z(0) * Z(1))
edge = Circuit(
blocks=[Encoding(in_features=2), Variational(depth=1)],
observables=cost - cost.constant,
)
value = edge(edge.spec.n_weights * [0.1], [0.3, 0.4]) + cost.constant
Text format
For quick experiments, you can write the observable as a string:
from eigenkit import from_string
H = from_string("0.5 Z0 X1 + 0.3 Y2")
# Equivalent to 0.5 * Z(0) * X(1) + 0.3 * Y(2)
Output shape
The shape of the output depends on what you pass, and on the number of dimensions
of the input. A 1-D inputs is one sample and has no batch axis. A 2-D input, of
shape (batch, n_inputs), has one.
observables= |
1-D input | 2-D (batch, n_inputs) |
|---|---|---|
None |
(n_qubits,) |
(batch, n_qubits) |
A single Observable or PauliExpr |
(), a scalar |
(batch,) |
A list of Observable or PauliExpr |
(len(list),) |
(batch, len(list)) |
These shapes were measured on a circuit with four qubits and a batch of shape
(32, 4). In both cases, weights stays 1-D, because the weights are shared across
the batch.
Measurements that are not Pauli operators
For observables based on fidelity (quantum kernels, swap tests), use
circuit.zero_state_probability(weights, inputs) instead. It returns the probability
of measuring the all-zeros state, without going through the Pauli machinery. See
Quantum Kernels.
API reference
eigenkit.core.ir.Observable
dataclass
A Hermitian operator to measure, written as a sum of Pauli terms.
You can build one with the z, x and y methods for a single Pauli
operator on one qubit, with from_string for a whole sum, or with the X,
Y and Z functions of eigenkit.core.observables, which support
arithmetic.
Attributes:
| Name | Type | Description |
|---|---|---|
terms |
tuple[PauliTerm, ...]
|
The Pauli terms of the sum. It can be empty: |
from_string(text)
classmethod
Parse a sum of Pauli terms written as a string.
Examples of such strings are "0.5 Z0 X1 + 0.3 Y2" and "-Z0 + Z1".
The grammar is as follows. The terms are joined by + or -. Each term
starts with an optional numeric coefficient, in plain or scientific
notation (1e-3 is accepted). The coefficient is followed by an optional
* and by one or more [XYZ]<qubit> operators on distinct qubits. Any
amount of whitespace is accepted, including none.
Every part of the string must be a valid term, and every term must contain
Pauli operators. A constant term ("0.5 + Z0", "0.5 I") raises an error
instead of being dropped from the sum: an Observable holds only Pauli
terms, and the constant must be kept next to it as an offset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The string to parse. |
required |
Returns:
| Type | Description |
|---|---|
'Observable'
|
The parsed observable. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
x(qubit)
classmethod
Return the observable X on the single qubit qubit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
qubit
|
int
|
The index of the qubit. |
required |
y(qubit)
classmethod
Return the observable Y on the single qubit qubit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
qubit
|
int
|
The index of the qubit. |
required |
z(qubit)
classmethod
Return the observable Z on the single qubit qubit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
qubit
|
int
|
The index of the qubit. |
required |
eigenkit.core.observables
Functions and classes that make observables easy to write.
There are three ways to choose what a circuit measures:
# Pauli algebra
observable = Z(0) * X(1) + 0.5 * Y(2)
# class methods, or a string
observable = Observable.from_string("Z0 X1 + 0.5 Y2")
observable = Observable.z(0)
# no observable at all: by default, Circuit measures Z on every qubit
PauliExpr is a simple builder. It collects terms, each made of a coefficient
and a tensor product of Pauli operators, and it converts them to an Observable
when needed. A product of two operators on the same qubit is simplified when the
two operators are equal (P * P = I). A product of different Pauli operators on
the same qubit (X * Y = iZ and so on) needs complex arithmetic and is not
supported.
PauliExpr
A sum of terms, each a coefficient times a product of Pauli operators.
In each term, the Pauli operators act on distinct qubits, so the product is a tensor product.
constant
property
The coefficient of the identity term, or 0.0 when there is none.
__init__(terms)
Build an expression from raw terms.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
terms
|
Iterable[_Term]
|
Internal records of the terms. Prefer the |
required |
single(operator, qubit, coeff=1.0)
classmethod
Return coeff times a single Pauli operator on qubit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operator
|
str
|
The Pauli operator: |
required |
qubit
|
int
|
The index of the qubit. |
required |
coeff
|
float
|
The coefficient. Default: |
1.0
|
Returns:
| Type | Description |
|---|---|
'PauliExpr'
|
The expression, which has a single term. |
to_observable()
Convert the expression to an Observable.
The terms with the same Pauli string are combined, and the terms whose
coefficient is 0 (at most 1e-15 in magnitude) are dropped.
An Observable holds only Pauli terms, so a non-zero identity term raises
an error. Such a term comes from a scalar offset, or from P * P = I.
Dropping it without an error would shift every expectation value. To
measure such an expression, measure expr - expr.constant and add
expr.constant to the result. The chemistry entry points take this
constant as offset.
Returns:
| Type | Description |
|---|---|
Observable
|
The observable. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the expression has a constant term that is not |
X(qubit)
Return Pauli X on qubit, as an expression that can be added and scaled.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
qubit
|
int
|
The index of the qubit. |
required |
Returns:
| Type | Description |
|---|---|
PauliExpr
|
The expression. |
Y(qubit)
Return Pauli Y on qubit, as an expression that can be added and scaled.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
qubit
|
int
|
The index of the qubit. |
required |
Returns:
| Type | Description |
|---|---|
PauliExpr
|
The expression. |
Z(qubit)
Return Pauli Z on qubit, as an expression that can be added and scaled.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
qubit
|
int
|
The index of the qubit. |
required |
Returns:
| Type | Description |
|---|---|
PauliExpr
|
The expression. |
all_z(n_qubits)
Return one Z observable for each qubit.
This is useful as a default measurement.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_qubits
|
int
|
The number of qubits of the circuit. |
required |
Returns:
| Type | Description |
|---|---|
list[Observable]
|
One |
from_string(text)
Parse an observable from a string.
This function is a shortcut for Observable.from_string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The string to parse. |
required |
Returns:
| Type | Description |
|---|---|
Observable
|
The parsed observable. |