This notebook shortly explains how to use Quandary through the python interface.¶
Latest update: 11/14/2023, Stefanie Guenther (guenther5@llnl.gov)
Content:
- How to optimize for control pulses that realize a unitary gate
- How to simulate the fidelity of (optimized or prescribed) control pulses
- How to evaluate pulses on a specific sample rate
- How to get the propagator for given control parameters from Quandary?
- How to compare results to QuTIP?
- How to optimize for control pulses that realize a state to state transfer
- How to use the open-system solver to model decay and dephasing
- FAQs ("What if...")
In [1]:
Copied!
# Quandary's python interface functions are defined in /path/to/quandary/quandary.py. Import them here.
from quandary import *
%matplotlib inline
# Quandary's python interface functions are defined in /path/to/quandary/quandary.py. Import them here.
from quandary import *
%matplotlib inline
1. How to optimize for control pulses that realize a unitary gate¶
In [2]:
Copied!
#
# THE SHORT STORY...
#
# Example usage of Quandary to optimize for pulses that realize a SWAP 02 gate to swap the 0-th and 2nd qubit state of a 3-level qudit.
# Define the number modelled energy levels per qubit. Here: One qudit modelled with 3 essential levels and one extra guard level
Ne = [3] # essential levels per qubit (dimension of the target gate)
Ng = [1] # guard levels per qubit (add a guard level to prevent leakage out of the computational subspace)
# Define the qubit frequencies [GHz]
freq01 = [4.10595] # 0-1 transition frequency per qubit [GHz]
selfkerr = [0.2198] # anharmonicity per qubit [GHz]
# For multiple qubits, define the qubit-qubit coupling strength [GHz]
Jkl = [0.0] # Dipole-dipole coupling strength. Format: [0<->1, 0<->2, ..., 1<->2, 1<->3, ...]
# Define the target gate. Here SWAP 02 gate to swap the 0th and 2nd energy level occupation
unitary = [[0,0,1],[0,1,0],[1,0,0]] # Target unitary gate, here SWAP02
# Define the pulse duration
pulse_length = 200.0 # unit [ns]
# Create a Quandary object, passing the above options. Other options will be using the default values. Check 'help(Quandary)' to see all options and their defaults.
quandary = Quandary(Ne=Ne, Ng=Ng, freq01=freq01, selfkerr=selfkerr, Jkl=Jkl, targetgate=unitary, T=pulse_length, rand_seed=34525)
# Optionally: Bound the maximum control pulse amplitude per qubit, or comment out.
quandary.maxctrl_MHz = [5.0] # [MHz]
# Optimize with Quandary. Check help(quandary.optimize) for optional arguments. This runs Quandary in the background on <prod(Ne)> cores.
t, p, q, infidelity, expectedEnergy, population = quandary.optimize()
print(f"\nOptimized Fidelity = {1.0 - infidelity}")
# Store the optimized Bspline parameters
popt = quandary.popt[:]
# For single-qubit systems: Plot all results in one window
plot_results_1osc(quandary, p[0], q[0], expectedEnergy[0], population[0])
# Other plotting functions:
# plot_pulse(Ne, t, p, q) # Plot only the pulses for each qubit
# plot_expectedEnergy(Ne, time, expectedEnergy) # Plot the trajectory of the expected energy level over time
# plot_population(Ne, time, population) # Plot the trajecotry of the qubit's level occupations over time
#
# THE SHORT STORY...
#
# Example usage of Quandary to optimize for pulses that realize a SWAP 02 gate to swap the 0-th and 2nd qubit state of a 3-level qudit.
# Define the number modelled energy levels per qubit. Here: One qudit modelled with 3 essential levels and one extra guard level
Ne = [3] # essential levels per qubit (dimension of the target gate)
Ng = [1] # guard levels per qubit (add a guard level to prevent leakage out of the computational subspace)
# Define the qubit frequencies [GHz]
freq01 = [4.10595] # 0-1 transition frequency per qubit [GHz]
selfkerr = [0.2198] # anharmonicity per qubit [GHz]
# For multiple qubits, define the qubit-qubit coupling strength [GHz]
Jkl = [0.0] # Dipole-dipole coupling strength. Format: [0<->1, 0<->2, ..., 1<->2, 1<->3, ...]
# Define the target gate. Here SWAP 02 gate to swap the 0th and 2nd energy level occupation
unitary = [[0,0,1],[0,1,0],[1,0,0]] # Target unitary gate, here SWAP02
# Define the pulse duration
pulse_length = 200.0 # unit [ns]
# Create a Quandary object, passing the above options. Other options will be using the default values. Check 'help(Quandary)' to see all options and their defaults.
quandary = Quandary(Ne=Ne, Ng=Ng, freq01=freq01, selfkerr=selfkerr, Jkl=Jkl, targetgate=unitary, T=pulse_length, rand_seed=34525)
# Optionally: Bound the maximum control pulse amplitude per qubit, or comment out.
quandary.maxctrl_MHz = [5.0] # [MHz]
# Optimize with Quandary. Check help(quandary.optimize) for optional arguments. This runs Quandary in the background on cores.
t, p, q, infidelity, expectedEnergy, population = quandary.optimize()
print(f"\nOptimized Fidelity = {1.0 - infidelity}")
# Store the optimized Bspline parameters
popt = quandary.popt[:]
# For single-qubit systems: Plot all results in one window
plot_results_1osc(quandary, p[0], q[0], expectedEnergy[0], population[0])
# Other plotting functions:
# plot_pulse(Ne, t, p, q) # Plot only the pulses for each qubit
# plot_expectedEnergy(Ne, time, expectedEnergy) # Plot the trajectory of the expected energy level over time
# plot_population(Ne, time, population) # Plot the trajecotry of the qubit's level occupations over time
Executing ' mpirun -np 3 quandary ./config.cfg --quiet . Runtype: optimization ... Objective Tikhonov Penalty-Leakage Penalty-StateVar Penalty-TotalEnergy Penalty-CtrlVar 0 9.01413770665704e-01 + 1.14880994107351e-06 + 4.92008024113254e-05 + 1.59677761749278e-06 + 1.82940089620197e-05 + 0.00000000000000e+00 Fidelity = 9.85862293342961e-02 ||Grad|| = 3.02938565436338e-01 1 4.46562869956035e-01 + 1.09663002122803e-06 + 1.53556555689983e-05 + 2.13023995821287e-06 + 2.83396267611865e-05 + 0.00000000000000e+00 Fidelity = 5.53437130043965e-01 ||Grad|| = 4.82739122708045e-01 2 1.34733999012784e-01 + 9.04952218860180e-07 + 1.56399651273920e-05 + 1.83369739152377e-06 + 2.13288288193727e-05 + 0.00000000000000e+00 Fidelity = 8.65266000987216e-01 ||Grad|| = 3.11873310564153e-01 3 4.47783395179877e-02 + 8.83120435314260e-07 + 1.35757010669867e-05 + 1.90737519871953e-06 + 2.10929666358654e-05 + 0.00000000000000e+00 Fidelity = 9.55221660482012e-01 ||Grad|| = 2.27565685766170e-01 4 1.18836390922550e-02 + 9.14034082633325e-07 + 1.07358166116044e-05 + 2.21578298707228e-06 + 2.31314049531738e-05 + 0.00000000000000e+00 Fidelity = 9.88116360907745e-01 ||Grad|| = 4.58657743743450e-01 5 1.44567734647949e-03 + 8.90377074216899e-07 + 1.14445650171382e-05 + 2.10558351083526e-06 + 2.20882568335783e-05 + 0.00000000000000e+00 Fidelity = 9.98554322653521e-01 ||Grad|| = 1.01861480379346e-01 6 1.17057339951243e-03 + 8.94917397615672e-07 + 1.13703917506050e-05 + 2.11539215137327e-06 + 2.22774947497935e-05 + 0.00000000000000e+00 Fidelity = 9.98829426600488e-01 ||Grad|| = 1.62871521370498e-01 7 1.12965466780646e-03 + 8.94758602473547e-07 + 1.13530018306847e-05 + 2.11732243490197e-06 + 2.22769114421360e-05 + 0.00000000000000e+00 Fidelity = 9.98870345332194e-01 ||Grad|| = 1.38452432779755e-01 8 1.04844710027463e-03 + 8.94544615373917e-07 + 1.12656018823656e-05 + 2.11989554910067e-06 + 2.22876484428447e-05 + 0.00000000000000e+00 Fidelity = 9.98951552899725e-01 ||Grad|| = 1.49305736125562e-01 9 9.65307584618991e-04 + 8.93356380816134e-07 + 1.11501089593342e-05 + 2.11627417885002e-06 + 2.22705239530524e-05 + 0.00000000000000e+00 Fidelity = 9.99034692415381e-01 ||Grad|| = 1.50332155154520e-01 10 6.54982143776128e-04 + 8.82025563842241e-07 + 1.03685280470029e-05 + 2.08383385670831e-06 + 2.21227792283338e-05 + 0.00000000000000e+00 Fidelity = 9.99345017856224e-01 ||Grad|| = 1.41827351605887e-01 11 3.71150273654419e-04 + 8.82731782479011e-07 + 1.02900268755868e-05 + 2.07978513458481e-06 + 2.21905290963260e-05 + 0.00000000000000e+00 Fidelity = 9.99628849726346e-01 ||Grad|| = 1.52752804379683e-01 12 8.38198386370337e-05 + 8.77778602374538e-07 + 1.00894094127874e-05 + 2.05589471747391e-06 + 2.22162623344718e-05 + 0.00000000000000e+00 Fidelity = 9.99916180161363e-01 ||Grad|| = 8.13613183603897e-02 Optimization converged with small final time cost. Optimized Fidelity = 0.999916180161363
Plotting results... -> Press <enter> to proceed.
//Users/guenther5/Numerics/quandary/quandary.py:1374: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown plt.waitforbuttonpress(1);
In [4]:
Copied!
#
# THE LONG STORY...
#
# Quandary uses B-spline basis functions with carrier waves to parameterize control pulses p(t) and q(t). It solves Schroedinger's equation (or Lindblad's master equation, see below) for each initial state, and compares the solution operator to the target unitary gate. Gradient-based optimization with box-constraints is used to design pulses that realize the target gate.
# Useful optional arguments. See help(Quandary) for a full list of all options and their default values
# control_enforce_BC (Bool) : Switch whether the control pulses should start and end at zero (p(t)=0 at t=0 and t=T). Default 'False'.
# tol_infidelity (Float) : Targeted accuraty of the pulses in terms of the fidelity. Default '1e-4' gives a 99.99% fidelity gate).
# maxiter (Int) : Maximum number of optimization iterations (default 100).
# initctrl_MHz (Float) : Amplitude of the initial guess for the pulses. Default: 1 MHz
# randomize_init_ctrl (Bool) : Switch whether initial control pulse should be a randomized pulse, or a constant pulse. Default: True (randomized). Only active if pcof0 is NOT given.
# initialcondition (String or List(float)): Decide which initial quantum states at time t=0.0 are considered. Default: "basis" (all basis states spanned in the essential dimension Ne). Or choose "pure, 0,0,1,..." for one initial state |001...>), or pass a vector as initial state (complex, essential dimensions only)
# pcof0 (List) : Initial guess for the Bspline coefficients.
# pcof0_filename (String): Load an initial guess from file.
# rand_seed (int) : Seed for the random number generator (Default: system time, non reproducable)
# Pmin (Int) : Use this many time-points to discretize one period of the solution. The higher the number, the more accurate the solution will be, and the longer the computation will take. (Default: 150)
# verbose (Bool) : Flag to print more output (default: false). This is particularly useful to investigate the carrier wave frequencies that quandary selected.
# Quandary returns:
# t (List) : Time-points where the optimized control pulses are stored (ns)
# p, q (List(List)) : Lists of control pulses p(t) and q(t) for each qubit, unit MHz, evaluated at each time point in t
# infidelity (Float) : 1-fidelity of the realized unitary compared to the target gate
# expectedEnergy (List(List)) : Lists of the extected energy level trajectory for each qubit evaluated at each time point in t
# population (List(List(List)) : Lists of the level occupations (population) for each qubit evaluated at each time point, one list for each initial basis state, for each qubit
# After quandary.optimize() finished, you can access
# - quandary.popt (list) : List of optimized B-spline coefficients that define the optimized pulses p(t) and q(t)
# - quandary.uT (matrix) : The final-time quantum state for each of the propagated initial states (one column per initial state). For gate optimization (basis initial states), this is the solution operator / process matrix / propagator, see below
# If quandary.optimize() does not converge (or converges slowly), try optimizing again with
# a) A different seed for the random number, and/or
# b) Increased pulse duration, and/or
# c) Increased maximum pulse amplitude 'maxctrl_MHz'
# E.g.
# quandary.rand_seed += 1
# quandary.T = pulse_length + 10.0 # [ns]
# quandary.maxctrl_MHz = [15.0] # [MHz]
# quandary.update() # Always call this if you changed member variables
# print(f"Re-optimizing... ")
# t, p, q, infidelity, expectedEnergy, population = quandary.optimize()
# print(f"\nOptimized Fidelity = {1.0 - infidelity}")
#
# THE LONG STORY...
#
# Quandary uses B-spline basis functions with carrier waves to parameterize control pulses p(t) and q(t). It solves Schroedinger's equation (or Lindblad's master equation, see below) for each initial state, and compares the solution operator to the target unitary gate. Gradient-based optimization with box-constraints is used to design pulses that realize the target gate.
# Useful optional arguments. See help(Quandary) for a full list of all options and their default values
# control_enforce_BC (Bool) : Switch whether the control pulses should start and end at zero (p(t)=0 at t=0 and t=T). Default 'False'.
# tol_infidelity (Float) : Targeted accuraty of the pulses in terms of the fidelity. Default '1e-4' gives a 99.99% fidelity gate).
# maxiter (Int) : Maximum number of optimization iterations (default 100).
# initctrl_MHz (Float) : Amplitude of the initial guess for the pulses. Default: 1 MHz
# randomize_init_ctrl (Bool) : Switch whether initial control pulse should be a randomized pulse, or a constant pulse. Default: True (randomized). Only active if pcof0 is NOT given.
# initialcondition (String or List(float)): Decide which initial quantum states at time t=0.0 are considered. Default: "basis" (all basis states spanned in the essential dimension Ne). Or choose "pure, 0,0,1,..." for one initial state |001...>), or pass a vector as initial state (complex, essential dimensions only)
# pcof0 (List) : Initial guess for the Bspline coefficients.
# pcof0_filename (String): Load an initial guess from file.
# rand_seed (int) : Seed for the random number generator (Default: system time, non reproducable)
# Pmin (Int) : Use this many time-points to discretize one period of the solution. The higher the number, the more accurate the solution will be, and the longer the computation will take. (Default: 150)
# verbose (Bool) : Flag to print more output (default: false). This is particularly useful to investigate the carrier wave frequencies that quandary selected.
# Quandary returns:
# t (List) : Time-points where the optimized control pulses are stored (ns)
# p, q (List(List)) : Lists of control pulses p(t) and q(t) for each qubit, unit MHz, evaluated at each time point in t
# infidelity (Float) : 1-fidelity of the realized unitary compared to the target gate
# expectedEnergy (List(List)) : Lists of the extected energy level trajectory for each qubit evaluated at each time point in t
# population (List(List(List)) : Lists of the level occupations (population) for each qubit evaluated at each time point, one list for each initial basis state, for each qubit
# After quandary.optimize() finished, you can access
# - quandary.popt (list) : List of optimized B-spline coefficients that define the optimized pulses p(t) and q(t)
# - quandary.uT (matrix) : The final-time quantum state for each of the propagated initial states (one column per initial state). For gate optimization (basis initial states), this is the solution operator / process matrix / propagator, see below
# If quandary.optimize() does not converge (or converges slowly), try optimizing again with
# a) A different seed for the random number, and/or
# b) Increased pulse duration, and/or
# c) Increased maximum pulse amplitude 'maxctrl_MHz'
# E.g.
# quandary.rand_seed += 1
# quandary.T = pulse_length + 10.0 # [ns]
# quandary.maxctrl_MHz = [15.0] # [MHz]
# quandary.update() # Always call this if you changed member variables
# print(f"Re-optimizing... ")
# t, p, q, infidelity, expectedEnergy, population = quandary.optimize()
# print(f"\nOptimized Fidelity = {1.0 - infidelity}")
2. How to simulate the fidelity of (optimized or prescribed) control pulses¶
In [5]:
Copied!
# Use the quandary.simulate(...) function to simulate the dynamics of prescribed (or previously optimized) control pulses and evalute the fidelity
# Option (a): Pass Bspline control coefficients directly. Make sure that the Quandary object is set up with the correct number of Bsplines and carrier wave frequency
t1, p1, q1, infidelity1, _, _ = quandary.simulate(pcof0=quandary.popt[:]) # Here, I'm using the optimized parameters from the optimization above
print("Fidelity = ", 1.0 - infidelity1)
# Option: (b) Pass control pulses p(t) and q(t) directly. Make sure that the Quandary object is set up with the matching pulse duration and time-step size
t2, p2, q2, infidelity2, _, _ = quandary.simulate(pt0=p[:], qt0=q[:]) # Here, I'm using the optimized control pulses from the optimization above
print("Fidelity = ", 1.0 - infidelity2)
# Use the quandary.simulate(...) function to simulate the dynamics of prescribed (or previously optimized) control pulses and evalute the fidelity
# Option (a): Pass Bspline control coefficients directly. Make sure that the Quandary object is set up with the correct number of Bsplines and carrier wave frequency
t1, p1, q1, infidelity1, _, _ = quandary.simulate(pcof0=quandary.popt[:]) # Here, I'm using the optimized parameters from the optimization above
print("Fidelity = ", 1.0 - infidelity1)
# Option: (b) Pass control pulses p(t) and q(t) directly. Make sure that the Quandary object is set up with the matching pulse duration and time-step size
t2, p2, q2, infidelity2, _, _ = quandary.simulate(pt0=p[:], qt0=q[:]) # Here, I'm using the optimized control pulses from the optimization above
print("Fidelity = ", 1.0 - infidelity2)
Executing ' mpirun -np 3 quandary ./config.cfg --quiet . Runtype: simulation ... Fidelity = 0.999916180161363 Executing ' mpirun -np 3 quandary ./config.cfg --quiet . Runtype: simulation ... Fidelity = 0.999912289800873
3. How to evaluate control pulses on a specific sample rate¶
In [6]:
Copied!
# Quandary.optimize(..) returns control pulses p(t) and q(t) that are evaluated at each time point in the list t.
# You can evaluate the pulses on a different time grid (i.e. to downsample or upsample the pulse), use the following:
# Grab the optimized Bspline coefficients:
popt = quandary.popt.copy()
# Define a sample rate (desired number of points per ns)
samplerate = 64
# Evaluate the controls on this sample rate
t_ds, p_ds, q_ds = quandary.evalControls(pcof0=popt, points_per_ns=samplerate)
plot_pulse(Ne, t_ds, p_ds, q_ds)
# Note, p and q are lists of lists (one list for each qubit), hence accessing the first qubit's control is in p[0], e.g.
# plt.plot(t_ds, p_ds[0])
# plt.plot(t_ds, q_ds[0])
# Quandary.optimize(..) returns control pulses p(t) and q(t) that are evaluated at each time point in the list t.
# You can evaluate the pulses on a different time grid (i.e. to downsample or upsample the pulse), use the following:
# Grab the optimized Bspline coefficients:
popt = quandary.popt.copy()
# Define a sample rate (desired number of points per ns)
samplerate = 64
# Evaluate the controls on this sample rate
t_ds, p_ds, q_ds = quandary.evalControls(pcof0=popt, points_per_ns=samplerate)
plot_pulse(Ne, t_ds, p_ds, q_ds)
# Note, p and q are lists of lists (one list for each qubit), hence accessing the first qubit's control is in p[0], e.g.
# plt.plot(t_ds, p_ds[0])
# plt.plot(t_ds, q_ds[0])
Executing ' mpirun -np 1 quandary ./config.cfg --quiet . Runtype: evalcontrols ...
<Figure size 640x480 with 0 Axes>
Plotting control pulses. -> Press <enter> to proceed.
//Users/guenther5/Numerics/quandary/quandary.py:1222: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown plt.waitforbuttonpress(1);
4. How to get the propagator for given control parameters from Quandary¶
In [7]:
Copied!
# After a quandary.optimize/simulate(), the field quandary.uT contains the propagated states at the final time, for each of the initial states.
# If the initial states are the full basis (default), then this is the solution operator / process matrix / propagator.
propagator_T = quandary.uT
# Note: If guard levels were used, then uT is rectangular matrix with one column for each initial basis state in the essential level dimension, but one row for each energy level in the full essential + guard level dimension. In the example here, uT is a 4x3 matrix
# Hopefully, the guard levels are not populated such that you can ignore the last row to extract the square (almost unitary) propagator:
propagator_3x3 = propagator_T[:-1,:] # takes all but the last row and all columns
print("Propagator = ", propagator_3x3)
# You can also compute the fidelity from the propagator and unitary gate directly, e.g. with
nstates_ess = np.prod(quandary.Ne)
fid = np.abs(np.trace(propagator_3x3.conj().T @ unitary))**2/nstates_ess**2
print('Pulse Fidelity = ', fid)
# After a quandary.optimize/simulate(), the field quandary.uT contains the propagated states at the final time, for each of the initial states.
# If the initial states are the full basis (default), then this is the solution operator / process matrix / propagator.
propagator_T = quandary.uT
# Note: If guard levels were used, then uT is rectangular matrix with one column for each initial basis state in the essential level dimension, but one row for each energy level in the full essential + guard level dimension. In the example here, uT is a 4x3 matrix
# Hopefully, the guard levels are not populated such that you can ignore the last row to extract the square (almost unitary) propagator:
propagator_3x3 = propagator_T[:-1,:] # takes all but the last row and all columns
print("Propagator = ", propagator_3x3)
# You can also compute the fidelity from the propagator and unitary gate directly, e.g. with
nstates_ess = np.prod(quandary.Ne)
fid = np.abs(np.trace(propagator_3x3.conj().T @ unitary))**2/nstates_ess**2
print('Pulse Fidelity = ', fid)
Propagator = [[-1.28130733e-03-0.00475225j -7.65933638e-04-0.00148926j -9.94304781e-01+0.1063895j ] [-5.17399639e-04+0.00323792j -9.94345417e-01+0.10590098j 4.43076186e-04-0.00162594j] [-9.93543796e-01+0.11287038j 1.17023343e-03+0.00309499j 1.86814872e-04-0.00489109j]] Pulse Fidelity = 0.9999122897988258
5. How to compare results to QuTIP?¶
In [8]:
Copied!
# Here is an example for comparing Quandary's prediction to simulation with QuTIP.
from qutip import Qobj, propagator
from qutip import QobjEvo
fidelity_quandary = np.abs(np.trace(propagator_3x3.conj().T @ unitary))**2/np.prod(quandary.Ne)**2
print("Quandary fidelity: ", fidelity_quandary)
# QuTip requires pulses to be defined in rad/ns, so here I convert the output from quandary from MHz to rad / ns
p_radns = np.array(p[0])*1e-3*(2*np.pi)
q_radns = np.array(q[0])*1e-3*(2*np.pi)
# For some reason, QuTIP can not handle the time variable t from Quandary, so we set up a new one here...
samplerate = quandary.nsteps / quandary.T # Sample rate of controls from Quandary
t_new = np.arange(0, len(p[0]))*1/samplerate
# Define the Hamiltonian model for QuTIP. Here, I'm taking the Hamiltonians as used in Quandary
h0 = quandary.Hsys # System Hamiltonian
hc_re = quandary.Hc_re[0] # Control Hamiltonian for first qubit, real part
hc_im = 1.0j*quandary.Hc_im[0] # Control Hamiltonian for first qubit, imaginary part
# Solve Hamiltonian dynamics with Qutip
H_evo = QobjEvo([Qobj(h0), [Qobj(hc_re), p_radns], [Qobj(hc_im), q_radns]], tlist=t_new)
U_all = propagator(H_evo, t_new)
# Extract final propagator with proper dimensions
U_final = U_all[-1]
nstates = np.prod(quandary.Ne)
propagator_qutip = Qobj(U_final[:nstates, :nstates])
# Compute fidelity safely
target_unitary = Qobj(unitary)
overlap = propagator_qutip.dag() * target_unitary
fidelity_qutip = np.abs(overlap.tr())**2 / nstates**2
print('QuTIP fidelity: ', fidelity_qutip)
# Check: Both fidelities should be fairly close to each other.
# Here is an example for comparing Quandary's prediction to simulation with QuTIP.
from qutip import Qobj, propagator
from qutip import QobjEvo
fidelity_quandary = np.abs(np.trace(propagator_3x3.conj().T @ unitary))**2/np.prod(quandary.Ne)**2
print("Quandary fidelity: ", fidelity_quandary)
# QuTip requires pulses to be defined in rad/ns, so here I convert the output from quandary from MHz to rad / ns
p_radns = np.array(p[0])*1e-3*(2*np.pi)
q_radns = np.array(q[0])*1e-3*(2*np.pi)
# For some reason, QuTIP can not handle the time variable t from Quandary, so we set up a new one here...
samplerate = quandary.nsteps / quandary.T # Sample rate of controls from Quandary
t_new = np.arange(0, len(p[0]))*1/samplerate
# Define the Hamiltonian model for QuTIP. Here, I'm taking the Hamiltonians as used in Quandary
h0 = quandary.Hsys # System Hamiltonian
hc_re = quandary.Hc_re[0] # Control Hamiltonian for first qubit, real part
hc_im = 1.0j*quandary.Hc_im[0] # Control Hamiltonian for first qubit, imaginary part
# Solve Hamiltonian dynamics with Qutip
H_evo = QobjEvo([Qobj(h0), [Qobj(hc_re), p_radns], [Qobj(hc_im), q_radns]], tlist=t_new)
U_all = propagator(H_evo, t_new)
# Extract final propagator with proper dimensions
U_final = U_all[-1]
nstates = np.prod(quandary.Ne)
propagator_qutip = Qobj(U_final[:nstates, :nstates])
# Compute fidelity safely
target_unitary = Qobj(unitary)
overlap = propagator_qutip.dag() * target_unitary
fidelity_qutip = np.abs(overlap.tr())**2 / nstates**2
print('QuTIP fidelity: ', fidelity_qutip)
# Check: Both fidelities should be fairly close to each other.
Quandary fidelity: 0.9999122897988258 QuTIP fidelity: 0.9999222912697915
6. How to use Quandary to generate pulses that realize a state-to-state transfer¶
In [9]:
Copied!
# Example how to use Quandary to optimize a state-to-state transfer
# Define the number of essential state and guard levels. This example considers a 2-level qubit with no extra guard level.
Ne = [2] # Number of essential states per qubit
Ng = [0] # Number of extra guard level to prevent leakage out of the computational subspce
# Define the target and the initial state (in the essential level dimensions)
initialcondition = [1.0, 0.0]
targetstate = [1.0/np.sqrt(2), 1.0/np.sqrt(2)]
# Pulse length
T = 100.0
# Optionally: Define desired accuraty of the pulse in terms of the infidelity.
tol_infidelity = 1e-5
# Optionally: Set the initial control pulse amplitude. Default is 10MHz, leading to larger controls than neccessary here, so we decrease it here.
initctrl_MHz=0.1
# Optionally: Set maximum amplitude bound [MHz]
maxctrl_MHz = 5.0
# Pass all options to the Quandary configuration
quandary = Quandary(Ne=Ne, Ng=Ng, T=T, targetstate=targetstate, initialcondition=initialcondition, tol_infidelity=tol_infidelity, initctrl_MHz=initctrl_MHz, maxctrl_MHz=maxctrl_MHz, rand_seed=1234)
# Execute quandary. Runs in the background on one core.
t, pt, qt, infidelity, expectedEnergy, population = quandary.optimize()
print(f"\nFidelity = {1.0 - infidelity}")
# Plot the control pulse and expected energy level evolution.
plot_results_1osc(quandary, pt[0], qt[0], expectedEnergy[0], population[0])
# Note: In contrast to gate optimization (passing targetgate=unitary), the state-to-state transfer (targetstate=...) propagates only ONE initial state through the time domain, not the entire basis of initial states.
# After optimization, the final state stored in quandary.uT hence consists only of ONE column:
realized_state= quandary.uT.copy()
# To compute the process matrix (the propagator for this state-to-state transfer (the solution matrix / unitary that maps the initial to the target state), simulate the dynamics for the basis of initial states, using the optimized pulses, e.g. by doing so:
quandary.initialcondition = "basis"
quandary.update() # call update() after you have changed quandary member variables directly
t, pt, qt, _, _, _ = quandary.simulate(pcof0=quandary.popt, datadir="./test")
propagator = quandary.uT.copy()
# TEST the fidelity: Check whether the propagator uT indeed maps the initial [1,0] state to the desired target state:
test_fid = np.abs(np.array(targetstate).conj() @ propagator @ initialcondition) # Overlap between target state and evolved state
print("Test fidelity = ", test_fid)
# Example how to use Quandary to optimize a state-to-state transfer
# Define the number of essential state and guard levels. This example considers a 2-level qubit with no extra guard level.
Ne = [2] # Number of essential states per qubit
Ng = [0] # Number of extra guard level to prevent leakage out of the computational subspce
# Define the target and the initial state (in the essential level dimensions)
initialcondition = [1.0, 0.0]
targetstate = [1.0/np.sqrt(2), 1.0/np.sqrt(2)]
# Pulse length
T = 100.0
# Optionally: Define desired accuraty of the pulse in terms of the infidelity.
tol_infidelity = 1e-5
# Optionally: Set the initial control pulse amplitude. Default is 10MHz, leading to larger controls than neccessary here, so we decrease it here.
initctrl_MHz=0.1
# Optionally: Set maximum amplitude bound [MHz]
maxctrl_MHz = 5.0
# Pass all options to the Quandary configuration
quandary = Quandary(Ne=Ne, Ng=Ng, T=T, targetstate=targetstate, initialcondition=initialcondition, tol_infidelity=tol_infidelity, initctrl_MHz=initctrl_MHz, maxctrl_MHz=maxctrl_MHz, rand_seed=1234)
# Execute quandary. Runs in the background on one core.
t, pt, qt, infidelity, expectedEnergy, population = quandary.optimize()
print(f"\nFidelity = {1.0 - infidelity}")
# Plot the control pulse and expected energy level evolution.
plot_results_1osc(quandary, pt[0], qt[0], expectedEnergy[0], population[0])
# Note: In contrast to gate optimization (passing targetgate=unitary), the state-to-state transfer (targetstate=...) propagates only ONE initial state through the time domain, not the entire basis of initial states.
# After optimization, the final state stored in quandary.uT hence consists only of ONE column:
realized_state= quandary.uT.copy()
# To compute the process matrix (the propagator for this state-to-state transfer (the solution matrix / unitary that maps the initial to the target state), simulate the dynamics for the basis of initial states, using the optimized pulses, e.g. by doing so:
quandary.initialcondition = "basis"
quandary.update() # call update() after you have changed quandary member variables directly
t, pt, qt, _, _, _ = quandary.simulate(pcof0=quandary.popt, datadir="./test")
propagator = quandary.uT.copy()
# TEST the fidelity: Check whether the propagator uT indeed maps the initial [1,0] state to the desired target state:
test_fid = np.abs(np.array(targetstate).conj() @ propagator @ initialcondition) # Overlap between target state and evolved state
print("Test fidelity = ", test_fid)
Executing ' mpirun -np 1 quandary ./config.cfg --quiet . Runtype: optimization ... Objective Tikhonov Penalty-Leakage Penalty-StateVar Penalty-TotalEnergy Penalty-CtrlVar 0 5.02049583478701e-01 + 2.56090704561570e-10 + 0.00000000000000e+00 + 9.01784421151395e-15 + 8.98449028423572e-09 + 0.00000000000000e+00 Fidelity = 4.97950416521299e-01 ||Grad|| = 1.33055979431340e-01 1 4.62265425914091e-02 + 1.73742095334636e-07 + 0.00000000000000e+00 + 1.24752998393009e-09 + 1.00933850676236e-05 + 0.00000000000000e+00 Fidelity = 9.53773457408591e-01 ||Grad|| = 3.24748385751329e-01 2 6.43219251352212e-03 + 8.61543734506834e-08 + 0.00000000000000e+00 + 5.68192565246782e-10 + 5.00304152044488e-06 + 0.00000000000000e+00 Fidelity = 9.93567807486478e-01 ||Grad|| = 9.69025583835661e-02 3 3.35556900665779e-06 + 1.07258277186620e-07 + 0.00000000000000e+00 + 7.57406598781309e-10 + 6.22957666424030e-06 + 0.00000000000000e+00 Fidelity = 9.99996644430993e-01 ||Grad|| = 6.19714259475643e-02 Optimization converged with small infidelity. Fidelity = 0.999996644430993
<Figure size 640x480 with 0 Axes>
Plotting results... -> Press <enter> to proceed.
//Users/guenther5/Numerics/quandary/quandary.py:1374: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown plt.waitforbuttonpress(1);
Executing ' mpirun -np 2 quandary ./config.cfg --quiet . Runtype: simulation ... Test fidelity = 0.9999983222139354
7. How to use the open-system solver to model decay and dephasing¶
In [10]:
Copied!
# You can simulate the dynamics under Lindbladian noise operators in Quandary (for decay and dephasing noise).
# To do so, the only thing to change is to pass the decay and dephasing times to the quandary configuration. All other options and runtypes are the same.
# Note that the Lindblad solver is computationally more expensive, especially for gate optimization. It might be good to pre-optimize on the closed system first and use the optimized parameters as initial guess for the Lindblad solver.
# Here is an example for the state-to-state transfer problem
# State-to-state transfer configuration
Ne = [2] # Number of essential states
Ng = [1]
targetstate = [1.0/np.sqrt(2), 1.0/np.sqrt(2)] # target state
initialstate = [1.0, 0.0] # initial ground state
T = 100.0 # pulse length
tol_infidelity = 1e-5 # Infidelity tolerance
maxiter = 50 # Maximum number of iterations
maxctrl_MHz = 4.0 # Maximum allowed control pulse amplitude [MHz]
initctrl_MHz = 0.1 # Initial guess for control pulse amplitude [MHz]
T1 = [100000.0] # T1 decay time [ns]
T2 = [80000.0] # T2 dephasing time [ns]
quandary_lblad = Quandary(Ne=Ne, Ng=Ng, T=T, T1=T1, T2=T2, targetstate=targetstate, initialcondition=initialstate, maxctrl_MHz=maxctrl_MHz, initctrl_MHz=initctrl_MHz, maxiter=maxiter)
# Create and pre-optimize on the closed-system solver:
quandary_closed = quandary_lblad.copy()
quandary_closed.T1=[] # disable T1 and T2, don't forget to update() quandary
quandary_closed.T2=[]
quandary_closed.update()
t, pt, qt, infidelity, expectedEnergy, population = quandary_closed.optimize()
print(f"\nOptimized Fidelity = {1.0 - infidelity}")
# Evaluate the optimized pulses using the open system solver:
t, pt, qt, infidelity, expectedEnergy, population = quandary_lblad.simulate(pcof0=quandary_closed.popt)
print(f"\nFidelity under noise = {1.0 - infidelity}")
# Further optimize on the lindblad solver
t, pt, qt, infidelity, expectedEnergy, population = quandary_lblad.optimize(pcof0=quandary_closed.popt)
print(f"\nRe-optimized Fidelity under noise = {1.0 - infidelity}")
# # Plot the control pulse and expected energy level evolution.
plot_results_1osc(quandary_lblad, pt[0], qt[0], expectedEnergy[0], population[0])
# You can simulate the dynamics under Lindbladian noise operators in Quandary (for decay and dephasing noise).
# To do so, the only thing to change is to pass the decay and dephasing times to the quandary configuration. All other options and runtypes are the same.
# Note that the Lindblad solver is computationally more expensive, especially for gate optimization. It might be good to pre-optimize on the closed system first and use the optimized parameters as initial guess for the Lindblad solver.
# Here is an example for the state-to-state transfer problem
# State-to-state transfer configuration
Ne = [2] # Number of essential states
Ng = [1]
targetstate = [1.0/np.sqrt(2), 1.0/np.sqrt(2)] # target state
initialstate = [1.0, 0.0] # initial ground state
T = 100.0 # pulse length
tol_infidelity = 1e-5 # Infidelity tolerance
maxiter = 50 # Maximum number of iterations
maxctrl_MHz = 4.0 # Maximum allowed control pulse amplitude [MHz]
initctrl_MHz = 0.1 # Initial guess for control pulse amplitude [MHz]
T1 = [100000.0] # T1 decay time [ns]
T2 = [80000.0] # T2 dephasing time [ns]
quandary_lblad = Quandary(Ne=Ne, Ng=Ng, T=T, T1=T1, T2=T2, targetstate=targetstate, initialcondition=initialstate, maxctrl_MHz=maxctrl_MHz, initctrl_MHz=initctrl_MHz, maxiter=maxiter)
# Create and pre-optimize on the closed-system solver:
quandary_closed = quandary_lblad.copy()
quandary_closed.T1=[] # disable T1 and T2, don't forget to update() quandary
quandary_closed.T2=[]
quandary_closed.update()
t, pt, qt, infidelity, expectedEnergy, population = quandary_closed.optimize()
print(f"\nOptimized Fidelity = {1.0 - infidelity}")
# Evaluate the optimized pulses using the open system solver:
t, pt, qt, infidelity, expectedEnergy, population = quandary_lblad.simulate(pcof0=quandary_closed.popt)
print(f"\nFidelity under noise = {1.0 - infidelity}")
# Further optimize on the lindblad solver
t, pt, qt, infidelity, expectedEnergy, population = quandary_lblad.optimize(pcof0=quandary_closed.popt)
print(f"\nRe-optimized Fidelity under noise = {1.0 - infidelity}")
# # Plot the control pulse and expected energy level evolution.
plot_results_1osc(quandary_lblad, pt[0], qt[0], expectedEnergy[0], population[0])
Executing ' mpirun -np 1 quandary ./config.cfg --quiet . Runtype: optimization ... Objective Tikhonov Penalty-Leakage Penalty-StateVar Penalty-TotalEnergy Penalty-CtrlVar 0 5.04302153193254e-01 + 2.10627618973362e-10 + 7.68884448559118e-13 + 1.30073858299733e-14 + 6.26929913180880e-09 + 0.00000000000000e+00 Fidelity = 4.95697846806746e-01 ||Grad|| = 1.06633467203195e-01 1 4.72015038447107e-02 + 1.74549639007923e-07 + 2.92029258595263e-06 + 1.65120445024727e-09 + 1.01403330272047e-05 + 0.00000000000000e+00 Fidelity = 9.52798496155289e-01 ||Grad|| = 2.87426078195549e-01 2 6.62533406187116e-03 + 8.58585448540212e-08 + 7.81392051010818e-07 + 8.04178467209846e-10 + 4.98513325515569e-06 + 0.00000000000000e+00 Fidelity = 9.93374665938129e-01 ||Grad|| = 7.44287957959761e-02 3 1.48898185361457e-05 + 1.07258244109532e-07 + 1.19054377578463e-06 + 1.06171270481068e-09 + 6.22907730287354e-06 + 0.00000000000000e+00 Fidelity = 9.99985110181464e-01 ||Grad|| = 6.53029668159423e-02 Optimization converged with small final time cost. Optimized Fidelity = 0.999985110181464 Executing ' mpirun -np 1 quandary ./config.cfg --quiet . Runtype: simulation ... Fidelity under noise = 0.999771006775087 Executing ' mpirun -np 1 quandary ./config.cfg --quiet . Runtype: optimization ... Objective Tikhonov Penalty-Leakage Penalty-StateVar Penalty-TotalEnergy Penalty-CtrlVar 0 2.28993224913099e-04 + 1.07258244109532e-07 + 2.43336388444282e-11 + 0.00000000000000e+00 + 6.22907730287354e-06 + 0.00000000000000e+00 Fidelity = 9.99771006775087e-01 ||Grad|| = 6.13164245554497e-02 1 2.26078220585180e-04 + 1.06806858565545e-07 + 2.39668918007565e-11 + 0.00000000000000e+00 + 6.20290239183761e-06 + 0.00000000000000e+00 Fidelity = 9.99773921779415e-01 ||Grad|| = 1.20347930207001e-02 2 2.25758075570015e-04 + 1.06837156241860e-07 + 2.40000541007549e-11 + 0.00000000000000e+00 + 6.20476423614831e-06 + 0.00000000000000e+00 Fidelity = 9.99774241924430e-01 ||Grad|| = 8.80172499657814e-03 3 2.21773652809398e-04 + 1.06855281228219e-07 + 2.44709702223234e-11 + 0.00000000000000e+00 + 6.20729860339882e-06 + 0.00000000000000e+00 Fidelity = 9.99778226347191e-01 ||Grad|| = 1.62081909221431e-02 4 2.13306992439488e-04 + 1.07321476848438e-07 + 3.22293418172472e-11 + 0.00000000000000e+00 + 6.22139805306520e-06 + 0.00000000000000e+00 Fidelity = 9.99786693007561e-01 ||Grad|| = 1.59653169436672e-02 5 2.09984866216928e-04 + 1.07589274386314e-07 + 3.77364843102416e-11 + 0.00000000000000e+00 + 6.22762055202065e-06 + 0.00000000000000e+00 Fidelity = 9.99790015133783e-01 ||Grad|| = 5.65192077110494e-03 6 2.03386036798925e-04 + 1.09018054464440e-07 + 5.88742590949734e-11 + 0.00000000000000e+00 + 6.27670593032406e-06 + 0.00000000000000e+00 Fidelity = 9.99796613963201e-01 ||Grad|| = 2.35643760742225e-02 7 1.99712034661070e-04 + 1.10569806499021e-07 + 7.80838377593882e-11 + 0.00000000000000e+00 + 6.33702903549288e-06 + 0.00000000000000e+00 Fidelity = 9.99800287965339e-01 ||Grad|| = 3.40112346434190e-02 8 1.93637001950298e-04 + 1.18023613807368e-07 + 1.46118379606516e-10 + 0.00000000000000e+00 + 6.66960192551512e-06 + 0.00000000000000e+00 Fidelity = 9.99806362998050e-01 ||Grad|| = 2.81437197631929e-02 9 1.88884697759240e-04 + 1.19908987385269e-07 + 1.57045574019375e-10 + 0.00000000000000e+00 + 6.76845991768941e-06 + 0.00000000000000e+00 Fidelity = 9.99811115302241e-01 ||Grad|| = 1.09875051046019e-02 10 1.80223739308372e-04 + 1.24909573305543e-07 + 1.77963459916433e-10 + 0.00000000000000e+00 + 7.04652183923839e-06 + 0.00000000000000e+00 Fidelity = 9.99819776260692e-01 ||Grad|| = 3.25876942277403e-02 11 1.67164641492690e-04 + 1.36225374045361e-07 + 2.27103241193916e-10 + 0.00000000000000e+00 + 7.66844437873525e-06 + 0.00000000000000e+00 Fidelity = 9.99832835358507e-01 ||Grad|| = 3.71614699969409e-02 12 1.64956697478180e-04 + 1.37806327182121e-07 + 2.36056886706543e-10 + 0.00000000000000e+00 + 7.75523192257459e-06 + 0.00000000000000e+00 Fidelity = 9.99835043302522e-01 ||Grad|| = 4.04278873509538e-02 13 1.55857852121466e-04 + 1.46349657759573e-07 + 2.83472756645525e-10 + 0.00000000000000e+00 + 8.21598853923484e-06 + 0.00000000000000e+00 Fidelity = 9.99844142147879e-01 ||Grad|| = 4.33180839759901e-02 14 1.54268444224281e-04 + 1.47733884528709e-07 + 2.91602014283611e-10 + 0.00000000000000e+00 + 8.29179159822293e-06 + 0.00000000000000e+00 Fidelity = 9.99845731555776e-01 ||Grad|| = 4.71852073979892e-02 15 1.50435556120820e-04 + 1.44609590333247e-07 + 2.89855923055578e-10 + 0.00000000000000e+00 + 8.12432603124287e-06 + 0.00000000000000e+00 Fidelity = 9.99849564443879e-01 ||Grad|| = 3.52749151452580e-02 16 1.46158887526004e-04 + 1.48988625052298e-07 + 3.12183524410338e-10 + 0.00000000000000e+00 + 8.35950810584543e-06 + 0.00000000000000e+00 Fidelity = 9.99853841112474e-01 ||Grad|| = 4.71672872869985e-02 17 1.42660657223792e-04 + 1.45721003011682e-07 + 3.05985715925823e-10 + 0.00000000000000e+00 + 8.18576341263927e-06 + 0.00000000000000e+00 Fidelity = 9.99857339342776e-01 ||Grad|| = 2.61193058293791e-02 18 1.39333435297040e-04 + 1.49654012006643e-07 + 3.24952526350875e-10 + 0.00000000000000e+00 + 8.39632678726048e-06 + 0.00000000000000e+00 Fidelity = 9.99860666564703e-01 ||Grad|| = 3.76498024275639e-02 19 1.30305796037122e-04 + 1.54581305498917e-07 + 3.88051050770953e-10 + 0.00000000000000e+00 + 8.63779439632753e-06 + 0.00000000000000e+00 Fidelity = 9.99869694203963e-01 ||Grad|| = 5.51590776744811e-02 20 1.25189310665852e-04 + 1.56565745017190e-07 + 4.31738002831865e-10 + 0.00000000000000e+00 + 8.71309193329720e-06 + 0.00000000000000e+00 Fidelity = 9.99874810689334e-01 ||Grad|| = 3.28161309731271e-02 21 1.23806248740310e-04 + 1.56364818998293e-07 + 4.28819574002137e-10 + 0.00000000000000e+00 + 8.70579253267380e-06 + 0.00000000000000e+00 Fidelity = 9.99876193751260e-01 ||Grad|| = 1.97643638546395e-02 22 1.20713756892155e-04 + 1.60174813622650e-07 + 4.46480744174271e-10 + 0.00000000000000e+00 + 8.91072159416754e-06 + 0.00000000000000e+00 Fidelity = 9.99879286243108e-01 ||Grad|| = 7.50990698875321e-03 23 1.18531944100164e-04 + 1.63951795545787e-07 + 4.63842508181624e-10 + 0.00000000000000e+00 + 9.10942346770481e-06 + 0.00000000000000e+00 Fidelity = 9.99881468055900e-01 ||Grad|| = 1.12446015089568e-02 24 1.18287077240153e-04 + 1.64511258463558e-07 + 4.66325919074695e-10 + 0.00000000000000e+00 + 9.13788297845654e-06 + 0.00000000000000e+00 Fidelity = 9.99881712922760e-01 ||Grad|| = 1.37134230492447e-02 25 1.18037364984724e-04 + 1.65168527770391e-07 + 4.69114387602680e-10 + 0.00000000000000e+00 + 9.17150021383621e-06 + 0.00000000000000e+00 Fidelity = 9.99881962635015e-01 ||Grad|| = 1.65149689278282e-02 26 1.17329812977518e-04 + 1.68702039638788e-07 + 4.80275743408714e-10 + 0.00000000000000e+00 + 9.35493974459227e-06 + 0.00000000000000e+00 Fidelity = 9.99882670187022e-01 ||Grad|| = 2.91857143513821e-02 27 1.16623293524687e-04 + 1.67399257676694e-07 + 4.79241622860109e-10 + 0.00000000000000e+00 + 9.28880564781688e-06 + 0.00000000000000e+00 Fidelity = 9.99883376706475e-01 ||Grad|| = 1.15645756141466e-02 28 1.16145789642030e-04 + 1.69079153638719e-07 + 4.82858618062823e-10 + 0.00000000000000e+00 + 9.36363375349057e-06 + 0.00000000000000e+00 Fidelity = 9.99883854210358e-01 ||Grad|| = 2.34530478344304e-02 29 1.15840998501704e-04 + 1.68315628679969e-07 + 4.81803471034956e-10 + 0.00000000000000e+00 + 9.32094606342072e-06 + 0.00000000000000e+00 Fidelity = 9.99884159001498e-01 ||Grad|| = 1.15526020707768e-02 30 1.15179741492000e-04 + 1.72274317364716e-07 + 4.92679016113979e-10 + 0.00000000000000e+00 + 9.49475687309361e-06 + 0.00000000000000e+00 Fidelity = 9.99884820258508e-01 ||Grad|| = 2.89195487286064e-02 31 1.14426876874685e-04 + 1.71115224892537e-07 + 4.93755156084762e-10 + 0.00000000000000e+00 + 9.42575657214545e-06 + 0.00000000000000e+00 Fidelity = 9.99885573123125e-01 ||Grad|| = 1.02106956854458e-02 32 1.13047002386568e-04 + 1.75131032876348e-07 + 5.13198105780701e-10 + 0.00000000000000e+00 + 9.57349800096227e-06 + 0.00000000000000e+00 Fidelity = 9.99886952997613e-01 ||Grad|| = 1.30782890717306e-02 33 1.11971216382734e-04 + 1.78307970161456e-07 + 5.28400926909190e-10 + 0.00000000000000e+00 + 9.69474300176169e-06 + 0.00000000000000e+00 Fidelity = 9.99888028783617e-01 ||Grad|| = 1.38886337184892e-02 34 1.07422520112199e-04 + 1.96045239743980e-07 + 6.12067582467903e-10 + 0.00000000000000e+00 + 1.03658742065014e-05 + 0.00000000000000e+00 Fidelity = 9.99892577479888e-01 ||Grad|| = 2.59698724381572e-02 35 1.07215487177958e-04 + 1.93550109430204e-07 + 6.00052958998210e-10 + 0.00000000000000e+00 + 1.02792677979056e-05 + 0.00000000000000e+00 Fidelity = 9.99892784512822e-01 ||Grad|| = 1.50399118787852e-02 36 1.07110855597892e-04 + 1.94161595226304e-07 + 6.04244459123006e-10 + 0.00000000000000e+00 + 1.03006037154944e-05 + 0.00000000000000e+00 Fidelity = 9.99892889144402e-01 ||Grad|| = 1.69646455858216e-02 37 1.05986616943499e-04 + 1.96107475081802e-07 + 6.27084111342618e-10 + 0.00000000000000e+00 + 1.03845594186616e-05 + 0.00000000000000e+00 Fidelity = 9.99894013383057e-01 ||Grad|| = 1.52352554534644e-02 38 1.05936617934677e-04 + 1.96572337093705e-07 + 6.30288093446773e-10 + 0.00000000000000e+00 + 1.04020831354242e-05 + 0.00000000000000e+00 Fidelity = 9.99894063382065e-01 ||Grad|| = 1.69114347284043e-02 39 1.05740074759830e-04 + 1.95571035646791e-07 + 6.26994963554851e-10 + 0.00000000000000e+00 + 1.03691146275579e-05 + 0.00000000000000e+00 Fidelity = 9.99894259925240e-01 ||Grad|| = 5.76554676364486e-03 40 1.05611783303705e-04 + 1.96469062409002e-07 + 6.34217701443377e-10 + 0.00000000000000e+00 + 1.04071498212252e-05 + 0.00000000000000e+00 Fidelity = 9.99894388216696e-01 ||Grad|| = 1.17736307648955e-02 41 1.05575843094785e-04 + 1.96165010351205e-07 + 6.32389208141837e-10 + 0.00000000000000e+00 + 1.03979614536512e-05 + 0.00000000000000e+00 Fidelity = 9.99894424156905e-01 ||Grad|| = 8.81909409438841e-03 42 1.05416269641112e-04 + 1.96758493059084e-07 + 6.39571636101170e-10 + 0.00000000000000e+00 + 1.04288910390230e-05 + 0.00000000000000e+00 Fidelity = 9.99894583730359e-01 ||Grad|| = 1.00561777531292e-02 43 1.05299425996908e-04 + 1.96521471133426e-07 + 6.38877340992711e-10 + 0.00000000000000e+00 + 1.04310203292536e-05 + 0.00000000000000e+00 Fidelity = 9.99894700574003e-01 ||Grad|| = 4.56114520212739e-03 44 1.05123273782648e-04 + 1.97404465569089e-07 + 6.47429872857280e-10 + 0.00000000000000e+00 + 1.04773005097978e-05 + 0.00000000000000e+00 Fidelity = 9.99894876726217e-01 ||Grad|| = 3.15389064576328e-03 45 1.04825005877052e-04 + 1.98998507253118e-07 + 6.57263650597029e-10 + 0.00000000000000e+00 + 1.05458535028181e-05 + 0.00000000000000e+00 Fidelity = 9.99895174994123e-01 ||Grad|| = 2.87627453181501e-03 46 1.04093291126928e-04 + 2.07245380038818e-07 + 6.95622167511459e-10 + 0.00000000000000e+00 + 1.08736032077078e-05 + 0.00000000000000e+00 Fidelity = 9.99895906708873e-01 ||Grad|| = 1.95226128792696e-02 47 1.03828690928620e-04 + 2.05042358683377e-07 + 6.86043639171949e-10 + 0.00000000000000e+00 + 1.07858208654711e-05 + 0.00000000000000e+00 Fidelity = 9.99896171309071e-01 ||Grad|| = 8.55622542925377e-03 48 1.03311567553277e-04 + 2.08811434901771e-07 + 6.91585553586705e-10 + 0.00000000000000e+00 + 1.09294711125066e-05 + 0.00000000000000e+00 Fidelity = 9.99896688432447e-01 ||Grad|| = 1.71948715094712e-02 49 1.03068506453252e-04 + 2.07438806081100e-07 + 6.82073810754494e-10 + 0.00000000000000e+00 + 1.08741444365783e-05 + 0.00000000000000e+00 Fidelity = 9.99896931493547e-01 ||Grad|| = 8.91494104389400e-03 50 1.03003259683931e-04 + 2.07749008250601e-07 + 6.82901301186979e-10 + 0.00000000000000e+00 + 1.08863398059898e-05 + 0.00000000000000e+00 Fidelity = 9.99896996740316e-01 ||Grad|| = 8.34647834172315e-03 Optimization stopped at maximum number of iterations. Re-optimized Fidelity under noise = 0.999896996740316
<Figure size 640x480 with 0 Axes>
Plotting results... -> Press <enter> to proceed.
In [20]:
Copied!
# ## FAQs
# * What to do if the optimizer does not converge?
# - Run again with a different initial guess (rand_seed=<different integer>, randomize_ctrl_init=True/False, initctrl_MHz=<double>)
# - Remove or increase the control bounds (maxctrl_MHz=<larger double or list>)
# - Increase the time domain (T)
# - Set 'verbose=True' when constructing the Quandary object. Inspect the output, in particular the selected carrier wave frequencies. Do they make sense for your target gate?
# - Increase the number of iterations (maxiter)
# - Increase the number of Bspline basis functions (increase nsplines, or decrease spline_knot_spacing)
# - Inspect the files written to './run_dir/' (or the user-defined alternative output directory). In particular config.cfg, targetgate/state.dat.
# * What to do if the results don't match to other software, or to my expectations?
# - Turn on 'verbose=True' and check screen output of quandary.simulate(). Anything suspicious?
# - Test if the time-step size is small enough (aka if "Pmin" is large enough):
# * Simulate the pulses again with smaller Pmin and compare the uT or fidelity. If the fidelity differs significantly, time-step size should be decreased further (by using a larger "Pmin"). Have a look at the 'timestep_richardson_est' function in quandary.py, which might be helpful to do this test.
# - Check if the correct pulses are used (e.g. by plotting the returned p and q after "simulation" runs as well as after calling 'evalControl(..)')
# Any time: Please reach out to guenther5@llnl.gov if you have any questions, concerns, or if you found a bug.
# ## FAQs
# * What to do if the optimizer does not converge?
# - Run again with a different initial guess (rand_seed=, randomize_ctrl_init=True/False, initctrl_MHz=)
# - Remove or increase the control bounds (maxctrl_MHz=)
# - Increase the time domain (T)
# - Set 'verbose=True' when constructing the Quandary object. Inspect the output, in particular the selected carrier wave frequencies. Do they make sense for your target gate?
# - Increase the number of iterations (maxiter)
# - Increase the number of Bspline basis functions (increase nsplines, or decrease spline_knot_spacing)
# - Inspect the files written to './run_dir/' (or the user-defined alternative output directory). In particular config.cfg, targetgate/state.dat.
# * What to do if the results don't match to other software, or to my expectations?
# - Turn on 'verbose=True' and check screen output of quandary.simulate(). Anything suspicious?
# - Test if the time-step size is small enough (aka if "Pmin" is large enough):
# * Simulate the pulses again with smaller Pmin and compare the uT or fidelity. If the fidelity differs significantly, time-step size should be decreased further (by using a larger "Pmin"). Have a look at the 'timestep_richardson_est' function in quandary.py, which might be helpful to do this test.
# - Check if the correct pulses are used (e.g. by plotting the returned p and q after "simulation" runs as well as after calling 'evalControl(..)')
# Any time: Please reach out to guenther5@llnl.gov if you have any questions, concerns, or if you found a bug.
In [ ]:
Copied!