Skip to main contentIBM Quantum Documentation

SparseObservable

class qiskit.quantum_info.SparseObservable

GitHub

Bases: object

An observable over Pauli bases that stores its data in a qubit-sparse format.


Mathematics

This observable represents a sum over strings of the Pauli operators and Pauli-eigenstate projectors, with each term weighted by some complex number. That is, the full observable is

SparseObservable=icinAi(n)\text{\texttt{SparseObservable}} = \sum_i c_i \bigotimes_n A^{(n)}_i

for complex numbers cic_i and single-qubit operators acting on qubit nn from a restricted alphabet Ai(n)A^{(n)}_i. The sum over ii is the sum of the individual terms, and the tensor product produces the operator strings.

The alphabet of allowed single-qubit operators that the Ai(n)A^{(n)}_i are drawn from is the Pauli operators and the Pauli-eigenstate projection operators. Explicitly, these are:

LabelOperatorNumeric valueBitTerm attribute
"I"II (identity)Not stored.Not stored.
"X"XX (Pauli X)0b0010 (2)X
"Y"YY (Pauli Y)0b0011 (3)Y
"Z"ZZ (Pauli Z)0b0001 (1)Z
"+"++\lvert+\rangle\langle+\rvert (projector to positive eigenstate of X)0b1010 (10)PLUS
"-"\lvert-\rangle\langle-\rvert (projector to negative eigenstate of X)0b0110 (6)MINUS
"r"rr\lvert r\rangle\langle r\rvert (projector to positive eigenstate of Y)0b1011 (11)RIGHT
"l"ll\lvert l\rangle\langle l\rvert (projector to negative eigenstate of Y)0b0111 (7)LEFT
"0"00\lvert0\rangle\langle0\rvert (projector to positive eigenstate of Z)0b1001 (9)ZERO
"1"11\lvert1\rangle\langle1\rvert (projector to negative eigenstate of Z)0b0101 (5)ONE

The allowed alphabet forms an overcomplete basis of the operator space. This means that there is not a unique summation to represent a given observable. By comparison, SparsePauliOp uses a precise basis of the operator space, so (after combining terms of the same Pauli string, removing zeros, and sorting the terms to some canonical order) there is only one representation of any operator.

SparseObservable uses its particular overcomplete basis with the aim of making “efficiency of measurement” equivalent to “efficiency of representation”. For example, the observable 00n{\lvert0\rangle\langle0\rvert}^{\otimes n} can be efficiently measured on hardware with simple ZZ measurements, but can only be represented by SparsePauliOp as (I+Z)n/2n{(I + Z)}^{\otimes n}/2^n, which requires 2n2^n stored terms. SparseObservable requires only a single term to store this.

The downside to this is that it is impractical to take an arbitrary matrix or SparsePauliOp and find the best SparseObservable representation. You typically will want to construct a SparseObservable directly, rather than trying to decompose into one.


Representation

The internal representation of a SparseObservable stores only the non-identity qubit operators. This makes it significantly more efficient to represent observables such as nqubitsZ(n)\sum_{n\in \text{qubits}} Z^{(n)}; SparseObservable requires an amount of memory linear in the total number of qubits, while SparsePauliOp scales quadratically.

The terms are stored compressed, similar in spirit to the compressed sparse row format of sparse matrices. In this analogy, the terms of the sum are the “rows”, and the qubit terms are the “columns”, where an absent entry represents the identity rather than a zero. More explicitly, the representation is made up of four contiguous arrays:

AttributeLengthDescription
coeffsttThe complex scalar multiplier for each term.
bit_termsssEach of the non-identity single-qubit terms for all of the operators, in order. These correspond to the non-identity Ai(n)A^{(n)}_i in the sum description, where the entries are stored in order of increasing ii first, and in order of increasing nn within each term.
indicesssThe corresponding qubit (nn) for each of the operators in bit_terms. SparseObservable requires that this list is term-wise sorted, and algorithms can rely on this invariant being upheld.
boundariest+1t+1The indices that partition bit_terms and indices into complete terms. For term number ii, its complex coefficient is coeffs[i], and its non-identity single-qubit operators and their corresponding qubits are the slice boundaries[i] : boundaries[i+1] into bit_terms and indices respectively. boundaries always has an explicit 0 as its first element.

The length parameter tt is the number of terms in the sum, and the parameter ss is the total number of non-identity single-qubit terms.

As illustrative examples:

  • in the case of a zero operator, boundaries is length 1 (a single 0) and all other vectors are empty.
  • in the case of a fully simplified identity operator, boundaries is [0, 0], coeffs has a single entry, and bit_terms and indices are empty.
  • for the operator Z2Z0X3Y1Z_2 Z_0 - X_3 Y_1, boundaries is [0, 2, 4], coeffs is [1.0, -1.0], bit_terms is [BitTerm.Z, BitTerm.Z, BitTerm.Y, BitTerm.X] and indices is [0, 2, 1, 3]. The operator might act on more than four qubits, depending on the num_qubits parameter. The bit_terms are integer values, whose magic numbers can be accessed via the BitTerm attribute class. Note that the single-bit terms and indices are sorted into termwise sorted order. This is a requirement of the class.

These cases are not special, they’re fully consistent with the rules and should not need special handling.

The scalar item of the bit_terms array is stored as a numeric byte. The numeric values are related to the symplectic Pauli representation that SparsePauliOp uses, and are accessible with named access by an enumeration:

BitTerm

class BitTerm

GitHub

An IntEnum that provides named access to the numerical values used to represent each of the single-qubit alphabet terms enumerated in Alphabet of single-qubit terms used in SparseObservable.

This class is attached to SparseObservable. Access it as SparseObservable.BitTerm. If this is too much typing, and you are solely dealing with :class:¬SparseObservable` objects and the BitTerm name is not ambiguous, you might want to shorten it as:

>>> ops = SparseObservable.BitTerm
>>> assert ops.X is SparseObservable.BitTerm.X

You can access all the values of the enumeration by either their full all-capitals name, or by their single-letter label. The single-letter labels are not generally valid Python identifiers, so you must use indexing notation to access them:

>>> assert SparseObservable.BitTerm.ZERO is SparseObservable.BitTerm["0"]

The numeric structure of these is that they are all four-bit values of which the low two bits are the (phase-less) symplectic representation of the Pauli operator related to the object, where the low bit denotes a contribution by ZZ and the second lowest a contribution by XX, while the upper two bits are 00 for a Pauli operator, 01 for the negative-eigenstate projector, and 10 for the positive-eigenstate projector.

X

Default value: 2

The Pauli XX operator. Uses the single-letter label "X".

PLUS

Default value: 10

The projector to the positive eigenstate of the XX operator: ++\lvert+\rangle\langle+\rvert. Uses the single-letter label "+".

MINUS

Default value: 6

The projector to the negative eigenstate of the XX operator: \lvert-\rangle\langle-\rvert. Uses the single-letter label "-".

Y

Default value: 3

The Pauli YY operator. Uses the single-letter label "Y".

Default value: 11

The projector to the positive eigenstate of the YY operator: rr\lvert r\rangle\langle r\rvert. Uses the single-letter label "r".

LEFT

Default value: 7

The projector to the negative eigenstate of the YY operator: ll\lvert l\rangle\langle l\rvert. Uses the single-letter label "l".

Z

Default value: 1

The Pauli ZZ operator. Uses the single-letter label "Z".

ZERO

Default value: 9

The projector to the positive eigenstate of the ZZ operator: 00\lvert0\rangle\langle0\rvert. Uses the single-letter label "0".

ONE

Default value: 5

The projector to the negative eigenstate of the ZZ operator: 11\lvert1\rangle\langle1\rvert. Uses the single-letter label "1".

Each of the array-like attributes behaves like a Python sequence. You can index and slice these with standard list-like semantics. Slicing an attribute returns a Numpy ndarray containing a copy of the relevant data with the natural dtype of the field; this lets you easily do mathematics on the results, like bitwise operations on bit_terms. You can assign to indices or slices of each of the attributes, but beware that you must uphold the data coherence rules while doing this. For example:

>>> obs = SparseObservable.from_list([("XZY", 1.5j), ("+1r", -0.5)])
>>> assert isinstance(obs.coeffs[:], np.ndarray)
>>> # Reduce all single-qubit terms to the relevant Pauli operator, if they are a projector.
>>> obs.bit_terms[:] = obs.bit_terms[:] & 0b00_11
>>> assert obs == SparseObservable.from_list([("XZY", 1.5j), ("XZY", -0.5)])
Note

The above reduction to the Pauli bases can also be achieved with pauli_bases().

Canonical ordering

For any given mathematical observable, there are several ways of representing it with SparseObservable. For example, the same set of single-bit terms and their corresponding indices might appear multiple times in the observable. Mathematically, this is equivalent to having only a single term with all the coefficients summed. Similarly, the terms of the sum in a SparseObservable can be in any order while representing the same observable, since addition is commutative (although while floating-point addition is not associative, SparseObservable makes no guarantees about the summation order).

These two categories of representation degeneracy can cause the == operator to claim that two observables are not equal, despite representating the same object. In these cases, it can be convenient to define some canonical form, which allows observables to be compared structurally.

You can put a SparseObservable in canonical form by using the simplify() method. The precise ordering of terms in canonical ordering is not specified, and may change between versions of Qiskit. Within the same version of Qiskit, however, you can compare two observables structurally by comparing their simplified forms.

Note

If you wish to account for floating-point tolerance in the comparison, it is safest to use a recipe such as:

def equivalent(left, right, tol):
    return (left - right).simplify(tol) == SparseObservable.zero(left.num_qubits)
Note

The canonical form produced by simplify() will still not universally detect all observables that are equivalent due to the over-complete basis alphabet; it is not computationally feasible to do this at scale. For example, on observable built from + and - components will not canonicalize to a single X term.

Indexing

SparseObservable behaves as a Python sequence (the standard form, not the expanded collections.abc.Sequence). The observable can be indexed by integers, and iterated through to yield individual terms.

Each term appears as an instance a self-contained class. The individual terms are copied out of the base observable; mutations to them will not affect the observable.

Term

class Term

GitHub

Bases: object

A single term from a complete SparseObservable.

These are typically created by indexing into or iterating through a SparseObservable.

bit_terms

Read-only view onto the individual single-qubit terms.

The only valid values in the array are those with a corresponding BitTerm.

coeff

The complex coefficient of the term.

copy

copy()

Get a copy of this term.

indices

Read-only view onto the indices of each non-identity single-qubit term.

The indices will always be in sorted order.

num_qubits

Number of qubits the entire term applies to.

pauli_base

pauli_base()

Get a Pauli object that represents the measurement basis needed for this term.

For example, the projector 0l+ will return a Pauli ZXY. The resulting Pauli is dense, in the sense that explicit identities are stored. An identity in the Pauli output does not require a concrete measurement.

Returns

the Pauli operator representing the necessary measurement basis.

Return type

Pauli

See also

SparseObservable.pauli_bases()

A similar method for an entire observable at once.

to_observable

to_observable()

Convert this term to a complete SparseObservable.


Construction

SparseObservable defines several constructors. The default constructor will attempt to delegate to one of the more specific constructors, based on the type of the input. You can always use the specific constructors to have more control over the construction.

MethodSummary
from_label()Convert a dense string label into a single-term SparseObservable.
from_list()Sum a list of tuples of dense string labels and the associated coefficients into an observable.
from_sparse_list()Sum a list of tuples of sparse string labels, the qubits they apply to, and their coefficients into an observable.
from_pauli()Raise a single Pauli into a single-term SparseObservable.
from_sparse_pauli_op()Raise a SparsePauliOp into a SparseObservable.
from_terms()Sum explicit single Term instances.
from_raw_parts()Build the observable from the raw data arrays.

__new__

__new__(data, /, num_qubits=None)

The default constructor of SparseObservable.

This delegates to one of the explicit conversion-constructor methods, based on the type of the data argument. If num_qubits is supplied and constructor implied by the type of data does not accept a number, the given integer must match the input.

Parameters

  • data – The data type of the input. This can be another SparseObservable, in which case the input is copied, a Pauli or SparsePauliOp, in which case from_pauli() or from_sparse_pauli_op() are called as appropriate, or it can be a list in a valid format for either from_list() or from_sparse_list().
  • num_qubits (int|None) – Optional number of qubits for the operator. For most data inputs, this can be inferred and need not be passed. It is only necessary for empty lists or the sparse-list format. If given unnecessarily, it must match the data input.

In addition to the conversion-based constructors, there are also helper methods that construct special forms of observables.

MethodSummary
zero()The zero operator on a given number of qubits.
identity()The identity operator on a given number of qubits.

Mathematical manipulation

SparseObservable supports the standard set of Python mathematical operators like other quantum_info operators.

In basic arithmetic, you can:

  • add two observables using +
  • subtract two observables using -
  • multiply or divide by an int, float or complex using * and /
  • negate all the coefficients in an observable with unary -

Each of the basic binary arithmetic operators has a corresponding specialized in-place method, which mutates the left-hand side in-place. Using these is typically more efficient than the infix operators, especially for building an observable in a loop.

The tensor product is calculated with tensor() (for standard, juxtaposition ordering of Pauli labels) or expand() (for the reverse order). The ^ operator is overloaded to be equivalent to tensor().

Note

When using the binary operators ^ (tensor()) and & (compose()), beware that Python’s operator-precedence rules may cause the evaluation order to be different to your expectation. In particular, the operator + binds more tightly than ^ or &, just like * binds more tightly than +.

When using the operators in mixed expressions, it is safest to use parentheses to group the operands of tensor products.

A SparseObservable has a well-defined adjoint(). The notions of scalar complex conjugation (conjugate()) and real-value transposition (transpose()) are defined analogously to the matrix representation of other Pauli operators in Qiskit.

Efficiency notes

Internally, SparseObservable is in-place mutable, including using over-allocating growable vectors for extending the number of terms. This means that the cost of appending to an observable using += is amortised linear in the total number of terms added, rather than the quadratic complexity that the binary + would require.

Additions and subtractions are implemented by a term-stacking operation; there is no automatic “simplification” (summing of like terms), because the majority of additions to build up an observable generate only a small number of duplications, and like-term detection has additional costs. If this does not fit your use cases, you can either periodically call simplify(), or discuss further APIs with us for better building of observables.


Attributes

bit_terms

A flat list of single-qubit terms. This is more naturally a list of lists, but is stored flat for memory usage and locality reasons, with the sublists denoted by boundaries.

boundaries

Indices that partition bit_terms and indices into sublists for each individual term in the sum. boundaries[0] : boundaries[1] is the range of indices into bit_terms and indices that correspond to the first term of the sum. All unspecified qubit indices are implicitly the identity. This is one item longer than coeffs, since boundaries[0] is always an explicit zero (for algorithmic ease).

coeffs

The coefficients of each abstract term in in the sum. This has as many elements as terms in the sum.

indices

A flat list of the qubit indices that the corresponding entries in bit_terms act on. This list must always be term-wise sorted, where a term is a sublist as denoted by boundaries.

Warning

If writing to this attribute from Python space, you must ensure that you only write in indices that are term-wise sorted.

num_qubits

The number of qubits the operator acts on.

This is not inferable from any other shape or values, since identities are not stored explicitly.

num_terms

The number of terms in the sum this operator is tracking.


Methods

adjoint

adjoint()

Calculate the adjoint of this observable.

This is well defined in the abstract mathematical sense. All the terms of the single-qubit alphabet are self-adjoint, so the result of this operation is the same observable, except its coefficients are all their complex conjugates.

Examples

>>> left = SparseObservable.from_list([("XY+-", 1j)])
>>> right = SparseObservable.from_list([("XY+-", -1j)])
>>> assert left.adjoint() == right

apply_layout

apply_layout(layout, num_qubits=None)

Apply a transpiler layout to this SparseObservable.

Typically you will have defined your observable in terms of the virtual qubits of the circuits you will use to prepare states. After transpilation, the virtual qubits are mapped to particular physical qubits on a device, which may be wider than your circuit. That mapping can also change over the course of the circuit. This method transforms the input observable on virtual qubits to an observable that is suitable to apply immediately after the fully transpiled physical circuit.

Parameters

  • layout (TranspileLayout |list[int] | None) – The layout to apply. Most uses of this function should pass the QuantumCircuit.layout field from a circuit that was transpiled for hardware. In addition, you can pass a list of new qubit indices. If given as explicitly None, no remapping is applied (but you can still use num_qubits to expand the observable).
  • num_qubits (int | None) – The number of qubits to expand the observable to. If not supplied, the output will be as wide as the given TranspileLayout, or the same width as the input if the layout is given in another form.

Returns

A new SparseObservable with the provided layout applied.

clear

clear()

Clear all the terms from this operator, making it equal to the zero operator again.

This does not change the capacity of the internal allocations, so subsequent addition or substraction operations may not need to reallocate.

Examples

>>> obs = SparseObservable.from_list([("IX+-rl", 2.0), ("01YZII", -1j)])
>>> obs.clear()
>>> assert obs == SparseObservable.zero(obs.num_qubits)

conjugate

conjugate()

Calculate the complex conjugation of this observable.

This operation is defined in terms of the standard matrix conventions of Qiskit, in that the matrix form is taken to be in the $Z$ computational basis. The $X$- and $Z$-related alphabet terms are unaffected by the complex conjugation, but $Y$-related terms modify their alphabet terms. Precisely:

  • YY conjguates to Y-Y
  • rr\lvert r\rangle\langle r\rvert conjugates to ll\lvert l\rangle\langle l\rvert
  • ll\lvert l\rangle\langle l\rvert conjugates to rr\lvert r\rangle\langle r\rvert

Additionally, all coefficients are conjugated.

Examples

>>> obs = SparseObservable([("III", 1j), ("Yrl", 0.5)])
>>> assert obs.conjugate() == SparseObservable([("III", -1j), ("Ylr", -0.5)])

copy

copy()

Get a copy of this observable.

Examples

>>> obs = SparseObservable.from_list([("IXZ+lr01", 2.5), ("ZXI-rl10", 0.5j)])
>>> assert obs == obs.copy()
>>> assert obs is not obs.copy()

expand

expand(other, /)

Reverse-order tensor product.

This is equivalent to other.tensor(self), except that other will first be type-cast to SparseObservable if it isn’t already one (by calling the default constructor).

Parameters

other – the observable to put on the left-hand side of the tensor product.

Examples

This is equivalent to tensor() with the order of the arguments flipped:

>>> left = SparseObservable.from_label("XYZ")
>>> right = SparseObservable.from_label("+-IIrl")
>>> assert left.tensor(right) == right.expand(left)
See also

tensor()

The same function with the order of arguments flipped. tensor() is the more standard argument ordering, and matches Qiskit’s other conventions.

from_label

static from_label(label, /)

Construct a single-term observable from a dense string label.

The resulting operator will have a coefficient of 1. The label must be a sequence of the alphabet 'IXYZ+-rl01'. The label is interpreted analogously to a bitstring. In other words, the right-most letter is associated with qubit 0, and so on. This is the same as the labels for Pauli and SparsePauliOp.

Parameters

label (str) – the dense label.

Examples

>>> SparseObservable.from_label("IIII+ZI")
<SparseObservable with 1 term on 7 qubits: (1+0j)(+_2 Z_1)>
>>> label = "IYXZI"
>>> pauli = Pauli(label)
>>> assert SparseObservable.from_label(label) == SparseObservable.from_pauli(pauli)
See also

from_list()

A generalization of this method that constructs a sum operator from multiple labels and their corresponding coefficients.

from_list

static from_list(iter, /, *, num_qubits=None)

Construct an observable from a list of dense labels and coefficients.

This is analogous to SparsePauliOp.from_list(), except it uses the extended alphabet of SparseObservable. In this dense form, you must supply all identities explicitly in each label.

The label must be a sequence of the alphabet 'IXYZ+-rl01'. The label is interpreted analogously to a bitstring. In other words, the right-most letter is associated with qubit 0, and so on. This is the same as the labels for Pauli and SparsePauliOp.

Parameters

  • iter (list[tuple[str, complex]]) – Pairs of labels and their associated coefficients to sum. The labels are interpreted the same way as in from_label().
  • num_qubits (int | None) – It is not necessary to specify this if you are sure that iter is not an empty sequence, since it can be inferred from the label lengths. If iter may be empty, you must specify this argument to disambiguate how many qubits the observable is for. If this is given and iter is not empty, the value must match the label lengths.

Examples

Construct an observable from a list of labels of the same length:

>>> SparseObservable.from_list([
...     ("III++", 1.0),
...     ("II--I", 1.0j),
...     ("I++II", -0.5),
...     ("--III", -0.25j),
... ])
<SparseObservable with 4 terms on 5 qubits:
    (1+0j)(+_1 +_0) + (0+1j)(-_2 -_1) + (-0.5+0j)(+_3 +_2) + (-0-0.25j)(-_4 -_3)>

Use num_qubits to disambiguate potentially empty inputs:

>>> SparseObservable.from_list([], num_qubits=10)
<SparseObservable with 0 terms on 10 qubits: 0.0>

This method is equivalent to calls to from_sparse_list() with the explicit qubit-arguments field set to decreasing integers:

>>> labels = ["XY+Z", "rl01", "-lXZ"]
>>> coeffs = [1.5j, 2.0, -0.5]
>>> from_list = SparseObservable.from_list(list(zip(labels, coeffs)))
>>> from_sparse_list = SparseObservable.from_sparse_list([
...     (label, (3, 2, 1, 0), coeff)
...     for label, coeff in zip(labels, coeffs)
... ])
>>> assert from_list == from_sparse_list
See also

from_label()

A similar constructor, but takes only a single label and always has its coefficient set to 1.0.

from_sparse_list()

Construct the observable from a list of labels without explicit identities, but with the qubits each single-qubit term applies to listed explicitly.

from_pauli

static from_pauli(pauli, /)

Construct a SparseObservable from a single Pauli instance.

The output observable will have a single term, with a unitary coefficient dependent on the phase.

Parameters

pauli (Pauli) – the single Pauli to convert.

Examples

>>> label = "IYXZI"
>>> pauli = Pauli(label)
>>> SparseObservable.from_pauli(pauli)
<SparseObservable with 1 term on 5 qubits: (1+0j)(Y_3 X_2 Z_1)>
>>> assert SparseObservable.from_label(label) == SparseObservable.from_pauli(pauli)

from_raw_parts

static from_raw_parts(num_qubits, coeffs, bit_terms, indices, boundaries, check=True)

Construct a SparseObservable from raw Numpy arrays that match the required data representation described in the class-level documentation.

The data from each array is copied into fresh, growable Rust-space allocations.

Parameters

  • num_qubits – number of qubits in the observable.

  • coeffs – complex coefficients of each term of the observable. This should be a Numpy array with dtype complex128.

  • bit_terms – flattened list of the single-qubit terms comprising all complete terms. This should be a Numpy array with dtype uint8 (which is compatible with BitTerm).

  • indices – flattened term-wise sorted list of the qubits each single-qubit term corresponds to. This should be a Numpy array with dtype uint32.

  • boundaries – the indices that partition bit_terms and indices into terms. This should be a Numpy array with dtype uintp.

  • check

    if True (the default), validate that the data satisfies all coherence guarantees. If False, no checks are done.

    Warning

    If check=False, the bit_terms absolutely must be all be valid values of SparseObservable.BitTerm. If they are not, Rust-space undefined behavior may occur, entirely invalidating the program execution.

Examples

Construct a sum of ZZ on each individual qubit:

>>> num_qubits = 100
>>> terms = np.full((num_qubits,), SparseObservable.BitTerm.Z, dtype=np.uint8)
>>> indices = np.arange(num_qubits, dtype=np.uint32)
>>> coeffs = np.ones((num_qubits,), dtype=complex)
>>> boundaries = np.arange(num_qubits + 1, dtype=np.uintp)
>>> SparseObservable.from_raw_parts(num_qubits, coeffs, terms, indices, boundaries)
<SparseObservable with 100 terms on 100 qubits: (1+0j)(Z_0) + ... + (1+0j)(Z_99)>

from_sparse_list

static from_sparse_list(iter, /, num_qubits)

Construct an observable from a list of labels, the qubits each item applies to, and the coefficient of the whole term.

This is analogous to SparsePauliOp.from_sparse_list(), except it uses the extended alphabet of SparseObservable.

The “labels” and “indices” fields of the triples are associated by zipping them together. For example, this means that a call to from_list() can be converted to the form used by this method by setting the “indices” field of each triple to (num_qubits-1, ..., 1, 0).

Parameters

  • iter (list[tuple[str, Sequence[int], complex]]) – triples of labels, the qubits each single-qubit term applies to, and the coefficient of the entire term.
  • num_qubits (int) – the number of qubits in the operator.

Examples

Construct a simple operator:

>>> SparseObservable.from_sparse_list(
...     [("ZX", (1, 4), 1.0), ("YY", (0, 3), 2j)],
...     num_qubits=5,
... )
<SparseObservable with 2 terms on 5 qubits: (1+0j)(X_4 Z_1) + (0+2j)(Y_3 Y_0)>

Construct the identity observable (though really, just use identity()):

>>> SparseObservable.from_sparse_list([("", (), 1.0)], num_qubits=100)
<SparseObservable with 1 term on 100 qubits: (1+0j)()>

This method can replicate the behavior of from_list(), if the qubit-arguments field of the triple is set to decreasing integers:

>>> labels = ["XY+Z", "rl01", "-lXZ"]
>>> coeffs = [1.5j, 2.0, -0.5]
>>> from_list = SparseObservable.from_list(list(zip(labels, coeffs)))
>>> from_sparse_list = SparseObservable.from_sparse_list([
...     (label, (3, 2, 1, 0), coeff)
...     for label, coeff in zip(labels, coeffs)
... ])
>>> assert from_list == from_sparse_list

from_sparse_pauli_op

static from_sparse_pauli_op(op, /)

Construct a SparseObservable from a SparsePauliOp instance.

This will be a largely direct translation of the SparsePauliOp; in particular, there is no on-the-fly summing of like terms, nor any attempt to refactorize sums of Pauli terms into equivalent projection operators.

Parameters

op (SparsePauliOp) – the operator to convert.

Examples

>>> spo = SparsePauliOp.from_list([("III", 1.0), ("IIZ", 0.5), ("IZI", 0.5)])
>>> SparseObservable.from_sparse_pauli_op(spo)
<SparseObservable with 3 terms on 3 qubits: (1+0j)() + (0.5+0j)(Z_0) + (0.5+0j)(Z_1)>

from_terms

static from_terms(obj, /, num_qubits=None)

Construct a SparseObservable out of individual terms.

All the terms must have the same number of qubits. If supplied, the num_qubits argument must match the terms.

No simplification is done as part of the observable creation.

Parameters

  • obj (Iterable[Term]) – Iterable of individual terms to build the observable from.
  • num_qubits (int | None) – The number of qubits the observable should act on. This is usually inferred from the input, but can be explicitly given to handle the case of an empty iterable.

Returns

The corresponding observable.

identity

static identity(num_qubits)

Get the identity operator over the given number of qubits.

Examples

Get the identity operator for 100 qubits:

>>> SparseObservable.identity(100)
<SparseObservable with 1 term on 100 qubits: (1+0j)()>

pauli_bases

pauli_bases()

Get a PauliList object that represents the measurement basis needed for each term (in order) in this observable.

For example, the projector 0l+ will return a Pauli ZXY. The resulting Pauli is dense, in the sense that explicit identities are stored. An identity in the Pauli output does not require a concrete measurement.

This will return an entry in the Pauli list for every term in the sum.

Returns

the Pauli operator list representing the necessary measurement bases.

Return type

PauliList

simplify

simplify(tol=1e-08)

Sum any like terms in this operator, removing them if the resulting complex coefficient has an absolute value within tolerance of zero.

As a side effect, this sorts the operator into canonical order.

Note

When using this for equality comparisons, note that floating-point rounding and the non-associativity fo floating-point addition may cause non-zero coefficients of summed terms to compare unequal. To compare two observables up to a tolerance, it is safest to compare the canonicalized difference of the two observables to zero.

Parameters

tol (float) – after summing like terms, any coefficients whose absolute value is less than the given absolute tolerance will be suppressed from the output.

Examples

Using simplify() to compare two operators that represent the same observable, but would compare unequal due to the structural tests by default:

>>> base = SparseObservable.from_sparse_list([
...     ("XZ", (2, 1), 1e-10),  # value too small
...     ("+-", (3, 1), 2j),
...     ("+-", (3, 1), 2j),     # can be combined with the above
...     ("01", (3, 1), 0.5),    # out of order compared to `expected`
... ], num_qubits=5)
>>> expected = SparseObservable.from_list([("I0I1I", 0.5), ("I+I-I", 4j)])
>>> assert base != expected  # non-canonical comparison
>>> assert base.simplify() == expected.simplify()

Note that in the above example, the coefficients are chosen such that all floating-point calculations are exact, and there are no intermediate rounding or associativity concerns. If this cannot be guaranteed to be the case, the safer form is:

>>> left = SparseObservable.from_list([("XYZ", 1.0/3.0)] * 3)   # sums to 1.0
>>> right = SparseObservable.from_list([("XYZ", 1.0/7.0)] * 7)  # doesn't sum to 1.0
>>> assert left.simplify() != right.simplify()
>>> assert (left - right).simplify() == SparseObservable.zero(left.num_qubits)

tensor

tensor(other, /)

Tensor product of two observables.

The bit ordering is defined such that the qubit indices of the argument will remain the same, and the indices of self will be offset by the number of qubits in other. This is the same convention as used by the rest of Qiskit’s quantum_info operators.

This function is used for the infix ^ operator. If using this operator, beware that Python’s operator-precedence rules may cause the evaluation order to be different to your expectation. In particular, the operator + binds more tightly than ^, just like * binds more tightly than +. Use parentheses to fix the evaluation order, if needed.

The argument will be cast to SparseObservable using its default constructor, if it is not already in the correct form.

Parameters

other – the observable to put on the right-hand side of the tensor product.

Examples

The bit ordering of this is such that the tensor product of two observables made from a single label “looks like” an observable made by concatenating the two strings:

>>> left = SparseObservable.from_label("XYZ")
>>> right = SparseObservable.from_label("+-IIrl")
>>> assert left.tensor(right) == SparseObservable.from_label("XYZ+-IIrl")

You can also use the infix ^ operator for tensor products, which will similarly cast the right-hand side of the operation if it is not already a SparseObservable:

>>> assert SparseObservable("rl") ^ Pauli("XYZ") == SparseObservable("rlXYZ")
See also

expand()

The same function, but with the order of arguments flipped. This can be useful if you like using the casting behavior for the argument, but you want your existing SparseObservable to be on the right-hand side of the tensor ordering.

transpose

transpose()

Calculate the matrix transposition of this observable.

This operation is defined in terms of the standard matrix conventions of Qiskit, in that the matrix form is taken to be in the $Z$ computational basis. The $X$- and $Z$-related alphabet terms are unaffected by the transposition, but $Y$-related terms modify their alphabet terms. Precisely:

  • YY transposes to Y-Y
  • rr\lvert r\rangle\langle r\rvert transposes to ll\lvert l\rangle\langle l\rvert
  • ll\lvert l\rangle\langle l\rvert transposes to rr\lvert r\rangle\langle r\rvert

Examples

>>> obs = SparseObservable([("III", 1j), ("Yrl", 0.5)])
>>> assert obs.transpose() == SparseObservable([("III", 1j), ("Ylr", -0.5)])

zero

static zero(num_qubits)

Get the zero operator over the given number of qubits.

The zero operator is the operator whose expectation value is zero for all quantum states. It has no terms. It is the identity element for addition of two SparseObservable instances; anything added to the zero operator is equal to itself.

If you want the projector onto the all zeros state, use:

>>> num_qubits = 10
>>> all_zeros = SparseObservable.from_label("0" * num_qubits)

Examples

Get the zero operator for 100 qubits:

>>> SparseObservable.zero(100)
<SparseObservable with 0 terms on 100 qubits: 0.0>
Was this page helpful?
Report a bug or request content on GitHub.