Getting Started¶
What is QuGrad¶
A python library for quantum optimal control using PySTE for the quantum evolution and gradient calculations and TensorFlow for backpropagating gradients through the pulse shaping functions.
Installation¶
The python package can be installed with pip as follows:
pip install qugrad
If on Linux and using a conda environment you may encounter an error
version `GLIBCXX_...' not found
to fix this you also need to execute:
conda install -c conda-forge libstdcxx-ng
Requirements¶
Requires:
Additional requirements for testing¶
Quick Start¶
A simple quantum optimal control problem on one qubit is optimising the drive frequency \(\omega\) and amplitude \(A\) such that a Rabi oscillation transfers the \(\left|0\right\rangle\) state to the \(\left|1\right\rangle\) state. To study this we will consider the Hamiltonian
where \(X\) and \(Z\) are the x and z Pauli matrices. We will evolve the system for \(T=10\) units of time.
To perform the optimisation we need an appropriate cost function. Here we use the expectation value of the \(Z\) operator as this is minimised by the \(\left|1\right\rangle\) state. The following program solves this task:
# examples/Rabi_oscillation_optimisation.py
import numpy as np
import tensorflow as tf
from scipy.optimize import minimize
from qugrad import QuantumSystem, HilbertSpace
# Constants
X = np.array([[0, 1], # Pauli-X
[1, 0]],
dtype=np.complex128)
Z = np.array([[1, 0], # Pauli-Z
[0, -1]],
dtype=np.complex128)
INITIAL_STATE = np.array([1, 0], dtype=np.complex128) # |0>
N_TIME_STEPS = 1000
T = 10
OBSERVABLE = Z
# Hamiltonian
H0 = Z
Hs = [X]
# Define quantum syste
hilbert_space = HilbertSpace([0, 1])
device = QuantumSystem(H0, Hs, hilbert_space)
# Define pulse form
def Rabi_drive(frequency, amplitude):
amplitude = tf.cast(amplitude, tf.complex128)
ctrl_amp = amplitude*tf.ones((N_TIME_STEPS, 1), dtype=tf.complex128)
initial_state = INITIAL_STATE
dt = T/N_TIME_STEPS
frequencies = tf.reshape(tf.cast(frequency, dtype=tf.complex128), (1,))
number_channels = [1]
return ctrl_amp, initial_state, dt, frequencies, number_channels
# SciPy minimization expects an array of variables so we will wrap our pulse
# form in this upack function
def unpack_variables(x):
frequency = x[0]
amplitude = x[1]
return frequency, amplitude
# Adding the pulses to the device
driven_device = device.pulse_form(Rabi_drive).pulse_form(unpack_variables)
# Initial parameters
x0 = [1, # frequency
0.1] # amplitude
# Expectation value before optimisation
initial_expectation_value = driven_device.evolved_expectation_value(x0,
OBSERVABLE)
# Optimisation
result = minimize(driven_device.gradient,
x0=x0,
args=(OBSERVABLE,),
jac=True)
# Print results
print("Initial expectation value: ", initial_expectation_value)
print("Optimisation result:")
print(result)
The output should look like:
Initial expectation value: (0.9747897138091817+0j)
Optimisation result:
message: Optimization terminated successfully.
success: True
status: 0
fun: -0.9999999999995379
x: [ 1.557e+00 1.473e+00]
nit: 12
jac: [-3.482e-07 1.729e-07]
hess_inv: [[ 7.625e-02 -3.556e-02]
[-3.556e-02 2.850e-02]]
nfev: 37
njev: 37
(You may see some output from TensorFlow too.)