# Import required librariesimportnumpyasnpfrommathimportceilimportmatplotlib.pyplotaspltfromIPython.displayimportdisplay,Latexfromdatetimeimportdatetimefrompytzimporttimezone# Install and import QuTiP if neededtry:fromqutipimport*exceptImportError:%pipinstallqutipfromqutipimport*# Configure matplotlib for LaTeX renderingplt.rcParams.update({"text.usetex":True,"text.latex.preamble":r"\usepackage{amsmath} \usepackage{physics}","font.family":"serif",})%configInlineBackend.figure_format='svg'# Print version and execution time infoprint(f"QuTiP version: {qutip.__version__}")print("Time of execution: ",datetime.now(timezone("America/Chicago")).strftime("%Y-%m-%d %H:%M:%S"),)
12
QuTiP version: 5.1.1
Time of execution: 2025-05-03 16:52:10
importdatetimefromqutip.qip.operationsimportcphase# import qutip.logging_utils as loggingimportlogginglogger=logging.getLogger("qutip")log_level=logging.INFO# #QuTiP control modulesimportqutip.control.pulseoptimascpo
Consider an NV with two carbon-13 nuclei nearby with energy levels of the electron spin system identical to that shown in Extended Data Figure 6 of Waldherr et al. You have to consider only the 4 levels arising from the two carbon atoms (disregard the nitrogen nuclear spin). Here, we will use QuTiP to implement a simplified implementation of quantum optimal control similar to what was done in the paper.
To begin, use QuTiP to implement the natural CC-\(2\pi\) gate where we drive resonantly on the \(\ket{011} \rightarrow \ket{111}\) (\(\ket{NV, C_1, C_2}\)) transition assuming a Rabi frequency of 0.1 MHz and the energy splittings from the paper.
The \(CC-2\pi\) gate can be expressed as: \(-\mathbb{1}\otimes\ket{11}\bra{11}+\mathbb{1}\otimes\left( \mathbb{1} - \ket{11}\bra{11} \right)\)
defCC2Pi():mat=np.zeros((8,8),dtype=complex)foriinnp.arange(0,8):mat[i,i]=1.0mat[3,3]=mat[7,7]=-1returnQobj(mat,dims=[[2,2,2],[2,2,2]])psi_0=tensor(basis(2,0),basis(2,1),basis(2,1))# NV, C1, C2 basis orderingpsi_1=tensor(basis(2,0),basis(2,0),basis(2,1))psi_2=tensor(basis(2,0),basis(2,1),basis(2,0))psi_3=tensor(basis(2,0),basis(2,0),basis(2,0))S000=tensor(basis(2,0),basis(2,0),basis(2,0))# all states in basis notationS001=tensor(basis(2,0),basis(2,0),basis(2,1))S010=tensor(basis(2,0),basis(2,1),basis(2,0))S011=tensor(basis(2,0),basis(2,1),basis(2,1))S100=tensor(basis(2,1),basis(2,0),basis(2,0))S101=tensor(basis(2,1),basis(2,0),basis(2,1))S110=tensor(basis(2,1),basis(2,1),basis(2,0))S111=tensor(basis(2,1),basis(2,1),basis(2,1))print(np.real(np.around(CC2Pi().full(),2)))# print out target matrix
The Hamiltonian in some fixed rotating frame is \(H = \Delta S_Z + \sum_i A_i I_{iZ}S_Z + H_{\text{drive}}\), where I choose \(S_Z\) as diag(0,-1) and \(I_{IZ}\) as diag(1/2,-1/2). \(\Delta\) depends on driving frequency.
########### Initial values ##################################### Here I will parametrize detuning with respect to omega1omega1=0.1e6E2=89e3/omega1# between same spin up down for eg: b/n |100> and |101>E1=413e3/omega1-E2# between two nuclear spins eg: b/n |110> and |101>omega=1###################################################################### Defining detuning when drive is resonant with |1,1,1> state ######################det100=-2*E2-E1det101=-E2-E1det110=-E2det111=0###################################################################################################### Initialization ####################psi0=tensor(basis(2,0),basis(2,1),basis(2,1))##################################################### define the detuning of the states ########################population_100=tensor(basis(2,1)*basis(2,1).dag(),basis(2,0)*basis(2,0).dag(),basis(2,0)*basis(2,0).dag(),)# state |1,0,0>population_101=tensor(basis(2,1)*basis(2,1).dag(),basis(2,0)*basis(2,0).dag(),basis(2,1)*basis(2,1).dag(),)# state |1,0,1>population_110=tensor(basis(2,1)*basis(2,1).dag(),basis(2,1)*basis(2,1).dag(),basis(2,0)*basis(2,0).dag(),)# state |1,1,0>population_111=tensor(basis(2,1)*basis(2,1).dag(),basis(2,1)*basis(2,1).dag(),basis(2,1)*basis(2,1).dag(),)# state |1,1,1>H=(det100*population_100+det101*population_101+det110*population_110+det111*population_111)################################################################################### Define the allowed transitions ################################### only electronic spin can change, nuclear spins will remain the sametransition1=(tensor(basis(2,1),basis(2,0),basis(2,0))*tensor(basis(2,0),basis(2,0),basis(2,0)).dag())# |1,0,0><0,0,0|transition2=(tensor(basis(2,1),basis(2,1),basis(2,0))*tensor(basis(2,0),basis(2,1),basis(2,0)).dag())# |1,1,0><0,1,0|transition3=(tensor(basis(2,1),basis(2,0),basis(2,1))*tensor(basis(2,0),basis(2,0),basis(2,1)).dag())# |1,0,1><0,0,1|transition4=(tensor(basis(2,1),basis(2,1),basis(2,1))*tensor(basis(2,0),basis(2,1),basis(2,1)).dag())# |1,1,1><0,1,1|H_int_x=(omega/2*(transition1+transition1.dag()+transition2+transition2.dag()+transition3+transition3.dag()+transition4+transition4.dag()))####################################################################################################### Hamiltonian ############################## This includes detuning part and the driving partH_tot=H+H_int_x############################################################################### Time and Evolution #######################tevol=2*np.pi/omegatimes=np.linspace(0,tevol,1000)results=sesolve(H_tot,psi0,times,e_ops=[population_100,population_101,population_110,population_111,psi0*psi0.dag(),],)################################################################################# Plotting #############################plt.plot(times/(2*np.pi/omega),results.expect[0],label=r"$\ket{100}$")plt.plot(times/(2*np.pi/omega),results.expect[1],label=r"$\ket{101}$")plt.plot(times/(2*np.pi/omega),results.expect[2],label=r"$\ket{110}$")plt.plot(times/(2*np.pi/omega),results.expect[3],label=r"$\ket{111}$")plt.plot(times/(2*np.pi/omega),results.expect[4],label=r"$\ket{011}$")plt.legend()plt.xlabel(r"times ($2\pi/\Omega$)")plt.show()print(H_tot)
Here we see it acts as perfect 2pi pulse. But this assumes that there is perfect initialization and also that other states are empty, which is not the case in general while manipulating the system. If we have an equal super position states in the ground state, this will not be a perfect CC2pi gate as can be seen below.
######## New inital state #####################psi1=0.5*(tensor(basis(2,0),basis(2,1),basis(2,1))+tensor(basis(2,0),basis(2,1),basis(2,0))+tensor(basis(2,0),basis(2,0),basis(2,1))+tensor(basis(2,0),basis(2,0),basis(2,0)))################################################################## Time and Evolution #######################results=sesolve(H_tot,psi1,times,e_ops=[population_100,population_101,population_110,population_111,psi0*psi0.dag(),],)################################################################################# Plotting #############################plt.plot(times/(2*np.pi/omega),results.expect[0],label=r"$\ket{100}$")plt.plot(times/(2*np.pi/omega),results.expect[1],label=r"$\ket{101}$")plt.plot(times/(2*np.pi/omega),results.expect[2],label=r"$\ket{110}$")plt.plot(times/(2*np.pi/omega),results.expect[3],label=r"$\ket{111}$")plt.plot(times/(2*np.pi/omega),results.expect[4],label=r"$\ket{011}$")plt.legend()plt.xlabel(r"times ($2\pi/\Omega$)")plt.show()###########################################################
################## Time and Evolution #######################results=sesolve(H_tot,psi1,times)#############################################################print(results.states[-1])print("\n")final_state_evolved=results.states[-1]print(CC2Pi()*psi1)
################## Time Evolution #######################results=sesolve(H_tot,tensor(qeye(2),qeye(2),qeye(2)),times)############################################################## here by feeding sesolve a unitary (the identity matrix) it solves the Schrodinger operator equation to find# the unitary that describes the full time evolution under our Hamiltonian for the given times vectorresults.states[-1].tidyup(1e-1)final_unitary=results.states[-1]# final_unitary.tidyup(1e-1) # you can print this matrix out, which will give you the following
Next, assume that we drive the system with a frequency fixed at the center of the 4 transition frequencies with complete control of the amplitude and phase of the applied microwave field for arbitrary slices of time. Formulate the Hamiltonian in the optimal control framework as \(\hat{H}_{\text{total}} = \hat{H}_{\text{drift}} + \sum_i \hat{H}_{i, \text{control}}\).
############ New detuning based on new pulse position between 4 resonances###################det100=-E1/2-E2det101=-E1/2det110=E1/2det111=E1/2+E2######################################################################################## Drift hamiltonian #######################################H_d=(det100*population_100+det101*population_101+det110*population_110+det111*population_111)################################################################################################## Control hamiltonian ############################## Pulse phase will change relative amplitude of sigma_x and sigma_yH_int_y=(omega/2*(1j*transition1-1j*transition1.dag()+1j*transition2-1j*transition2.dag()+1j*transition3-1j*transition3.dag()+1j*transition4-1j*transition4.dag()))H_c=[H_int_x,H_int_y]##################################################################################################### Unitaries ######################################## start point for the gate evolutionU_0=tensor(qeye(2),qeye(2),qeye(2))# target unitaryU_targ=tensor(qeye(2),cphase(np.pi))# same as CC2Pi() but different coding structure############################################################################
Use quantum optimal control to optimize for a unitary which is closest to the desired CC-\(2\pi\) gate using the same total timescale as the resonant gate implemented in part a.
#################### optimization parameters ################################ Number of time slotsn_ts=30# Time allowed for the evolutionevo_time=1*tevol# Fidelity error targetfid_err_targ=1e-15# Maximum iterations for the optisation algorithmmax_iter=2600# Maximum (elapsed) time allowed in secondsmax_wall_time=120# Minimum gradient (sum of gradients squared)# as this tends to 0 -> local minima has been foundmin_grad=1e-28# pulse type alternatives: RND|ZERO|LIN|SINE|SQUARE|SAW|TRIANGLE|p_type="RND"####################################################################################################### optimization ##################################result=cpo.optimize_pulse_unitary(H_d,H_c,U_0,CC2Pi(),n_ts,evo_time,fid_err_targ=fid_err_targ,min_grad=min_grad,max_iter=max_iter,max_wall_time=max_wall_time,out_file_ext=None,init_pulse_type=p_type,pulse_scaling=1,log_level=log_level,gen_stats=True,optim_method="LBFGSB",)################################################################################################ Result reporting ######################################result.stats.report()print("Final evolution\n{}\n".format(result.evo_full_final))print("********* Summary *****************")print("Final fidelity error {}".format(result.fid_err))print("Final gradient normal {}".format(result.grad_norm_final))print("Terminated due to {}".format(result.termination_reason))print("Number of iterations {}".format(result.num_iter))print("Completed in {} HH:MM:SS.US".format(datetime.timedelta(seconds=result.wall_time)))
####### Plot the control pulse sequence ################labels=["u_x","u_y"]fig1=plt.figure()ax1=fig1.add_subplot(2,1,1)ax1.set_title("Initial control amps")# ax1.set_xlabel("Time")ax1.set_ylabel("Control amplitude")forkinrange(len(H_c)):ax1.step(result.time,np.hstack((result.initial_amps[:,k],result.initial_amps[-1,k])),where="post",label=labels[k],)ax1.legend()ax2=fig1.add_subplot(2,1,2)ax2.set_title("Optimised Control Sequences")ax2.set_xlabel("Time")ax2.set_ylabel("Control amplitude")forkinrange(len(H_c)):ax2.step(result.time,np.hstack((result.final_amps[:,k],result.final_amps[-1,k])),where="post",label=labels[k],)ax2.legend()plt.tight_layout()plt.show()
###################### Checking the final unitary ###############print("Target unitary")print(U_targ)print("\n")print("optimized unitary")# 1j * result.evo_full_final.tidyup(1.0e-1) # you can print this out# Here I am multiplying an overall phase to the unitary to make the comparison clearer#################################################################
Compare the errors (infidelities) between the resonant pulse and the optimized off-resonant gate with respect to the ideal CC-\(2\pi\) gate using the same unitary matrix fidelity metric as QuTiP’s optimize_pulse_unitary() command: \(\epsilon = 1 - \frac{1}{d} \lvert \text{Tr}(\hat{U}^\dagger_{\text{target}} \hat{U}_{\text{actual}}) \rvert\)
Apply the resonant pulse as well as the optimized off-resonant pulse to each of the four ground basis states as well as their equal superposition: \(\ket{000}, \ket{001}, \ket{010}, \ket{011}\), and \(\frac{1}{2} (\ket{000} + \ket{001} + \ket{010} + \ket{011})\). Compare the fidelities of the output states with respect to the output states of the ideal CC-\(2\pi\) gate using the fidelity() command.
print("First, calculate the infidelity using the same metric as optimize_pulse_unitary() based on unitaries:")print("\n")print("Error for Resonant Pulse = "+str(1-np.abs((CC2Pi().dag()*final_unitary).tr())/8))print("Error for Optimized Pulse = "+str(1-np.abs((CC2Pi().dag()*result.evo_full_final).tr())/8))print("\n")print("Next, calculate the infidelity between final states after the two different unitaries act on each of the 4 input ground states and a final case where we input an equal superposition of the 4 ground states:")print("\n")psis=[tensor(basis(2,0),basis(2,0),basis(2,0)),tensor(basis(2,0),basis(2,0),basis(2,1)),tensor(basis(2,0),basis(2,1),basis(2,0)),tensor(basis(2,0),basis(2,1),basis(2,1)),psi1,]i=0forinputstateinpsis:ifi==0:print("Input State = |000>:")ifi==1:print("Input State = |001>:")ifi==2:print("Input State = |010>:")ifi==3:print("Input State = |011>:")ifi==4:print("Input State = 1/2*(|000>+|001>+|010>+|011>):")print("Error for Resonant Pulse = "+str(1-fidelity(final_unitary*inputstate,CC2Pi()*inputstate)))print("Error for Optimized Pulse = "+str(1-fidelity(result.evo_full_final*inputstate,CC2Pi()*inputstate)))print("\n")i+=1
First, calculate the infidelity using the same metric as optimize_pulse_unitary() based on unitaries:
Error for Resonant Pulse = 0.2396487936963544
Error for Optimized Pulse = 0.05716482860058514
Next, calculate the infidelity between final states after the two different unitaries act on each of the 4 input ground states and a final case where we input an equal superposition of the 4 ground states:
Input State = |000>:
Error for Resonant Pulse = 0.002535332931576262
Error for Optimized Pulse = 0.007095959107194738
Input State = |001>:
Error for Resonant Pulse = 0.013884581607307878
Error for Optimized Pulse = 0.009100306977431805
Input State = |010>:
Error for Resonant Pulse = 0.2427419387278329
Error for Optimized Pulse = 0.0005437003355196479
Input State = |011>:
Error for Resonant Pulse = 1.2555989581386484e-10
Error for Optimized Pulse = 0.0010333473988436426
Input State = 1/2*(|000>+|001>+|010>+|011>):
Error for Resonant Pulse = 0.14610099672315868
Error for Optimized Pulse = 0.03776277596227273
Use quantum optimal control along with your understanding of the system to design an even better CC-\(2\pi\) gate in the same total time by for example adjusting the drive frequency, incorporating multiple drive frequencies, etc. Demonstrate that your gate is in fact better using the error metrics in previous parts.
Don’t be afraid! You are still dealing with some 2-level systems effectively. In this problem, we will try to go through some very recent proposal to achieve robust CZ gate in neutral atoms with the help of Rydberg interactions. They try to use composite pulse techniques to fight against quasi-static errors including amplitude and detuning errors. The paper is:
Fromonteil et al. Protocols for Rydberg entangling gates featuring robustness against quasi-static errors, arXiv:2210.08824
Now you need to use 3 levels \(\{\ket{0}, \ket{1}, \ket{r} \}\) to model each atom, where \(\ket{0}\) and \(\ket{1}\) form the computational basis while \(\ket{r}\) is the Rydberg level. You can use global laser field to drive two atoms between \(\ket{1}\) and \(\ket{r}\) on-resonance together (since local addressing is a little difficult).
The Hamiltonian in the rotating frame is written as Eq. (1) in the paper. In the strong blockade limit \(V \to +\infty\), you can only consider Hamiltonian confined in 8 dimensions \(H_B = P_B H P_B\), where \(P_B = I \otimes I - \ket{r,r}\bra{r,r}\) is the projection operator.
Please write down matrix representation of \(H_B\) under the basis \(\{ \ket{00}, \ket{01}, \ket{0r}, \ket{10}, \ket{r0}, \ket{11}, \ket{W}, \ket{A} \}\) with \(\ket{W} = \frac{1}{\sqrt{2}}(\ket{1r} + \ket{r1})\) and \(\ket{A} = \frac{1}{\sqrt{2}}(\ket{1r} - \ket{r1})\).
Briefly explain why it is sufficient to use knowledge from 2-level systems to calculate the dynamics of this system.
We note that in the paper the Hamiltonian is written as
### Define the HamiltoniandefH(omegax,omegay):omega=omegax-1j*omegayH1=omega*(basis(8,2)*basis(8,1).dag()+basis(8,4)*basis(8,3).dag()+np.sqrt(2)*basis(8,6)*basis(8,5).dag())H=(H1+H1.dag())/2returnH
Then we want to build up entanglement following FIG. 1 (Eq. (2) in paper). Suppose we initialize our state in \(\ket{\psi_i} = \ket{+X} \ket{+X}\) where \(\ket{+X} = \frac{1}{\sqrt{2}}(\ket{0} + \ket{1})\).
For the ideal case \(\epsilon = 0\), what is the final state \(\ket{\psi_f(0)}\)? Verify you can get a Bell state from \(\ket{\psi_f(0)}\) with only single-qubit operations.
The state is \((|0,0\rangle - |0,1\rangle - |1,0\rangle - |1,1\rangle) / 2 = [|0\rangle(|0\rangle-|1\rangle) - |1\rangle(|0\rangle+|1\rangle)] / 2\). We can do a Hadamard gate on the 2nd qubit to achieve \((|0,1\rangle - |1,0\rangle) / \sqrt{2}\), which is a Bell state.
### 2.3 Show on the Bloch sphere# First you need to represent effective sigma_x,y,z operators using 8 demensional basissigax=basis(8,1)*basis(8,2).dag()+basis(8,2)*basis(8,1).dag()sigay=1j*basis(8,1)*basis(8,2).dag()-1j*basis(8,2)*basis(8,1).dag()sigaz=basis(8,2)*basis(8,2).dag()-basis(8,1)*basis(8,1).dag()sigbx=basis(8,5)*basis(8,6).dag()+basis(8,6)*basis(8,5).dag()sigby=1j*basis(8,5)*basis(8,6).dag()-1j*basis(8,6)*basis(8,5).dag()sigbz=basis(8,6)*basis(8,6).dag()-basis(8,5)*basis(8,5).dag()
For the noisy case, can you plot the state preparation fidelity \(F(\epsilon) = \left| \braket{\psi_f(0)}{\psi_f(\epsilon)} \right|^2\) as \(\epsilon\) varies? In the \(\epsilon \ll 1\) regime, we have \((1 - F) \propto \epsilon^n\), can you determine what \(n\) is?
The authors claimed that their gate has “conditional robustness”, that is, we can improve the fidelity by performing measurement and keep the part which lies in the computational subspace \(\{\ket{0}, \ket{1} \}^{\otimes 2}\). This means finally we have
### 2.5 Fidelity after post-selectionproj=(ket2dm(basis(8,0))+ket2dm(basis(8,1))+ket2dm(basis(8,3))+ket2dm(basis(8,5)))fid_2=[expect(ket2dm(psi_fnl),proj*U(eps)*psi_ini)/expect(qeye(8),proj*U(eps)*psi_ini)forepsineps_list]
### Use the log-log plot to estimate the scaling, which is shown as the slope### Let's pick some points to calculate thatplt.loglog(eps_list,1-np.array(fid),label="no post-selection")plt.loglog(eps_list,1-np.array(fid_2),label="post-selection")plt.scatter(eps_list[2],1-np.array(fid[2]),c="C0")plt.scatter(eps_list[20],1-np.array(fid[20]),c="C0")plt.scatter(eps_list[2],1-np.array(fid_2[2]),c="C1")plt.scatter(eps_list[20],1-np.array(fid_2[20]),c="C1")plt.xlim(2e-3,2e-1)plt.xlabel(r"$\epsilon$")plt.ylabel("Infidelity")plt.legend()plt.show()
In Sec. IV.A.2 the authors propose another sequence to achieve “fully robust protocol” by applying two controlled-\((\frac{\pi}{2})\) gates sequentially (FIG. 2). Please first intuitively explain why the generalization shown in FIG. 2 gives controlled-\((\frac{\pi}{2})\) gates and can be further generalized to controlled-\((\theta)\) gate with arbitrary \(\theta\).
Then repeat what you did in part (d) with the “fully robust” CZ gate suffering from amplitude error \(\epsilon\). Specifically, how does