

Hosted by
Alice & Bob
aqora template dynamiqs-2025-2test.aqora lab -p dynamiqs-2025-2submission/solution.ipynb.aqora testaqora uploadimport dynamiqs as dq
import jax
import jax.numpy as jnp
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm
import optax
dq.plot.mplstyle(dpi=150)
tsave, target = input
# Declare useful helpers to handle units
MHz = 2 * jnp.pi
kHz = 2 * jnp.pi * 1e-3
us = 1.0
ns = 1.0e-3
# Instanciate our bosonic annihilation operator
Na = 15 # bosonic mode truncation
a = dq.destroy(Na) & dq.eye(2)
# Qubit Pauli gate operators
X = dq.eye(Na) & dq.sigmax() # X gate
Y = dq.eye(Na) & dq.sigmay() # Y gate
Z = dq.eye(Na) & dq.sigmaz() # Z gate# coupling between the memory and the qubit
chi_aq = 2.0 * MHz
tsave = np.linspace(0, 1 * us, 100)
target = dq.fock(Na, 2) & dq.basis(2, 0)
def simulate(drives, progress_meter=True):
# Redefine Hamiltonian with the new g2 value
H = dq.pwc(tsave, drives["memory"]["x"], a + a.dag()) # memory drive along x
H += dq.pwc(tsave, drives["memory"]["y"], 1j * (a - a.dag())) # memory drive along y
H += dq.pwc(tsave, drives["qubit"]["x"], X) # qubit drive, pauli X
H += dq.pwc(tsave, drives["qubit"]["y"], Y) # qubit drive, pauli Y
H += dq.constant(chi_aq * a.dag() @ a @ Z) # memory-qubit coupling
# initial state: memory in vacuum and qubit in |0>
psi0 = dq.coherent(Na, 0) & dq.basis(2, 0)
options = dq.Options(
progress_meter=
dq.progress_meter.TqdmProgressMeter() if progress_meter
else dq.progress_meter.NoProgressMeter(),
)
# Perform the master equation simulation
return dq.sesolve(H, psi0, tsave, options=options)
def cost_function(drive, do_sum=True):
final_states = simulate(drive, progress_meter=False).states[:, -1]
loss = (1 - abs(target.dag() @ final_states) ** 2)
if do_sum:
loss = loss.sum()
return lossBATCH_SIZE. Here it's set at 200, meaning that we are going to optimize on 200 candidate set of control parameters at the timeBATCH_SIZE = 200
NUM_TRAIN_STEPS = 500
MAX_AMP = 2.0 # maximum drive strength
key = jax.random.PRNGKey(42) # random number generation seed
keys = jax.random.split(key, 4)
draw = lambda key: dq.random.real(key, (BATCH_SIZE, len(tsave)-1), max=0.1)
memory_drive = dict(x=draw(keys[0]), y=draw(keys[1]))
qubit_drive = dict(x=draw(keys[2]), y=draw(keys[3]))
params = dict(memory=memory_drive, qubit=qubit_drive)
optimizer = optax.chain(
optax.adam(learning_rate=1e-2),
optax.clip(1.0)
)
opt_state = optimizer.init(params)
@jax.jit
def step(params, opt_state):
cost = cost_function(params, do_sum=False)[..., 0, 0] # log all costs
cost_grad = jax.grad(cost_function)(params) # compute costs gradient
updates, opt_state = optimizer.update(cost_grad, opt_state, params)
params = optax.apply_updates(params, updates)
return cost, params, opt_state
params_history, costs_history = [], []
for i in tqdm(range(NUM_TRAIN_STEPS), disable=False):
cost, params, opt_state = step(params, opt_state)
params_history.append(params)
costs_history.append(cost)
costs_history = np.array(costs_history)
plt.plot(costs_history.min(axis=1), label="min")
plt.plot(costs_history.mean(axis=1), label="mean")
plt.plot(costs_history.max(axis=1), label="max")
plt.legend()# this is the index of the pulse sequence that minimizes the cost
idx_best_sequence = np.argmin(costs_history[:, -1])
res = simulate(params_history[-1]).states[idx_best_sequence]
dq.plot.wigner_mosaic(res.ptrace(0), n=15, nrows=3)fig, axs = plt.subplots(2, 1)
memory_pulse = params_history[-1]["memory"]["x"][idx_best_sequence] + 1j * params_history[-1]["memory"]["y"][idx_best_sequence]
qubit_pulse = params_history[-1]["qubit"]["x"][idx_best_sequence] + 1j * params_history[-1]["qubit"]["y"][idx_best_sequence]
dq.plot.pwc_pulse(tsave, memory_pulse, ax=axs[0])
dq.plot.pwc_pulse(tsave, qubit_pulse, ax=axs[1])output = dict(
memory=dict(
x=params_history[-1]["memory"]["x"][idx_best_sequence],
y=params_history[-1]["memory"]["y"][idx_best_sequence],
),
qubit=dict(
x=params_history[-1]["qubit"]["x"][idx_best_sequence],
y=params_history[-1]["qubit"]["y"][idx_best_sequence],
),
)$ aqora upload