qiskit-documentation/docs/api/qiskit-c/qk-obs.mdx

934 lines
32 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: QkObs (latest version)
description: API reference for QkObs in the latest version of qiskit-c
in_page_toc_min_heading_level: 2
python_api_type: module
python_api_name: QkObs
---
# QkObs
```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
$$
\text{\texttt{QkObs}} = \sum_i c_i \bigotimes_n A^{(n)}_i
$$
for complex numbers $c_i$ and single-qubit operators acting on qubit $n$ from a restricted alphabet $A^{(n)}_i$. The sum over $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 $A^{(n)}_i$ are drawn from is the Pauli operators and the Pauli-eigenstate projection operators. Explicitly, these are:
<span id="qkobs-alphabet" />
| Operator | `QkBitTerm` | Numeric value |
| ------------------------------------------------------------------------ | ----------------- | ------------- |
| $I$ (identity) | Not stored. | Not stored. |
| $X$ (Pauli X) | `QkBitTerm_X` | `0b0010` (2) |
| $Y$ (Pauli Y) | `QkBitTerm_Y` | `0b0011` (3) |
| $Z$ (Pauli Z) | `QkBitTerm_Z` | `0b0001` (1) |
| $\lvert+\rangle\langle+\rvert$ (projector to positive eigenstate of X) | `QkBitTerm_Plus` | `0b1010` (10) |
| $\lvert-\rangle\langle-\rvert$ (projector to negative eigenstate of X) | `QkBitTerm_Minus` | `0b0110` (6) |
| $\lvert r\rangle\langle r\rvert$ (projector to positive eigenstate of Y) | `QkBitTerm_Right` | `0b1011` (11) |
| $\lvert l\rangle\langle l\rvert$ (projector to negative eigenstate of Y) | `QkBitTerm_Left` | `0b0111` (7) |
| $\lvert0\rangle\langle0\rvert$ (projector to positive eigenstate of Z) | `QkBitTerm_Zero` | `0b1001` (9) |
| $\lvert1\rangle\langle1\rvert$ (projector to negative eigenstate of Z) | `QkBitTerm_One` | `0b0101` (5) |
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 ${\lvert0\rangle\langle0\rvert}^{\otimes n}$ can be efficiently measured on hardware with simple $Z$ measurements, but can only be represented in terms of Paulis as ${(I + Z)}^{\otimes n}/2^n$, which requires $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 $\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:
<span id="qkobs-arrays" />
| Attribute accessible by | Length | Description |
| ----------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `qk_obs_coeffs` | $t$ | The complex scalar multiplier for each term. |
| `qk_obs_bit_terms` | $s$ | Each of the non-identity single-qubit terms for all of the operators, in order. These correspond to the non-identity $A^{(n)}_i$ in the sum description, where the entries are stored in order of increasing $i$ first, and in order of increasing $n$ within each term. |
| `qk_obs_indices` | $s$ | The corresponding qubit ($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` | $t+1$ | The indices that partition the bit terms and indices into complete terms. For term number $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 $t$ is the number of terms in the sum and can be queried using `qk_obs_num_terms`. The parameter $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 $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, theyre 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.
<Admonition title="Note" type="note">
If you wish to account for floating-point tolerance in the comparison, it is safest to use a recipe such as:
```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;
}
```
</Admonition>
<Admonition title="Note" type="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.
</Admonition>
### 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
```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...
}
```
<Admonition title="Warning" type="caution">
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.
</Admonition>
## 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.
<span id="qkobs-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 [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
### qk\_obs\_zero
<Function id="qk_obs_zero" signature="QkObs *qk_obs_zero(uint32_t num_qubits)">
Construct the zero observable (without any terms).
<span id="group__QkObs_1autotoc_md3" />
#### Example
```c
QkObs *zero = qk_obs_zero(100);
```
**Parameters**
**num\_qubits** The number of qubits the observable is defined on.
**Returns**
A pointer to the created observable.
</Function>
### qk\_obs\_identity
<Function id="qk_obs_identity" signature="QkObs *qk_obs_identity(uint32_t num_qubits)">
Construct the identity observable.
<span id="group__QkObs_1autotoc_md4" />
#### Example
```c
QkObs *identity = qk_obs_identity(100);
```
**Parameters**
**num\_qubits** The number of qubits the observable is defined on.
**Returns**
A pointer to the created observable.
</Function>
### qk\_obs\_new
<Function id="qk_obs_new" signature="QkObs *qk_obs_new(uint32_t num_qubits, uint64_t num_terms, uint64_t num_bits, QkComplex64 *coeffs, QkBitTerm *bit_terms, uint32_t *indices, uintptr_t *boundaries)">
Construct a new observable from raw data.
<span id="group__QkObs_1autotoc_md5" />
#### Example
```c
// define the raw data for the 100-qubit observable |01><01|_{0, 1} - |+-><+-|_{98, 99}
uint32_t num_qubits = 100;
uint64_t num_terms = 2; // we have 2 terms: |01><01|, -1 * |+-><+-|
uint64_t num_bits = 4; // we have 4 non-identity bits: 0, 1, +, -
complex double coeffs[2] = {1, -1};
QkBitTerm bits[4] = {QkBitTerm_Zero, QkBitTerm_One, QkBitTerm_Plus, QkBitTerm_Minus};
uint32_t indices[4] = {0, 1, 98, 99}; // <-- e.g. {1, 0, 99, 98} would be invalid
size_t boundaries[3] = {0, 2, 4};
QkObs *obs = qk_obs_new(
num_qubits, num_terms, num_bits, coeffs, bits, indices, boundaries
);
```
<span id="group__QkObs_1autotoc_md6" />
#### Safety
<span id="group__QkObs_1autotoc_md6" />
Behavior is undefined if any of the following conditions are violated:
* `coeffs` is a pointer to a `complex double` array of length `num_terms`
* `bit_terms` is a pointer to an array of valid `QkBitTerm` elements of length `num_bits`
* `indices` is a pointer to a `uint32_t` array of length `num_bits`, which is term-wise sorted in strict ascending order, and every element is smaller than `num_qubits`
* `boundaries` is a pointer to a `size_t` array of length `num_terms + 1`, which is sorted in ascending order, the first element is 0 and the last element is smaller than `num_terms`
**Parameters**
* **num\_qubits** The number of qubits the observable is defined on.
* **num\_terms** The number of terms.
* **num\_bits** The total number of non-identity bit terms.
* **coeffs** A pointer to the first element of the coefficients array, which has length `num_terms`.
* **bit\_terms** A pointer to the first element of the bit terms array, which has length `num_bits`.
* **indices** A pointer to the first element of the indices array, which has length `num_bits`. Note that, per term, these *must* be sorted incrementally.
* **boundaries** A pointer to the first element of the boundaries array, which has length `num_terms + 1`.
**Returns**
If the input data was coherent and the construction successful, the result is a pointer to the observable. Otherwise a null pointer is returned.
</Function>
### qk\_obs\_free
<Function id="qk_obs_free" signature="void qk_obs_free(QkObs *obs)">
Free the observable.
<span id="group__QkObs_1autotoc_md7" />
#### Example
```c
QkObs *obs = qk_obs_zero(100);
qk_obs_free(obs);
```
<span id="group__QkObs_1autotoc_md8" />
#### Safety
<span id="group__QkObs_1autotoc_md8" />
Behavior is undefined if `obs` is not either null or a valid pointer to a `QkObs`.
**Parameters**
**obs** A pointer to the observable to free.
</Function>
### qk\_obs\_add\_term
<Function id="qk_obs_add_term" signature="QkExitCode qk_obs_add_term(QkObs *obs, const QkObsTerm *cterm)">
Add a term to the observable.
<span id="group__QkObs_1autotoc_md9" />
#### Example
```c
uint32_t num_qubits = 100;
QkObs *obs = qk_obs_zero(num_qubits);
complex double coeff = 1;
QkBitTerm bit_terms[3] = {QkBitTerm_X, QkBitTerm_Y, QkBitTerm_Z};
uint32_t indices[3] = {0, 1, 2};
QkObsTerm term = {&coeff, 3, bit_terms, indices, num_qubits};
int exit_code = qk_obs_add_term(obs, &term);
```
<span id="group__QkObs_1autotoc_md10" />
#### Safety
<span id="group__QkObs_1autotoc_md10" />
Behavior is undefined if any of the following is violated:
* `obs` is a valid, non-null pointer to a `QkObs`
* `cterm` is a valid, non-null pointer to a `QkObsTerm`
**Parameters**
* **obs** A pointer to the observable.
* **cterm** A pointer to the term to add.
**Returns**
An exit code. This is `>0` if the term is incoherent or adding the term fails.
</Function>
### qk\_obs\_term
<Function id="qk_obs_term" signature="QkExitCode qk_obs_term(QkObs *obs, uint64_t index, QkObsTerm *out)">
Get an observable term by reference.
A `QkObsTerm` contains pointers to the indices and bit terms in the term, which can be used to modify the internal data of the observable. This can leave the observable in an incoherent state and should be avoided, unless great care is taken. It is generally safer to construct a new observable instead of attempting in-place modifications.
<span id="group__QkObs_1autotoc_md11" />
#### Example
```c
QkObs *obs = qk_obs_identity(100);
QkObsTerm term;
int exit_code = qk_obs_term(obs, 0, &term);
// out-of-bounds indices return an error code
// int error = qk_obs_term(obs, 12, &term);
```
<span id="group__QkObs_1autotoc_md12" />
#### Safety
<span id="group__QkObs_1autotoc_md12" />
Behavior is undefined if any of the following is violated
* `obs` is a valid, non-null pointer to a `QkObs`
* `out` is a valid, non-null pointer to a `QkObsTerm`
**Parameters**
* **obs** A pointer to the observable.
* **index** The index of the term to get.
* **out** A pointer to a `QkObsTerm` used to return the observable term.
**Returns**
An exit code.
</Function>
### qk\_obs\_num\_terms
<Function id="qk_obs_num_terms" signature="uintptr_t qk_obs_num_terms(const QkObs *obs)">
Get the number of terms in the observable.
<span id="group__QkObs_1autotoc_md13" />
#### Example
```c
QkObs *obs = qk_obs_identity(100);
size_t num_terms = qk_obs_num_terms(obs); // num_terms==1
```
<span id="group__QkObs_1autotoc_md14" />
#### Safety
<span id="group__QkObs_1autotoc_md14" />
Behavior is undefined `obs` is not a valid, non-null pointer to a `QkObs`.
**Parameters**
**obs** A pointer to the observable.
**Returns**
The number of terms in the observable.
</Function>
### qk\_obs\_num\_qubits
<Function id="qk_obs_num_qubits" signature="uint32_t qk_obs_num_qubits(const QkObs *obs)">
Get the number of qubits the observable is defined on.
<span id="group__QkObs_1autotoc_md15" />
#### Example
```c
QkObs *obs = qk_obs_identity(100);
uint32_t num_qubits = qk_obs_num_qubits(obs); // num_qubits==100
```
<span id="group__QkObs_1autotoc_md16" />
#### Safety
<span id="group__QkObs_1autotoc_md16" />
Behavior is undefined `obs` is not a valid, non-null pointer to a `QkObs`.
**Parameters**
**obs** A pointer to the observable.
**Returns**
The number of qubits the observable is defined on.
</Function>
### qk\_obs\_len
<Function id="qk_obs_len" signature="uintptr_t qk_obs_len(const QkObs *obs)">
Get the number of bit terms/indices in the observable.
<span id="group__QkObs_1autotoc_md17" />
#### Example
```c
QkObs *obs = qk_obs_identity(100);
size_t len = qk_obs_len(obs); // len==0, as there are no non-trivial bit terms
```
<span id="group__QkObs_1autotoc_md18" />
#### Safety
<span id="group__QkObs_1autotoc_md18" />
Behavior is undefined `obs` is not a valid, non-null pointer to a `QkObs`.
**Parameters**
**obs** A pointer to the observable.
**Returns**
The number of terms in the observable.
</Function>
### qk\_obs\_coeffs
<Function id="qk_obs_coeffs" signature="QkComplex64 *qk_obs_coeffs(QkObs *obs)">
Get a pointer to the coefficients.
This can be used to read and modify the observables coefficients. The resulting pointer is valid to read for `qk_obs_num_terms(obs)` elements of `complex double`.
<span id="group__QkObs_1autotoc_md19" />
#### Example
```c
QkObs *obs = qk_obs_identity(100);
size_t num_terms = qk_obs_num_terms(obs);
complex double *coeffs = qk_obs_coeffs(obs);
for (size_t i = 0; i < num_terms; i++) {
printf("%f + i%f\n", creal(coeffs[i]), cimag(coeffs[i]));
}
```
<span id="group__QkObs_1autotoc_md20" />
#### Safety
<span id="group__QkObs_1autotoc_md20" />
Behavior is undefined `obs` is not a valid, non-null pointer to a `QkObs`.
**Parameters**
**obs** A pointer to the observable.
**Returns**
A pointer to the coefficients.
</Function>
### qk\_obs\_indices
<Function id="qk_obs_indices" signature="uint32_t *qk_obs_indices(QkObs *obs)">
Get a pointer to the indices.
This can be used to read and modify the observables indices. The resulting pointer is valid to read for `qk_obs_len(obs)` elements of size `uint32_t`.
<span id="group__QkObs_1autotoc_md21" />
#### Example
```c
uint32_t num_qubits = 100;
QkObs *obs = qk_obs_zero(num_qubits);
complex double coeff = 1;
QkBitTerm bit_terms[3] = {QkBitTerm_X, QkBitTerm_Y, QkBitTerm_Z};
uint32_t indices[3] = {0, 1, 2};
QkObsTerm term = {&coeff, 3, bit_terms, indices, num_qubits};
qk_obs_add_term(obs, &term);
size_T len = qk_obs_len(obs);
uint32_t *indices = qk_obs_indices(obs);
for (size_t i = 0; i < len; i++) {
printf("index %i: %i\n", i, indices[i]);
}
```
<span id="group__QkObs_1autotoc_md22" />
#### Safety
<span id="group__QkObs_1autotoc_md22" />
Behavior is undefined `obs` is not a valid, non-null pointer to a `QkObs`.
**Parameters**
**obs** A pointer to the observable.
**Returns**
A pointer to the indices.
</Function>
### qk\_obs\_boundaries
<Function id="qk_obs_boundaries" signature="uintptr_t *qk_obs_boundaries(QkObs *obs)">
Get a pointer to the term boundaries.
This can be used to read and modify the observables term boundaries. The resulting pointer is valid to read for `qk_obs_num_terms(obs) + 1` elements of size `size_t`.
<span id="group__QkObs_1autotoc_md23" />
#### Example
```c
uint32_t num_qubits = 100;
QkObs *obs = qk_obs_zero(num_qubits);
complex double coeff = 1;
QkBitTerm bit_terms[3] = {QkBitTerm_X, QkBitTerm_Y, QkBitTerm_Z};
uint32_t indices[3] = {0, 1, 2};
QkObsTerm term = {&coeff, 3, bit_terms, indices, num_qubits};
qk_obs_add_term(obs, &term);
size_t num_terms = qk_obs_num_terms(obs);
uint32_t *boundaries = qk_obs_boundaries(obs);
for (size_t i = 0; i < num_terms + 1; i++) {
printf("boundary %i: %i\n", i, boundaries[i]);
}
```
<span id="group__QkObs_1autotoc_md24" />
#### Safety
<span id="group__QkObs_1autotoc_md24" />
Behavior is undefined `obs` is not a valid, non-null pointer to a `QkObs`.
**Parameters**
**obs** A pointer to the observable.
**Returns**
A pointer to the boundaries.
</Function>
### qk\_obs\_bit\_terms
<Function id="qk_obs_bit_terms" signature="QkBitTerm *qk_obs_bit_terms(QkObs *obs)">
Get a pointer to the bit terms.
This can be used to read and modify the observables bit terms. The resulting pointer is valid to read for `qk_obs_len(obs)` elements of size `uint8_t`.
<span id="group__QkObs_1autotoc_md25" />
#### Example
```c
uint32_t num_qubits = 100;
QkObs *obs = qk_obs_zero(num_qubits);
complex double coeff = 1;
QkBitTerm bit_terms[3] = {QkBitTerm_X, QkBitTerm_Y, QkBitTerm_Z};
uint32_t indices[3] = {0, 1, 2};
QkObsTerm term = {&coeff, 3, bit_terms, indices, num_qubits};
qk_obs_add_term(obs, &term);
size_t len = qk_obs_len(obs);
QkBitTerm *bits = qk_obs_bit_terms(obs);
for (size_t i = 0; i < len; i++) {
printf("bit term %i: %i\n", i, bits[i]);
}
```
<span id="group__QkObs_1autotoc_md26" />
#### Safety
<span id="group__QkObs_1autotoc_md26" />
Behavior is undefined `obs` is not a valid, non-null pointer to a `QkObs`, or if invalid valus are written into the resulting `QkBitTerm` pointer.
**Parameters**
**obs** A pointer to the observable.
**Returns**
A pointer to the bit terms.
</Function>
### qk\_obs\_multiply
<Function id="qk_obs_multiply" signature="QkObs *qk_obs_multiply(const QkObs *obs, const QkComplex64 *coeff)">
Multiply the observable by a complex coefficient.
<span id="group__QkObs_1autotoc_md27" />
#### Example
```c
QkObs *obs = qk_obs_identity(100);
complex double coeff = 2;
QkObs *result = qk_obs_multiply(obs, &coeff);
```
<span id="group__QkObs_1autotoc_md28" />
#### Safety
<span id="group__QkObs_1autotoc_md28" />
Behavior is undefined if any of the following is violated
* `obs` is a valid, non-null pointer to a `QkObs`
* `coeff` is a valid, non-null pointer to a `complex double`
**Parameters**
* **obs** A pointer to the observable.
* **coeff** The coefficient to multiply the observable with.
</Function>
### qk\_obs\_add
<Function id="qk_obs_add" signature="QkObs *qk_obs_add(const QkObs *left, const QkObs *right)">
Add two observables.
<span id="group__QkObs_1autotoc_md29" />
#### Example
```c
QkObs *left = qk_obs_identity(100);
QkObs *right = qk_obs_zero(100);
QkObs *result = qk_obs_add(left, right);
```
<span id="group__QkObs_1autotoc_md30" />
#### Safety
<span id="group__QkObs_1autotoc_md30" />
Behavior is undefined if `left` or `right` are not valid, non-null pointers to `QkObs`\ s.
**Parameters**
* **left** A pointer to the left observable.
* **right** A pointer to the right observable.
**Returns**
A pointer to the result `left + right`.
</Function>
### qk\_obs\_compose
<Function id="qk_obs_compose" signature="QkObs *qk_obs_compose(const QkObs *first, const QkObs *second)">
Compose (multiply) two observables.
<span id="group__QkObs_1autotoc_md31" />
#### Example
```c
QkObs *first = qk_obs_zero(100);
QkObs *second = qk_obs_identity(100);
QkObs *result = qk_obs_compose(first, second);
```
<span id="group__QkObs_1autotoc_md32" />
#### Safety
<span id="group__QkObs_1autotoc_md32" />
Behavior is undefined if `first` or `second` are not valid, non-null pointers to `QkObs`\ s.
**Parameters**
* **first** One observable.
* **second** The other observable.
**Returns**
`first.compose(second)` which equals the observable `result = second @ first`, in terms of the matrix multiplication `@`.
</Function>
### qk\_obs\_compose\_map
<Function id="qk_obs_compose_map" signature="QkObs *qk_obs_compose_map(const QkObs *first, const QkObs *second, const uint32_t *qargs)">
Compose (multiply) two observables according to a custom qubit order.
Notably, this allows composing two observables of different size.
<span id="group__QkObs_1autotoc_md33" />
#### Example
```c
QkObs *first = qk_obs_zero(100);
QkObs *second = qk_obs_identity(100);
QkObs *result = qk_obs_compose(first, second);
```
<span id="group__QkObs_1autotoc_md34" />
#### Safety
<span id="group__QkObs_1autotoc_md34" />
To call this function safely
* `first` and `second` must be valid, non-null pointers to `QkObs`\ s
* `qargs` must point to an array of `uint32_t`, readable for `qk_obs_num_qubits(second)` elements (meaning the number of qubits in `second`)
**Parameters**
* **first** One observable.
* **second** The other observable. The number of qubits must match the length of `qargs`.
* **qargs** The qubit arguments specified which indices in `first` to associate with the ones in `second`.
**Returns**
`first.compose(second)` which equals the observable `result = second @ first`, in terms of the matrix multiplication `@`.
</Function>
### qk\_obs\_canonicalize
<Function id="qk_obs_canonicalize" signature="QkObs *qk_obs_canonicalize(const QkObs *obs, double tol)">
Calculate the canonical representation of the observable.
<span id="group__QkObs_1autotoc_md35" />
#### Example
```c
QkObs *iden = qk_obs_identity(100);
QkObs *two = qk_obs_add(iden, iden);
double tol = 1e-6;
QkObs *canonical = qk_obs_canonicalize(two);
```
<span id="group__QkObs_1autotoc_md36" />
#### Safety
<span id="group__QkObs_1autotoc_md36" />
Behavior is undefined `obs` is not a valid, non-null pointer to a `QkObs`.
**Parameters**
* **obs** A pointer to the observable.
* **tol** The tolerance below which coefficients are considered to be zero.
**Returns**
The canonical representation of the observable.
</Function>
### qk\_obs\_copy
<Function id="qk_obs_copy" signature="QkObs *qk_obs_copy(const QkObs *obs)">
Copy the observable.
<span id="group__QkObs_1autotoc_md37" />
#### Example
```c
QkObs *original = qk_obs_identity(100);
QkObs *copied = qk_obs_copy(original);
```
<span id="group__QkObs_1autotoc_md38" />
#### Safety
<span id="group__QkObs_1autotoc_md38" />
Behavior is undefined `obs` is not a valid, non-null pointer to a `QkObs`.
**Parameters**
**obs** A pointer to the observable.
**Returns**
A pointer to a copy of the observable.
</Function>
### qk\_obs\_equal
<Function id="qk_obs_equal" signature="bool qk_obs_equal(const QkObs *obs, const QkObs *other)">
Compare two observables for equality.
Note that this does not compare mathematical equality, but data equality. This means that two observables might represent the same observable but not compare as equal.
<span id="group__QkObs_1autotoc_md39" />
#### Example
```c
QkObs *observable = qk_obs_identity(100);
QkObs *other = qk_obs_identity(100);
bool are_equal = qk_obs_equal(observable, other);
```
<span id="group__QkObs_1autotoc_md40" />
#### Safety
<span id="group__QkObs_1autotoc_md40" />
Behavior is undefined if `obs` or `other` are not valid, non-null pointers to `QkObs`\ s.
**Parameters**
* **obs** A pointer to one observable.
* **other** A pointer to another observable.
**Returns**
`true` if the observables are equal, `false` otherwise.
</Function>
### qk\_obs\_str
<Function id="qk_obs_str" signature="char *qk_obs_str(const QkObs *obs)">
Return a string representation of a `QkObs`.
<span id="group__QkObs_1autotoc_md41" />
#### Example
```c
QkObs *obs = qk_obs_identity(100);
char *string = qk_obs_str(obs);
qk_str_free(string);
```
<span id="group__QkObs_1autotoc_md42" />
#### Safety
<span id="group__QkObs_1autotoc_md42" />
Behavior is undefined `obs` is not a valid, non-null pointer to a `QkObs`.
The string must not be freed with the normal C free, you must use `qk_str_free` to free the memory consumed by the String. Not calling `qk_str_free` will lead to a memory leak.
Do not change the length of the string after its returned (by writing a nul byte somewhere inside the string or removing the final one), although values can be mutated.
**Parameters**
**obs** A pointer to the `QkObs` to get the string for.
**Returns**
A pointer to a nul-terminated char array of the string representation for `obs`
</Function>
### qk\_str\_free
<Function id="qk_str_free" signature="void qk_str_free(char *string)">
Free a string representation.
<span id="group__QkObs_1autotoc_md43" />
#### Safety
<span id="group__QkObs_1autotoc_md43" />
Behavior is undefined if `str` is not a pointer returned by `qk_obs_str` or `qk_obsterm_str`.
**Parameters**
**string** A pointer to the returned string representation from `qk_obs_str` or `qk_obsterm_str`.
</Function>
### qk\_obs\_to\_python
<Function id="qk_obs_to_python" signature="PyObject *qk_obs_to_python(const QkObs *obs)">
Convert to a Python-space `SparseObservable`.
<span id="group__QkObs_1autotoc_md48" />
#### Safety
<span id="group__QkObs_1autotoc_md48" />
Behavior is undefined if `obs` is not a valid, non-null pointer to a `QkObs`.
It is assumed that the thread currently executing this function holds the Python GIL this is required to create the Python object returned by this function.
**Parameters**
**obs** The C-space `QkObs` pointer.
**Returns**
A Python object representing the `SparseObservable`.
</Function>