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

\[ H(t)=Z+A\cos(\omega t)X, \]

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.)


Previous | Next