View on QuantumAI | Run in Google Colab | View source on GitHub | Download notebook |

```
try:
import cirq
except ImportError:
print("installing cirq...")
!pip install --quiet cirq --pre
print("installed cirq.")
```

# Introduction

Cirq's protocols are very similar concept to Python's built-in protocols that were introduced in PEP 544.
Python's built-in protocols are extremely convenient, for example behind all the for loops and list comprehensions you can find the Iterator protocol.
As long as an object has the `__iter__()`

magic method that returns an iterator object, it has iterator support.
An iterator object has to define `__iter__()`

and `__next__()`

magic methods, that defines the iterator protocol.
The `iter(val)`

builtin function returns an iterator for `val`

if it defines the above methods, otherwise throws a `TypeError`

. Cirq protocols work similarly.

A canonical Cirq protocol example is the `unitary`

protocol that allows to check the unitary matrix of values that support the protocol by calling `cirq.unitary(val)`

.

```
import cirq
print(cirq.X)
print("cirq.X unitary:\n", cirq.unitary(cirq.X))
a, b = cirq.LineQubit.range(2)
circuit = cirq.Circuit(cirq.X(a), cirq.Y(b))
print(circuit)
print("circuit unitary:\n", cirq.unitary(circuit))
```

When an object does not support a given protocol, an error is thrown.

```
try:
print(cirq.unitary(a)) ## error!
except Exception as e:
print("As expected, a qubit does not have a unitary. The error: ")
print(e)
```

## What is a protocol?

A protocol is a combination of the following two items:

- a
`SupportsXYZ`

class, which defines and documents all the magic functions that need to be implemented in order to support that given protocol - the entrypoint function(s), which are exposed to the main cirq namespace as
`cirq.xyz()`

## Cirq's protocols

For a complete list of Cirq protocols, refer to the `cirq.protocols`

package.
Here we provide a list of frequently used protocols for debugging, simulation and testing.

Protocol | Description |
---|---|

`cirq.apply_channel` |
High performance evolution under a channel evolution. |

`cirq.apply_mixture` |
High performance evolution under a mixture of unitaries evolution. |

`cirq.apply_unitaries` |
Apply a series of unitaries onto a state tensor. |

`cirq.apply_unitary` |
High performance left-multiplication of a unitary effect onto a tensor. |

`cirq.approx_eq` |
Approximately compares two objects. |

`cirq.commutes` |
Determines whether two values commute. |

`cirq.definitely_commutes` |
Determines whether two values definitely commute. |

`cirq.decompose` |
Recursively decomposes a value into `cirq.Operation` s meeting a criteria. |

`cirq.decompose_once` |
Decomposes a value into operations, if possible. |

`cirq.decompose_once_with_qubits` |
Decomposes a value into operations on the given qubits. |

`cirq.equal_up_to_global_phase` |
Determine whether two objects are equal up to global phase. |

`cirq.has_kraus` |
Returns whether the value has a Kraus representation. |

`cirq.has_mixture` |
Returns whether the value has a mixture representation. |

`cirq.has_unitary` |
Determines whether the value has a unitary effect. |

`cirq.inverse` |
Returns the inverse `val**-1` of the given value, if defined. |

`cirq.is_measurement` |
Determines whether or not the given value is a measurement. |

`cirq.is_parameterized` |
Returns whether the object is parameterized with any Symbols. |

`cirq.kraus` |
Returns a Kraus representation of the given channel. |

`cirq.measurement_key` |
Get the single measurement key for the given value. |

`cirq.measurement_keys` |
Gets the measurement keys of measurements within the given value. |

`cirq.mixture` |
Return a sequence of tuples representing a probabilistic unitary. |

`cirq.num_qubits` |
Returns the number of qubits, qudits, or qids `val` operates on. |

`cirq.parameter_names` |
Returns parameter names for this object. |

`cirq.parameter_symbols` |
Returns parameter symbols for this object. |

`cirq.pauli_expansion` |
Returns coefficients of the expansion of val in the Pauli basis. |

`cirq.phase_by` |
Returns a phased version of the effect. |

`cirq.pow` |
Returns `val**factor` of the given value, if defined. |

`cirq.qasm` |
Returns QASM code for the given value, if possible. |

`cirq.qid_shape` |
Returns a tuple describing the number of quantum levels of each |

`cirq.quil` |
Returns the QUIL code for the given value. |

`cirq.read_json` |
Read a JSON file that optionally contains cirq objects. |

`cirq.resolve_parameters` |
Resolves symbol parameters in the effect using the param resolver. |

`cirq.to_json` |
Write a JSON file containing a representation of obj. |

`cirq.trace_distance_bound` |
Returns a maximum on the trace distance between this effect's input |

`cirq.trace_distance_from_angle_list` |
Given a list of arguments of the eigenvalues of a unitary matrix, |

`cirq.unitary` |
Returns a unitary matrix describing the given value. |

`cirq.validate_mixture` |
Validates that the mixture's tuple are valid probabilities. |

### Quantum operator representation protocols

The following family of protocols is an important and frequently used set of features of Cirq and it is worthwhile mentioning them and and how they interact with each other. They are, in the order of increasing generality:

`*unitary`

`*kraus`

`*mixture`

All these protocols make it easier to work with different representations of quantum operators, namely:

- finding that representation (
`unitary`

,`kraus`

,`mixture`

), - determining whether the operator has that representation (
`has_*`

) - and applying them (
`apply_*`

) on a state vector.

#### Unitary

The `*unitary`

protocol is the least generic, as only unitary operators should implement it. The `cirq.unitary`

function returns the matrix representation of the operator in the computational basis. We saw an example of the unitary protocol above, but let's see the unitary matrix of the Pauli-Y operator as well:

```
print(cirq.unitary(cirq.Y))
```

[[0.+0.j 0.-1.j] [0.+1.j 0.+0.j]]

#### Mixture

The `*mixture`

protocol should be implemented by operators that are *unitary-mixtures*. These probabilistic operators are represented by a list of tuples ($p_i$, $U_i$), where each unitary effect $U_i$ occurs with a certain probability $p_i$, and $\sum p_i = 1$. Probabilities are a Python float between 0.0 and 1.0, and the unitary matrices are numpy arrays.

Constructing simple probabilistic gates in Cirq is easiest with the `with_probability`

method.

```
probabilistic_x = cirq.X.with_probability(.3)
for p, op in cirq.mixture(probabilistic_x):
print(f"probability: {p}")
print("operator:")
print(op)
```

probability: 0.3 operator: [[0.+0.j 1.+0.j] [1.+0.j 0.+0.j]] probability: 0.7 operator: [[1. 0.] [0. 1.]]

In case an operator does not implement `SupportsMixture`

, but does implement `SupportsUnitary`

, `*mixture`

functions fall back to the `*unitary`

methods. It is easy to see that a unitary operator $U$ is just a "mixture" of a single unitary with probability $p=1$.

```
# cirq.Y has a unitary effect but does not implement SupportsMixture
# thus mixture protocols will return ((1, cirq.unitary(Y)))
print(cirq.mixture(cirq.Y))
print(cirq.has_mixture(cirq.Y))
```

((1.0, array([[0.+0.j, 0.-1.j], [0.+1.j, 0.+0.j]])),) True

#### Channel

The `kraus`

representation is the operator sum representation of a quantum operator (a channel):

These matrices are required to satisfy the trace preserving condition

where $I$ is the identity matrix. The matrices $A_k$ are sometimes called Kraus or noise operators.

The `cirq.kraus`

returns a tuple of numpy arrays, one for each of the Kraus operators:

```
cirq.kraus(cirq.DepolarizingChannel(p=0.3))
```

(array([[0.83666003, 0. ], [0. , 0.83666003]]), array([[0. +0.j, 0.31622777+0.j], [0.31622777+0.j, 0. +0.j]]), array([[0.+0.j , 0.-0.31622777j], [0.+0.31622777j, 0.+0.j ]]), array([[ 0.31622777+0.j, 0. +0.j], [ 0. +0.j, -0.31622777+0.j]]))

In case the operator does not implement `SupportsChannel`

, but it does implement `SupportsMixture`

, the `*kraus`

protocol will generate the Kraus operators based on the `*mixture`

representation.

Thus for example `((0.25, X), (0.75, I)) -> (0.5 X, sqrt(0.75) I)`

:

```
cirq.kraus(cirq.X.with_probability(0.25))
```

(array([[0. +0.j, 0.5+0.j], [0.5+0.j, 0. +0.j]]), array([[0.8660254, 0. ], [0. , 0.8660254]]))

In the simplest case of a unitary operator, `cirq.kraus`

returns a one-element tuple with the same unitary as returned by `cirq.unitary`

:

```
print(cirq.kraus(cirq.Y))
print(cirq.unitary(cirq.Y))
print(cirq.has_kraus(cirq.Y))
```

(array([[0.+0.j, 0.-1.j], [0.+1.j, 0.+0.j]]),) [[0.+0.j 0.-1.j] [0.+1.j 0.+0.j]] True