Skip to content

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: jordan_wigner returns an empty observable, together with an offset, when the operator reduces to a multiple of the identity. However, Circuit refuses to measure an empty observable, because there is nothing to measure.

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 text is empty, if it contains a constant term, if a part of it is not a valid term, or if a term uses the same qubit twice.

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 X, Y and Z functions and ordinary arithmetic, such as 0.5 * Z(0) + X(1). They build the same expression without creating these records directly.

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: "X", "Y" or "Z".

required
qubit int

The index of the qubit.

required
coeff float

The coefficient. Default: 1.0.

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 0, or if no term that can be measured is left.

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 Observable for each qubit.

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.