Skip to content

Commit f02132e

Browse files
author
Patrick Hopf
committed
add goat, jopt tutorial
1 parent d512ec2 commit f02132e

File tree

1 file changed

+267
-0
lines changed

1 file changed

+267
-0
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
---
2+
jupyter:
3+
jupytext:
4+
text_representation:
5+
extension: .md
6+
format_name: markdown
7+
format_version: '1.3'
8+
jupytext_version: 1.16.4
9+
kernelspec:
10+
display_name: Python 3
11+
name: python3
12+
---
13+
14+
```python
15+
import jax
16+
import numpy as np
17+
import qutip as qt
18+
import qutip_qoc as qoc
19+
from matplotlib import pyplot as plt
20+
```
21+
22+
# QuTiP - Quantum Optimal Control - GOAT and JOPT
23+
24+
25+
This tutorial notebook will guide you through the implementation of a **Hadamard** gate using a python defined control (pulse) functions.
26+
27+
We will optimize the control parameters using the **GOAT** and **JOPT** methods available in `qutip-qoc`.
28+
29+
30+
31+
The first step is to set up our quantum system in familiar QuTiP fashion.
32+
33+
Find out how you can define your system using QuTiP in our [example notebooks](https://qutip.org/qutip-tutorials/).
34+
35+
```python
36+
# Define quantum system
37+
σx, σy, σz = qt.sigmax(), qt.sigmay(), qt.sigmaz()
38+
39+
# Energy splitting, tunnelling, amplitude damping
40+
ω, Δ, γ, π = 0.1, 1.0, 0.1, np.pi
41+
42+
# Time independent drift Hamiltonian
43+
H_d = 1 / 2 ** σz + Δ * σx)
44+
45+
# And bake into Liouvillian
46+
L = qt.liouvillian(H=H_d, c_ops=[np.sqrt(γ) * qt.sigmam()])
47+
```
48+
49+
In addition to the system at hand, we need to specify the control Hamiltonian and the pulse function to be optimized.
50+
51+
```python
52+
def pulse(t, α):
53+
"""Parameterized pulse function."""
54+
return α[0] * np.sin(α[1] * t + α[2])
55+
56+
57+
def pulse_grad(t, α, i):
58+
"""Derivative with respect to α and t (only required for GOAT)."""
59+
if i == 0:
60+
return np.sin(α[1] * t + α[2])
61+
elif i == 1:
62+
return α[0] * t * np.cos(α[1] * t + α[2])
63+
elif i == 2:
64+
return α[0] * np.cos(α[1] * t + α[2])
65+
elif i == 3:
66+
return α[0] * α[1] * np.cos(α[1] * t)
67+
```
68+
69+
In the same way, we would construct a `quip.QuobjEvo`, we merge the constant Louivillian with our time dependent control Hamiltonians and their parameterized pulse functions.
70+
71+
```python
72+
# Define the control Hamiltonian
73+
H_c = [qt.liouvillian(H) for H in [σx, σy]]
74+
75+
# And merge it together with the associated pulse functions.
76+
H = [
77+
L, # Note the additional dictionary specifying our gradient function.
78+
[H_c[0], lambda t, p: pulse(t, p), {"grad": pulse_grad}],
79+
[H_c[1], lambda t, q: pulse(t, q), {"grad": pulse_grad}],
80+
]
81+
82+
# Define the optimization goal
83+
initial = qt.qeye(2) # Identity
84+
target = qt.gates.hadamard_transform()
85+
86+
# Super-operator form for open systems
87+
initial = qt.sprepost(initial, initial.dag())
88+
target = qt.sprepost(target, target.dag())
89+
```
90+
91+
When defining initial and target state make sure the dimensions match.
92+
93+
Finally we specify the time interval in which we want to optimize the pulses and the overall objective.
94+
95+
```python
96+
objective = qoc.Objective(initial, H, target)
97+
tlist = np.linspace(0, 2 * π, 100)
98+
```
99+
100+
We can now run the local optimization using the **GOAT** algorithm. The global optimizer can be enabled by an additional keyword.
101+
102+
```python
103+
res_goat = qoc.optimize_pulses(
104+
objectives=objective,
105+
control_parameters={
106+
"p": {
107+
"guess": [1.0, 1.0, 1.0],
108+
"bounds": [(-1, 1), (0, 1), (0, 2 * π)],
109+
},
110+
"q": {
111+
"guess": [1.0, 1.0, 1.0],
112+
"bounds": [(-1, 1), (0, 1), (0, 2 * π)],
113+
},
114+
},
115+
tlist=tlist,
116+
algorithm_kwargs={
117+
"fid_err_targ": 0.01,
118+
"alg": "GOAT",
119+
},
120+
# uncomment for global optimization
121+
# optimizer_kwargs={"max_iter": 10}
122+
)
123+
```
124+
125+
```python
126+
res_goat
127+
```
128+
129+
Unfortunately the desired target fidelity could not yet be achieved within the given constraints.
130+
131+
To compare the result with the target visually we can take a quick look at the hinton plot.
132+
133+
```python
134+
fig, (ax0, ax1, ax2) = plt.subplots(1, 3, figsize=(15, 5))
135+
ax0.set_title("Initial")
136+
ax1.set_title("Final")
137+
ax2.set_title("Target")
138+
139+
qt.hinton(initial, ax=ax0)
140+
qt.hinton(res_goat.final_states[0], ax=ax1)
141+
qt.hinton(target, ax=ax2)
142+
```
143+
144+
If we don't have the derivative of our pulse function available, we can use JAX defined pulse functions instead and optimize them with the **JOPT** algorithm.
145+
146+
```python
147+
def sin_jax(t, α):
148+
return α[0] * jax.numpy.sin(α[1] * t + α[2])
149+
```
150+
151+
To use the function with `qutip-jax` we need to jit compile them.
152+
153+
```python
154+
@jax.jit
155+
def sin_x_jax(t, p, **kwargs):
156+
return sin_jax(t, p)
157+
158+
159+
@jax.jit
160+
def sin_y_jax(t, q, **kwargs):
161+
return sin_jax(t, q)
162+
```
163+
164+
Since JAX comes with automatic differentiation, we can drop the `"grad"` dictionary and functions.
165+
166+
```python
167+
H_jax = [L, [H_c[0], sin_x_jax], [H_c[1], sin_y_jax]]
168+
```
169+
170+
We simply have to change the ``algorithm_kwargs`` to run the **JOPT** algorithm.
171+
172+
```python
173+
res_jopt = qoc.optimize_pulses(
174+
objectives=qoc.Objective(initial, H_jax, target),
175+
tlist=tlist,
176+
control_parameters={
177+
"p": {
178+
"guess": [1.0, 1.0, 1.0],
179+
"bounds": [(-1, 1), (0, 1), (0, 2 * π)],
180+
},
181+
"q": {
182+
"guess": [1.0, 1.0, 1.0],
183+
"bounds": [(-1, 1), (0, 1), (0, 2 * π)],
184+
},
185+
},
186+
algorithm_kwargs={
187+
"alg": "JOPT", # Use the JAX optimizer
188+
"fid_err_targ": 0.01,
189+
},
190+
)
191+
```
192+
193+
We end up with the same result.
194+
195+
```python
196+
res_jopt
197+
```
198+
199+
# Multi-objective and time parameter
200+
201+
202+
Both algorithms provide the option for multiple objectives, e.g. to account for variations in the Hamiltonian.
203+
204+
```python
205+
H_low = [0.95 * L, [0.95 * H_c[0], sin_x_jax], [0.95 * H_c[1], sin_y_jax]]
206+
207+
H_high = [1.05 * L, [1.05 * H_c[0], sin_x_jax], [1.05 * H_c[1], sin_y_jax]]
208+
```
209+
210+
```python
211+
objectives = [
212+
qoc.Objective(initial, H_low, target, weight=0.25),
213+
qoc.Objective(initial, H_jax, target, weight=0.50),
214+
qoc.Objective(initial, H_high, target, weight=0.25),
215+
]
216+
```
217+
218+
Additionally we can loosen the fixed time constraint to reach the desired target fidelity.
219+
220+
```python
221+
# same control parameters as before
222+
control_parameters = {
223+
k: {
224+
"guess": [1.0, 1.0, 1.0],
225+
"bounds": [(-1, 1), (0, 1), (0, 2 * π)],
226+
}
227+
for k in ["ctrl_x", "ctrl_y"]
228+
}
229+
230+
# add magic time parameter
231+
control_parameters["__time__"] = {
232+
"guess": tlist[len(tlist) // 3],
233+
"bounds": [tlist[0], tlist[-1]],
234+
}
235+
```
236+
237+
Again we run the optimization with the **JOPT** algorithm.
238+
239+
```python
240+
# run optimization
241+
result = qoc.optimize_pulses(
242+
objectives,
243+
control_parameters,
244+
tlist,
245+
algorithm_kwargs={
246+
"alg": "JOPT",
247+
"fid_err_targ": 0.01,
248+
},
249+
)
250+
```
251+
252+
Finaly we manage to achieve the desired target fidelity by loosening the time constraint.
253+
254+
```python
255+
result
256+
```
257+
258+
```python
259+
fig, (ax0, ax1, ax2) = plt.subplots(1, 3, figsize=(15, 5))
260+
ax0.set_title("Initial")
261+
ax1.set_title("Final")
262+
ax2.set_title("Target")
263+
264+
qt.hinton(initial, ax=ax0)
265+
qt.hinton(result.final_states[0], ax=ax1)
266+
qt.hinton(target, ax=ax2)
267+
```

0 commit comments

Comments
 (0)