# How to visualize data with the Q-CTRL Visualizer package

**Display quantum controls and dynamics**

The Q-CTRL Visualizer package offers a range of functions and options to help you implement and understand quantum control and dynamics. The functions are easily applied to outputs from the Boulder Opal tools for quantum control, and support the visualization of various use-cases:

- control pulses,
- control robustness,
- quantum dynamics, and
- implementing Q-CTRL styling in Matplotlib.

## Visualization overview

Here we present the different visualization functions and options that you can use to display different characteristics of your quantum system.

### Control pulses

The Q-CTRL Visualizer package offers tools to visualize controls of different forms.

The `plot_controls`

function is a flexible way to display controls, and it is routinely used in our documentation to display optimized control outputs. The required inputs are the `matplotlib`

Figure which will contain the plots, often specified as `figure=plt.figure()`

, and the dictionary of `controls`

to be plotted. Dictionary keys should be the names of the controls, where each control contains the pulse segment `durations`

and `values`

as specified in the reference documentation. A number of convenience options can help tailor the display to particular use-cases:

- use the
`polar`

option to specify whether to display the controls in polar coordinates, - use the
`smooth`

option to display linear interpolation between pulse samples rather than piecewise-constant segments, - select your desired
`unit_symbol`

string to replace the`Hz`

default, and - specify whether control values should be divided by a
`two_pi_factor`

in the plots.

The `plot_smooth_controls`

function is similar to `plot_controls`

, but displays controls given in terms of pulse sample `times`

(in units of seconds), and `values`

. As above, the `polar`

, `unit_symbol`

(to specify the units for the control values), and `two_pi_factor`

options can tailor the control display.

For dynamical decoupling sequences, such as those available from Q-CTRL Open Controls, `plot_sequences`

can be used to display the controls. As for the other control plots, the `figure`

(matplotlib Figure) must be provided, along with the `seq`

dictionary of controls. This dictionary is similar to those for `plot_control`

, but takes `offset`

instead of `duration`

, and `rotation`

instead of `value`

, as described in the reference documentation.

### Control robustness

Filter functions are useful tools to quantify the control robustness to given noise sources. To calculate filter functions, refer to our user guide: How to calculate and use filter functions for arbitrary controls. Once calculated, you can visualize the filter functions with `plot_filter_functions`

from the Q-CTRL Visualizer. The `figure`

(matplotlib Figure to contain the plots) should be provided, along with the `filter_functions`

dictionary. Here the keys are the names of the filter functions, and the values are the list of samples for the given filter function. Each sample is a dictionary with the keys:

- the
`frequency`

(in Hz) at which the sample was taken, - the
`inverse_power`

(in seconds) for the filter function value at the sample, and - the (optional)
`inverse_power_uncertainty`

(in seconds).

### Quantum dynamics

The Q-CTRL Visualizer also provides functions to visualize the state evolution, which helps understand the system dynamics. These Bloch sphere visualizers require IPython and must be run from a Jupyter notebook.

For a single qubit, there are several options. The `display_bloch_sphere`

function takes a `[T,2]`

-shaped array of pure states and shows the state trajectory on a Bloch sphere over the `T`

trajectory steps. Similarly, the function `display_bloch_sphere_from_density_matrices`

takes the `[T,2,2]`

-shaped array of density matrices to show the state trajectory for pure or mixed states, such that the trajectory points lie on the surface of the Bloch sphere or inside it, respectively. The Bloch sphere trajectory can also be constructed using the `[T,3]`

-shaped array of Cartesian Bloch-sphere coordinates with `display_bloch_sphere_from_bloch_vectors`

, which can represent both mixed and pure states. Options for the Bloch sphere display are available in the reference documentation.

For two qubits, the `display_bloch_sphere_and_correlations`

function provides a visualization of Bloch spheres for each qubit as well as three tetrahedra to represent the correlations between qubit observables, with axes representing covariances $V(A\otimes B)$ for Pauli operators $A,B \in \{\sigma_X, \sigma_Y, \sigma_Z\}$. Additionally, there is a bar displaying the concurrence to quantify entanglement. The required input is a `[T,4]`

-shaped array representing the `T`

trajectory steps of the `4`

-dimensional pure state.

### Implementing Q-CTRL styling in Matplotlib

It is possible to apply Q-CTRL styling across plots generated using `matplotlib`

, by implementing the `get_qctrl_style`

function as follows:

```
import matplotlib.pyplot as plt
from qctrlvisualizer import get_qctrl_style
plt.style.use(get_qctrl_style())
```

This produces plot styles that are consistent with our inbuilt plotting functions, as shown in our documentation such as the application note 'Demonstrating SU(3) gates on superconducting hardware'.

## Worked example: Visualizing two-qubit optimization results and dynamics

To demonstrate the usage of the visualization functions, we consider a two-qubit system described by the following Hamiltonian:

$$ H = \chi a^\dagger a b^\dagger b + \left(\gamma_T (t) b + H.c.\right) + \left(\gamma_C (t) a + H.c.\right),$$which can represent two-level treatment of a transmon system coupled to a cavity in the dispersive regime, as explored in the application note 'Performing optimal Fock state generation in superconducting resonators'. Here $\chi$ is the dispersive shift, $a$ and $b$ are the annihilation operators of the cavity and transmon systems, respectively, and $\gamma_{T(C)}(t)$ is the complex drive amplitude applied to the transmon (cavity) qubit.

We consider the case that smooth controls have been optimized (as in the application note) to perform a target two-qubit `CX`

operation. This optimization was performed both with and without robustness to fluctuations in the amplitude of the transmon control pulse. Note that the optimization procedure and robust optimization are both introduced in our user guides.

In the following cell we import the relevant packages and functions.

```
import jsonpickle
import matplotlib.pyplot as plt
import numpy as np
from attr import asdict
from qctrlvisualizer import (
display_bloch_spheres_and_correlations,
get_qctrl_style,
plot_controls,
plot_filter_functions,
)
plt.style.use(get_qctrl_style())
# Predefined pulse imports
import qctrlopencontrols
from qctrl import Qctrl
# Starting a session with the API
qctrl = Qctrl()
def load_var(file_name):
# Return a variable from a json file
file_path = "./resources/visualize-data/"
f = open(file_path + file_name, "r+")
encoded = f.read()
decoded = jsonpickle.decode(encoded)
f.close()
return decoded
```

We next import the result of `qctrl.functions.calculate_optimization`

, where we specified the drive signal names (\$\gamma_T\\$, \$\gamma_C\\$") among the `output_node_names`

arguments. We can now plot these control pulses directly, using the `plot_controls`

function. As an example, we plot the robust control on the transmon qubit below.

```
robust_result = load_var("robust_control_result")
controls = {
"$\gamma_T$": robust_result.output["$\gamma_T$"],
}
plot_controls(plt.figure(), controls, polar=True)
```

For a smooth representation of the `I`

and `Q`

transmon control pulse components, we adjust the plotting options as shown below.

```
plot_controls(plt.figure(), controls, polar=False, smooth=True)
```

We next visualize the robustness of the controls to noise in the transmon drive amplitude. Robustness can be quantified using filter functions, as demonstrated in the user guide 'How to calculate and use filter functions for arbitrary controls'. In the cell below, we import results from the `qctrl.functions.calculate_filter_function`

function applied to the optimized and robust controls, with respect to multiplicative amplitude fluctuation noise on the transmon drive. We then display the filter functions using `plot_filter_functions`

from the Q-CTRL Visualizer.

```
optimized_ff = load_var("optimized_filter_function")
robust_ff = load_var("robust_filter_function")
plot_filter_functions(
plt.figure(),
{
"Robust": map(asdict, robust_ff.samples),
"Optimized": map(asdict, optimized_ff.samples),
},
)
```

We can also display the quantum dynamics induced by the controls. In the cell below, we import the result of system simulation that followed the procedure in the user guide 'How to simulate quantum dynamics for noiseless systems using graphs'. We simulated the system dynamics with the optimized control pulses, and obtained the unitary evolution operators for 101 `sample_times`

using `qctrl.operations.time_evolution_operators_pwc`

. These unitaries acted on the initial state $H |0\rangle \otimes |0\rangle$. As in the user guide, the state at each of the sample times was returned by the `qctrl.functions.calculate_graph`

function by referencing the states in the `output_node_names`

argument.

To display the Bloch sphere dynamics of the system evolution, we first reshape the state data to the required `[T, 4]`

dimensions. We then use the function `display_bloch_sphere_and_correlations`

to display the dynamics. Note that the concurrence entanglement measure (the central bar) displays maximal entanglement after the operation; the ideal target `CX`

operator indeed produces a two-qubit GHZ state when acted on the given initial state.

```
optimized_states = load_var("optimized_state").output["states"]["value"].squeeze()
display_bloch_spheres_and_correlations(optimized_states)
```

Next we display the state population dynamics to demonstrate the effect of the `get_qctrl_style`

function on standard `Matplotlib`

usage. Here the $|i,j\rangle$ state refers to the $i$ transmon qubit state and the $j$ cavity qubit state.

```
sample_times = np.linspace(0, 550e-9, len(optimized_states))
populations = np.abs(optimized_states.T) ** 2
fig, ax = plt.subplots()
ax.plot(sample_times, populations[0], label=r"$|0,0\rangle$")
ax.plot(sample_times, populations[1], label=r"$|0,1\rangle$")
ax.plot(sample_times, populations[2], label=r"$|1,0\rangle$")
ax.plot(sample_times, populations[3], label=r"$|1,1\rangle$")
ax.set_xlabel("Time (ns)")
ax.set_ylabel("Population")
plt.legend(title="State")
plt.show()
```