qiskit/docs/cdoc/qk-obs.rst

254 lines
13 KiB
ReStructuredText

=====
QkObs
=====
.. code-block:: c
typedef struct QkObs QkObs
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
.. math::
\text{\texttt{QkObs}} = \sum_i c_i \bigotimes_n A^{(n)}_i
for complex numbers :math:`c_i` and single-qubit operators acting on qubit :math:`n` from a
restricted alphabet :math:`A^{(n)}_i`. The sum over :math:`i` is the sum of the individual
terms, and the tensor product produces the operator strings.
The alphabet of allowed single-qubit operators that the :math:`A^{(n)}_i` are drawn from is the
Pauli operators and the Pauli-eigenstate projection operators. Explicitly, these are:
.. _qkobs-alphabet:
.. table:: Alphabet of single-qubit terms used in ``QkObs``
+----------------------------------------+--------------------+----------------+
| Operator | ``QkBitTerm`` | Numeric value |
+========================================+====================+================+
| :math:`I` (identity) | Not stored. | Not stored. |
+----------------------------------------+--------------------+----------------+
| :math:`X` (Pauli X) | ``QkBitTerm_X`` | ``0b0010`` (2) |
+----------------------------------------+--------------------+----------------+
| :math:`Y` (Pauli Y) | ``QkBitTerm_Y`` | ``0b0011`` (3) |
+----------------------------------------+--------------------+----------------+
| :math:`Z` (Pauli Z) | ``QkBitTerm_Z`` | ``0b0001`` (1) |
+----------------------------------------+--------------------+----------------+
| :math:`\lvert+\rangle\langle+\rvert` | ``QkBitTerm_Plus`` | ``0b1010`` (10)|
| (projector to positive eigenstate of X)| | |
+----------------------------------------+--------------------+----------------+
| :math:`\lvert-\rangle\langle-\rvert` | ``QkBitTerm_Minus``| ``0b0110`` (6) |
| (projector to negative eigenstate of X)| | |
+----------------------------------------+--------------------+----------------+
| :math:`\lvert r\rangle\langle r\rvert` | ``QkBitTerm_Right``| ``0b1011`` (11)|
| (projector to positive eigenstate of Y)| | |
+----------------------------------------+--------------------+----------------+
| :math:`\lvert l\rangle\langle l\rvert` | ``QkBitTerm_Left`` | ``0b0111`` (7) |
| (projector to negative eigenstate of Y)| | |
+----------------------------------------+--------------------+----------------+
| :math:`\lvert0\rangle\langle0\rvert` | ``QkBitTerm_Zero`` | ``0b1001`` (9) |
| (projector to positive eigenstate of Z)| | |
+----------------------------------------+--------------------+----------------+
| :math:`\lvert1\rangle\langle1\rvert` | ``QkBitTerm_One`` | ``0b0101`` (5) |
| (projector to negative eigenstate of Z)| | |
+----------------------------------------+--------------------+----------------+
Due to allowing both the Paulis and their projectors, 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. As a consequence, comparison requires additional care and using
``qk_obs_canonicalize`` on two mathematically equivalent observables might not result in the same
representation.
``QkObs`` uses its particular overcomplete basis with the aim of making
"efficiency of measurement" equivalent to "efficiency of representation". For example, the
observable :math:`{\lvert0\rangle\langle0\rvert}^{\otimes n}` can be efficiently measured on
hardware with simple :math:`Z` measurements, but can only be represented in terms of Paulis
as :math:`{(I + Z)}^{\otimes n}/2^n`, which requires :math:`2^n` stored terms. ``QkObs`` requires
only a single term to store this. The downside to this is that it is impractical to take an
arbitrary matrix and find the *best* ``QkObs`` representation. You typically will want to construct
a ``QkObs`` directly, rather than trying to decompose into one.
Representation
==============
The internal representation of a ``QkObs`` stores only the non-identity qubit
operators. This makes it significantly more efficient to represent observables such as
:math:`\sum_{n\in \text{qubits}} Z^{(n)}`; ``QkObs`` requires an amount of
memory linear in the total number of qubits.
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:
.. _qkobs-arrays:
.. table:: Data arrays used to represent ``QkObs``
======================= =========== ============================================================
Attribute accessible by Length Description
======================= =========== ============================================================
``qk_obs_coeffs`` :math:`t` The complex scalar multiplier for each term.
``qk_obs_bit_terms`` :math:`s` Each of the non-identity single-qubit terms for all of
the operators, in order. These correspond to the
non-identity :math:`A^{(n)}_i` in the sum description,
where the entries are stored in order of increasing
:math:`i` first, and in order of increasing :math:`n`
within each term.
``qk_obs_indices`` :math:`s` The corresponding qubit (:math:`n`) for each of the
bit terms. ``QkObs`` requires that this list is term-wise
sorted, and algorithms can rely on this invariant being
upheld.
``qk_obs_boundaries`` :math:`t+1` The indices that partition the bit terms and indices
into complete terms. For term number :math:`i`, its
complex coefficient is stored at index ``i``, and its
non-identity single-qubit operators and their corresponding
qubits are in the range ``[boundaries[i], boundaries[i+1])``
in the bit terms and indices, respectively.
The boundaries always have an explicit 0 as their first
element.
======================= =========== ============================================================
The length parameter :math:`t` is the number of terms in the sum and can be queried using
``qk_obs_num_terms``. The parameter :math:`s` is the total number of non-identity single-qubit
terms and can be queried using ``qk_obs_len``.
As illustrative examples:
* in the case of a zero operator, the boundaries are length 1 (a single 0) and all other
vectors are empty.
* in the case of a fully simplified identity operator, the boundaries are ``{0, 0}``,
the coefficients have a single entry, and both the bit terms and indices are empty.
* for the operator :math:`Z_2 Z_0 - X_3 Y_1`, the boundaries are ``{0, 2, 4}``,
the coeffs are ``{1.0, -1.0}``, the bit terms are ``{QkBitTerm_Z, QkBitTerm_Z, QkBitTerm_Y,
QkBitTerm_X}`` and the indices are ``{0, 2, 1, 3}``. The operator might act on more than
four qubits, depending on the the number of qubits (see ``qk_obs_num_qubits``). Note
that the single-bit terms and indices are sorted into termwise sorted order.
These cases are not special, they're fully consistent with the rules and should not need special
handling.
Canonical ordering
------------------
For any given mathematical observable, there are several ways of representing it with
``QkObs``. 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 ``QkObs``
can be in any order while representing the same observable, since addition is commutative
(although while floating-point addition is not associative, ``QkObs`` makes no guarantees about
the summation order).
These two categories of representation degeneracy can cause the operator equality,
``qk_obs_equal``, 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 ``QkObs`` in canonical form by using the ``qk_obs_canonicalize`` function.
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:
.. code-block:: c
bool equivalent(QkObs *left, QkObs *right, double tol) {
// compare a canonicalized version of left - right to the zero observable
QkObs *neg_right = qk_obs_mul(right, -1);
QkObs *diff = qk_obs_add(left, neg_right);
QkObs *canonical = qk_obs_canonicalize(diff, tol);
QkObs *zero = qk_obs_zero(qk_obs_num_qubits(left));
bool equiv = qk_obs_equal(diff, zero);
// free all temporary variables
qk_obs_free(neg_right);
qk_obs_free(diff);
qk_obs_free(canonical);
qk_obs_free(zero);
return equiv;
}
.. note::
The canonical form produced by ``qk_obs_canonicalize`` alone will not universally detect all
observables that are equivalent due to the over-complete basis alphabet.
Indexing
--------
Individual observable sum terms in ``QkObs`` can be accessed via ``qk_obs_term`` and return
objects of type ``QkObsTerm``. These terms then contain fields with the coefficient of the term,
its bit terms, indices and the number of qubits it is defined on. Together with the information
of the number of terms, you can iterate over all observable terms as
.. code-block:: c
size_t num_terms = qk_obs_num_terms(obs); // obs is QkObs*
for (size_t i = 0; i < num_terms; i++) {
QkObsTerm term; // allocate term on stack
int exit = qk_obs_term(obs, i, &term); // get the term (exit > 0 upon index errors)
// do something with the term...
}
.. warning::
Populating a ``QkObsTerm`` via ``qk_obs_term`` will reference data of the original
``QkObs``. Modifying the bit terms or indices will change the observable and can leave
it in an incoherent state.
Construction
============
``QkObs`` can be constructed by initializing an empty observable (with ``qk_obs_zero``) and
iteratively adding terms (with ``qk_obs_add_term``). Alternatively, an observable can be
constructed from "raw" data (with ``qk_obs_new``) if all internal data is specified. This requires
care to ensure the data is coherent and results in a valid observable.
.. _qkobs-constructors:
.. table:: Constructors
=================== =========================================================================
Function Summary
=================== =========================================================================
``qk_obs_zero`` Construct an empty observable on a given number of qubits.
``qk_obs_identity`` Construct the identity observable on a given number of qubits.
``qk_obs_new`` Construct an observable from :ref:`the raw data arrays <qkobs-arrays>`.
=================== =========================================================================
Mathematical manipulation
=========================
``QkObs`` supports fundamental arithmetic operations in between observables or with scalars.
You can:
* add two observables using ``qk_obs_add``
* multiply by a complex number with ``qk_obs_multiply``
* compose (multiply) two observables via ``qk_obs_compose`` and ``qk_obs_compose_map``
Functions
=========
.. doxygengroup:: QkObs
:content-only: