Skip to main contentIBM Quantum Documentation
This page is from the dev version of Qiskit SDK. Go to the stable version.
Important

IBM Quantum Platform is moving and this version will be sunset on July 1. To get started on the new platform, read the migration guide.

PauliLindbladMap

class qiskit.quantum_info.PauliLindbladMap

GitHub

Bases: object

A Pauli Lindblad map stored in a qubit-sparse format.


Mathematics

A Pauli-Lindblad map is a linear map acting on density matrices on nn-qubits of the form:

Λ[]=exp(PKλP(PP))\Lambda\bigl[\circ\bigr] = \exp\left(\sum_{P \in K} \lambda_P (P \circ P - \circ)\right)

where KK is a subset of nn-qubit Pauli operators, and the rates, or coefficients, λP\lambda_P are real numbers. When all the rates λP\lambda_P are non-negative, this corresponds to a completely positive and trace preserving map. The sum in the exponential is called the generator, and each individual term the generators. To simplify notation in the rest of the documention, we denote L(P)[]=PPL(P)\bigl[\circ\bigr] = P \circ P - \circ.


Quasi-probability representation

The map Λ\Lambda can be written as a product:

Λ[]=PKexp(λP(PP)).\Lambda\bigl[\circ\bigr] = \prod_{P \in K}\exp\left(\lambda_P(P \circ P - \circ)\right).

For each PP, it holds that

exp(λP(PP))=ω(λP)+(1ω(λP))PP,\exp\left(\lambda_P(P \circ P - \circ)\right) = \omega(\lambda_P) \circ + (1 - \omega(\lambda_P)) P \circ P,

where ω(x)=12(1+e2x)\omega(x) = \frac{1}{2}(1 + e^{-2 x}). Observe that if λP0\lambda_P \geq 0, then ω(λP)(12,1]\omega(\lambda_P) \in (\frac{1}{2}, 1], and this term is a completely-positive and trace-preserving map. However, if λP<0\lambda_P < 0, then ω(λP)>1\omega(\lambda_P) > 1 and the map is not completely positive or trace preserving. Letting γP=ω(λP)+1ω(λP)\gamma_P = \omega(\lambda_P) + |1 - \omega(\lambda_P)|, pP=ω(λP)/γPp_P = \omega(\lambda_P) / \gamma_P and bP{0,1}b_P \in \{0, 1\} be 11 if λP<0\lambda_P < 0 and 00 otherwise, we rewrite the map as:

ω(λP)+(1ω(λP))PP=γP(pP+(1)bP(1pP)PP).\omega(\lambda_P) \circ + (1 - \omega(\lambda_P)) P \circ P = \gamma_P \left(p_P \circ + (-1)^{b_P}(1 - p_P) P \circ P\right).

If λP0\lambda_P \geq 0, γP=1\gamma_P = 1 and the expression reduces to the standard mixture of the identity map and conjugation by PP. If λP<0\lambda_P < 0, γP>1\gamma_P > 1, and the map is a scaled difference of the identity map and conjugation by PP, with probability weights (hence “quasi-probability”). Note that this is a slightly different presentation than in the literature, but this notation allows us to handle both non-negative and negative rates simultaneously. The overall γ\gamma of the channel is the product γ=PKγP\gamma = \prod_{P \in K} \gamma_P.

See the PauliLindbladMap.sample() method for the sampling procedure for this map.


Representation

Each individual Pauli operator in the generator is a tensor product of single-qubit Pauli operators of the form P=nAi(n)P = \bigotimes_n A^{(n)}_i, for Ai(n){I,X,Y,Z}A^{(n)}_i \in \{I, X, Y, Z\}. The internal representation of a PauliLindbladMap stores only the non-identity single-qubit Pauli operators. This makes it significantly more efficient to represent generators such as nqubitscnL(Z(n))\sum_{n\in \text{qubits}} c_n L(Z^{(n)}); for which PauliLindbladMap requires an amount of memory linear in the total number of qubits.

Internally, PauliLindbladMap stores an array of rates and a QubitSparsePauliList containing the corresponding sparse Pauli operators. Additionally, PauliLindbladMap can compute the overall channel γ\gamma in the get_gamma() method, as well as the corresponding probabilities (or quasi-probabilities) via the get_probabilities() method.

Indexing

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

Each generator term appears as an instance a self-contained class. The individual terms are copied out of the base map; mutations to them will not affect the original map from which they are indexed.

GeneratorTerm

class GeneratorTerm

GitHub

Bases: object

A single term from a complete PauliLindbladMap.

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

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

The number of qubits the term is defined on.

pauli_labels

pauli_labels()

Return the pauli labels of the term as a string.

The pauli labels will match the order of GeneratorTerm.indices, such that the i-th character in the string is applied to the qubit index at term.indices[i]. E.g. the term with operator X acting on qubit 0 and Y acting on qubit 3 will have term.indices == np.array([0, 3]) and term.pauli_labels == "XY".

Returns

The non-identity bit terms as a concatenated string.

paulis

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

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

qubit_sparse_pauli

The term’s qubit_sparse_pauli.

rate

The term’s rate.

to_pauli_lindblad_map

to_pauli_lindblad_map()

Convert this term to a complete PauliLindbladMap.


Construction

PauliLindbladMap 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_list()Generators given as a list of tuples of dense string labels and the associated rates.
from_sparse_list()Generators given as a list of tuples of sparse string labels, the qubits they apply to, and their rates.
from_terms()Sum explicit single GeneratorTerm instances.
from_components()Build from an array of rates and a QubitSparsePauliList instance.

__new__

__new__(data, /, num_qubits=None)

The default constructor of PauliLindbladMap.

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 PauliLindbladMap, in which case the input is copied, 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 map. 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 maps.

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

Conversions

An existing PauliLindbladMap can be converted into other formats.

MethodSummary
to_sparse_list()Express the map in a sparse list format with elements (paulis, indices, rate).

Attributes

num_qubits

The number of qubits the map acts on.

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

num_terms

The number of generator terms in the exponent for this map.

rates

The rates for the map.


Methods

apply_layout

apply_layout(layout, num_qubits=None)

Apply a transpiler layout to this Pauli Lindblad map.

This enables remapping of qubit indices, e.g. if the map is defined in terms of virtual qubit labels.

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 map).
  • num_qubits (int | None) – The number of qubits to expand the map 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 PauliLindbladMap with the provided layout applied.

clear

clear()

Clear all the generator terms from this map, making it equal to the identity map again.

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

Examples

>>> pauli_lindblad_map = PauliLindbladMap.from_list([("IXXXYY", 2.0), ("ZZYZII", -1)])
>>> pauli_lindblad_map.clear()
>>> assert pauli_lindblad_map == PauliLindbladMap.identity(pauli_lindblad_map.py_num_qubits())

compose

compose(other)

Compose with another PauliLindbladMap.

This appends the internal arrays of self and other, and therefore results in a map with whose enumerated terms are those of self followed by those of other.

Parameters

other (PauliLindbladMap) – the Pauli Lindblad map to compose with.

copy

copy()

Get a copy of this Pauli Lindblad map.

Examples

>>> pauli_lindblad_map = PauliLindbladMap.from_list([("IXZXYYZZ", 2.5), ("ZXIXYYZZ", 0.5)])
>>> assert pauli_lindblad_map == pauli_lindblad_map.copy()
>>> assert pauli_lindblad_map is not pauli_lindblad_map.copy()

drop_paulis

drop_paulis(indices)

Drop Paulis out of this Pauli Lindblad map.

Drop every Pauli on the given indices, effectively replacing them with an identity.

Parameters

indices (Sequence[int]) – The indices for which Paulis must be dropped.

Returns

A new Pauli Lindblad map where every Pauli on the given indices has been dropped.

Examples

>>> pauli_map_in = PauliLindbladMap.from_list([("XXIZI", 2.0), ("IIIYZ", 0.5), ("ZIIXY", -0.25)])
>>> pauli_map_out = pauli_map_in.keep_paulis([1, 2, 4])
>>> assert pauli_map_out == PauliLindbladMap.from_list([("XIIZI", 2.0), ("IIIYI", 0.5), ("ZIIXI", -0.25)])

from_components

static from_components(rates, qubit_sparse_pauli_list)

from_list

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

Construct a Pauli Lindblad map from a list of dense generator labels and rates.

This is analogous to SparsePauliOp.from_list(). In this dense form, you must supply all identities explicitly in each label.

The label must be a sequence of the alphabet 'IXYZ'. 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, float]]) – Pairs of labels and their associated rates in the generator sum.
  • 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 map acts on. If this is given and iter is not empty, the value must match the label lengths.

Examples

Construct a Pauli Lindblad map from a list of labels:

>>> PauliLindbladMap.from_list([
...     ("IIIXX", 1.0),
...     ("IIYYI", 1.0),
...     ("IXXII", -0.5),
...     ("ZZIII", -0.25),
... ])
<PauliLindbladMap with 4 terms on 5 qubits:
    (1)L(X_1 X_0) + (1)L(Y_2 Y_1) + (-0.5)L(X_3 X_2) + (-0.25)L(Z_4 Z_3)>

Use num_qubits to disambiguate potentially empty inputs:

>>> PauliLindbladMap.from_list([], num_qubits=10)
<PauliLindbladMap 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 = ["XYXZ", "YYZZ", "XYXZ"]
>>> rates = [1.5, 2.0, -0.5]
>>> from_list = PauliLindbladMap.from_list(list(zip(labels, rates)))
>>> from_sparse_list = PauliLindbladMap.from_sparse_list([
...     (label, (3, 2, 1, 0), rate)
...     for label, rate in zip(labels, rates)
... ])
>>> assert from_list == from_sparse_list
See also

from_sparse_list()

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

from_sparse_list

static from_sparse_list(iter, /, num_qubits)

Construct a Pauli Lindblad map from a list of labels, the qubits each item applies to, and the rate of the whole term.

This is analogous to SparsePauliOp.from_sparse_list().

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], float]]) – triples of labels, the qubits each single-qubit term applies to, and the rate of the entire term.
  • num_qubits (int) – the number of qubits the map acts on.

Examples

Construct a simple map:

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

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

>>> labels = ["XYXZ", "YYZZ", "XYXZ"]
>>> rates = [1.5, 2.0, -0.5]
>>> from_list = PauliLindbladMap.from_list(list(zip(labels, rates)))
>>> from_sparse_list = PauliLindbladMap.from_sparse_list([
...     (label, (3, 2, 1, 0), rate)
...     for label, rate in zip(labels, rates)
... ])
>>> assert from_list == from_sparse_list
See also

to_sparse_list()

The reverse of this method.

from_terms

static from_terms(obj, /, num_qubits=None)

Construct a PauliLindbladMap 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 map creation.

Parameters

  • obj (Iterable[Term]) – Iterable of individual terms to build the map generator from.
  • num_qubits (int | None) – The number of qubits the map 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 map.

gamma

gamma()

Calculate the γ\gamma for the map.

get_qubit_sparse_pauli_list_copy

get_qubit_sparse_pauli_list_copy()

Get a copy of the map’s qubit sparse pauli list.

identity

static identity(num_qubits)

Get the identity map on the given number of qubits.

The identity map contains no generator terms, and is the identity element for composition of two PauliLindbladMap instances; anything composed with the identity map is equal to itself.

Examples

Get the identity map on 100 qubits:

>>> PauliLindbladMap.identity(100)
<PauliLindbladMap with 0 terms on 100 qubits: 0.0>

inverse

inverse()

Return a new PauliLindbladMap that is the mathematical inverse of self.

keep_paulis

keep_paulis(indices)

Keep every Pauli on the given indices and drop all others.

This is equivalent to using PauliLindbladMap.drop_paulis() on the complement set of indices.

Parameters

indices (Sequence[int]) – The indices for which Paulis must be kept.

Returns

A new Pauli Lindblad map where every Pauli on the given indices has been kept and all other Paulis have been dropped.

Examples

>>> pauli_map_in = PauliLindbladMap.from_list([("XXIZI", 2.0), ("IIIYZ", 0.5), ("ZIIXY", -0.25)])
>>> pauli_map_out = pauli_map_in.keep_paulis([1, 2, 4])
>>> assert pauli_map_out == PauliLindbladMap.from_list([("XIIZI", 2.0), ("IIIYI", 0.5), ("ZIIXI", -0.25)])

pauli_fidelity

pauli_fidelity(qubit_sparse_pauli)

Compute the Pauli fidelity of this map for a qubit sparse Pauli.

For a Pauli QQ, the fidelity with respect to the Pauli Lindblad map Λ\Lambda is the real number f(Q)f(Q) for which Λ(Q)=f(Q)Q\Lambda(Q) = f(Q) Q. I.e. every Pauli is an eigenvector of the linear map Λ\Lambda, and the fidelity is the corresponding eigenvalue. For a Pauli Lindblad map with generator set KK and rate function λ:KR\lambda : K \rightarrow \mathbb{R}, the pauli fidelity mathematically is

f(Q)=exp(2PKλ(P)P,Qsp),f(Q) = \exp\left(-2 \sum_{P \in K} \lambda(P) \langle P, Q\rangle_{sp}\right),

where P,Qsp\langle P, Q\rangle_{sp} is 00 if PP and QQ commute, and 11 if they anti-commute.

Args: qubit_sparse_pauli (QubitSparsePauli): the qubit sparse Pauli to compute the Pauli

fidelity of.

probabilities

probabilities()

Calculate the probabilities for the map.

sample

sample(num_samples, seed=None)

For PauliLindbladMap instances with purely non-negative rates, sample Pauli operators from the map. If the map has negative rates, use PauliLindbladMap.signed_sample().

Given the quasi-probability representation given in the class-level documentation, each sample is drawn via the following process:

  • Initialize a :class`~.QubitSparsePauli` instance to the identity operator.
  • Iterate through each Pauli in the map. Using the pseudo-probability associated with each operator, randomly choose between applying the operator or not.
  • If the operator is applied, update the :class`QubitSparsePauli` by multiplying it with the Pauli.

The sampled qubit sparse Paulis are returned in the form of a QubitSparsePauliList.

Parameters

  • num_samples (int) – Number of samples to draw.
  • seed (int) – Random seed. Defaults to None.

Returns

The list of qubit sparse paulis.

Return type

qubit_sparse_pauli_list

Raises

ValueError – If any of the rates in the map are negative.

scale_rates

scale_rates(scale_factor)

Return a new PauliLindbladMap with rates scaled by scale_factor.

Parameters

scale_factor (float) – the scaling coefficient.

signed_sample

signed_sample(num_samples, seed=None)

Sample sign and Pauli operator pairs from the map.

Each sign is represented by a boolean, with True representing +1, and False representing -1.

Given the quasi-probability representation given in the class-level documentation, each sample is drawn via the following process:

  • Initialize the sign boolean, and a QubitSparsePauli instance to the identity operator.
  • Iterate through each Pauli in the map. Using the pseudo-probability associated with each operator, randomly choose between applying the operator or not.
  • If the operator is applied, update the :class`QubitSparsePauli` by multiplying it with the Pauli. If the rate associated with the Pauli is negative, flip the sign boolean.

The results are returned as a 1d array of booleans, and the corresponding sampled qubit sparse Paulis in the form of a QubitSparsePauliList.

Parameters

  • num_samples (int) – Number of samples to draw.
  • seed (int) – Random seed.

Returns

The boolean array of signs and the list of qubit sparse paulis.

Return type

signs, qubit_sparse_pauli_list

simplify

simplify(tol=1e-08)

Sum any like terms in the generator, removing them if the resulting rate has an absolute value within tolerance of zero. This also removes terms whose Pauli operator is proportional to the identity, as the correponding generator is actually the zero map.

As a side effect, this sorts the generators into a fixed 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 rates 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 map, but would compare unequal due to the structural tests by default:

>>> base = PauliLindbladMap.from_sparse_list([
...     ("XZ", (2, 1), 1e-10),  # value too small
...     ("XX", (3, 1), 2),
...     ("XX", (3, 1), 2),      # can be combined with the above
...     ("ZZ", (3, 1), 0.5),    # out of order compared to `expected`
... ], num_qubits=5)
>>> expected = PauliLindbladMap.from_list([("IZIZI", 0.5), ("IXIXI", 4)])
>>> 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 = PauliLindbladMap.from_list([("XYZ", 1.0/3.0)] * 3)   # sums to 1.0
>>> right = PauliLindbladMap.from_list([("XYZ", 1.0/7.0)] * 7)  # doesn't sum to 1.0
>>> assert left.simplify() != right.simplify()
>>> assert left.compose(right.inverse()).simplify() == PauliLindbladMap.identity(left.num_qubits)

to_sparse_list

to_sparse_list()

Express the map in terms of a sparse list format.

This can be seen as counter-operation of PauliLindbladMap.from_sparse_list(), however the order of terms is not guaranteed to be the same at after a roundtrip to a sparse list and back.

Examples

>>> pauli_lindblad_map = PauliLindbladMap.from_list([("IIXIZ", 2), ("IIZIX", 3)])
>>> reconstructed = PauliLindbladMap.from_sparse_list(pauli_lindblad_map.to_sparse_list(), pauli_lindblad_map.num_qubits)
See also

from_sparse_list()

The constructor that can interpret these lists.

Was this page helpful?
Report a bug or request content on GitHub.