In this example we simulate ambiguous resonances in multipulse quantum sensing with nitrogen vacancy (NV) centers in diamonds. This notebook closely follows the work from “Ambiguous Resonances in Multipulse Quantum Sensing with Nitrogen Vacancy Centers” available at:

    1. Tsunaki, A. Singh, K. Volkova, S. Trofimov, T. Pregnolato, T. Schröder, & B. Naydenov. (2025). Ambiguous resonances in multipulse quantum sensing with nitrogen-vacancy centers. Physical Review A, 111(2), 022606. doi: 10.1103/PhysRevA.111.022606.

As this is a quite complex problem, physics discussions will be kept to a minimum here in favor of a focus to the simulation. For a detailed explanation of the problem, please refer to the original paper.

[1]:
import numpy as np
from qutip import tensor, jmat, qeye

from quaccatoo import NV, XY8, Analysis

1. Field Misalignment and \(^{15}\text{N}\) Coupling

We begin simulating a XY8-2 sequence with a misaligned magnetic field, as in Sec. IV A of the paper. Initially, we define the NV system with the experimental parameters of the paper and the XY8 pulse sequence.

[2]:
qsys_1 = NV(
    N=15,
    B0 = 39.4,
    units_B0='mT',
    theta=2.6,
    units_angles='deg'
)

w1 = 20

XY8_2_15N  = XY8(
    free_duration = np.linspace(.25, .36, 100),
    system = qsys_1,
    M=2,
    pi_pulse_duration = 1/2/w1,
    h1 = w1*qsys_1.MW_h1,
    # MW frequency for the ms=0 --> ms=+1 state transition
    pulse_params = {'f_pulse': qsys_1.MW_freqs[1]},
    # Number of time steps in each MW pulse
    time_steps=100,
)

XY8_2_15N.plot_pulses(tau=.1, figsize=(12, 4))
../_images/tutorials_05_NV_ambiguous_resonances_4_0.png

This leads to a \(\pi\)-pulse duration shorter than \(1/(2\omega_1)\), resulting from a hyperfine enhancement effect.

Given the defined system with the experimental parameters, the simplest dynamical decoupling sequence for suppressing the noise from the environment is the Hahn echo technique. The sequence is composed by two free evolutions of duration \(\tau\), with an intermediate \(\pi\)-pulse to the electron spin. In addition, \(\pi/2\)-pulses are included before and after the sequence to drive the electron spin from and to the quantization axis. In NMR, the last projective pulse is not required though, as the measurement is performed perpendicular to the quantization axis. The intermediate \(\pi\)-pulse causes a refocusing of static or slow dephasings, such as those arising from the 14N nuclear spin. This allows the coupled 13C nuclear spin to be probed by the NV.

For performing a Hahn echo simulation in the software component, we use the Hahn class:

Here we can observe the 16 \(\pi\)-pulses plus the initial and final \(\pi/2\) pulses. Now to run the experiment as in Fig. 3 (d):

[3]:
XY8_2_15N.run()
Analysis(XY8_2_15N).plot_results(title=r'XY8-2 $B_0=39$ mT $\theta_0=2.6^\circ$')
../_images/tutorials_05_NV_ambiguous_resonances_7_0.png

Increasing the XY8 order. First for \(M=4\):

[4]:
XY8_4_15N  = XY8(
    free_duration = np.linspace(.25, .36, 100),
    system = qsys_1,
    M=4,
    pi_pulse_duration = 1/2/w1,
    h1 = w1*qsys_1.MW_h1,
    pulse_params = {'f_pulse': qsys_1.MW_freqs[1]},
)

XY8_4_15N.run()
Analysis(XY8_4_15N).plot_results(title=r'XY8-4 $B_0=39$ mT $\theta_0=2.6^\circ$')
../_images/tutorials_05_NV_ambiguous_resonances_9_0.png

\(M=6\):

[5]:
XY8_6_15N  = XY8(
    free_duration = np.linspace(.25, .36, 100),
    system = qsys_1,
    M=6,
    pi_pulse_duration = 1/2/w1,
    h1 = w1*qsys_1.MW_h1,
    pulse_params = {'f_pulse': qsys_1.MW_freqs[1]},
)

XY8_6_15N.run()
Analysis(XY8_6_15N).plot_results(title=r'XY8-6 $B_0=39$ mT $\theta_0=2.6^\circ$')
../_images/tutorials_05_NV_ambiguous_resonances_11_0.png

\(M=8\):

[6]:
XY8_8_15N  = XY8(
    free_duration = np.linspace(.25, .36, 100),
    system = qsys_1,
    M=8,
    pi_pulse_duration = 1/2/w1,
    h1 = w1*qsys_1.MW_h1,
    pulse_params = {'f_pulse': qsys_1.MW_freqs[1]},
)

XY8_8_15N.run()
Analysis(XY8_8_15N).plot_results(title=r'XY8-8 $B_0=39$ mT $\theta_0=2.6^\circ$')
../_images/tutorials_05_NV_ambiguous_resonances_13_0.png

Now, changing the field for XY8-2 as in Fig. 3 (c). First, \(B_0=33\) mT and \(\theta_0 = 5.1^\circ\):

[7]:
qsys_2 = NV(
    N=15,
    B0 = 33,
    units_B0='mT',
    theta=5.1,
    units_angles='deg'
)

XY8_15N_B1  = XY8(
    free_duration = np.linspace(.22, .45, 100),
    system = qsys_2,
    M=2,
    pi_pulse_duration = 1/2/w1,
    h1 = w1*qsys_2.MW_h1,
    pulse_params = {'f_pulse': qsys_2.MW_freqs[1]}
)

XY8_15N_B1.run()
Analysis(XY8_15N_B1).plot_results(title=r'XY8-2 $B_0=33$ mT $\theta_0=5.1^\circ$')
../_images/tutorials_05_NV_ambiguous_resonances_15_0.png

\(B_0=26\) mT and \(\theta_0 = 9.3^\circ\):

[8]:
qsys_3 = NV(
    N=15,
    B0 = 26,
    units_B0='mT',
    theta = 9.3,
    units_angles='deg'
)

XY8_15N_B2  = XY8(
    free_duration = np.linspace(.22, .45, 100),
    system = qsys_3,
    M=2,
    pi_pulse_duration = 1/2/w1,
    h1 = w1*qsys_3.MW_h1,
    pulse_params = {'f_pulse': qsys_3.MW_freqs[1]}
)

XY8_15N_B2.run()
Analysis(XY8_15N_B2).plot_results(title=r'XY8-2 $B_0=26$ mT $\theta_0=9.3^\circ$')
../_images/tutorials_05_NV_ambiguous_resonances_17_0.png

\(B_0=17\) mT and \(\theta_0 = 17^\circ\):

[9]:
qsys_4 = NV(
    N=15,
    B0 = 17,
    units_B0='mT',
    theta=17,
    units_angles='deg'
)

XY8_15N_B3  = XY8(
    free_duration = np.linspace(.25, .45, 100),
    system = qsys_4,
    M=2,
    pi_pulse_duration = 1/2/w1,
    h1 = w1*qsys_4.MW_h1,
    pulse_params = {'f_pulse': qsys_4.MW_freqs[1]}
)

XY8_15N_B3.run()
Analysis(XY8_15N_B3).plot_results(title=r'XY8-2 $B_0=17$ mT $\theta_0=17^\circ$')
../_images/tutorials_05_NV_ambiguous_resonances_19_0.png

If we now include a pulse length error, as in Fig. 6, we have:

[10]:
XY8_15N_B3_errors  = XY8(
    free_duration = np.linspace(.25, .45, 100),
    system = qsys_4,
    M=2,
    pi_pulse_duration = 1/2/w1*.8,
    h1 = w1*qsys_4.MW_h1,
    pulse_params = {'f_pulse': qsys_4.MW_freqs[1]}
)

XY8_15N_B3_errors.run()
Analysis(XY8_15N_B3_errors).plot_results(title=r'XY8-2 $B_0=17$ mT $\theta_0=17^\circ$, pulse lenght=0.8 $t_{\pi}$')
../_images/tutorials_05_NV_ambiguous_resonances_21_0.png

2. 13C Coupling

As in Sec. IV B, we need to define the coupling Hamiltonian \(\hat{H}_2\) in the same way as Eq. 3 of the paper. We begin defining the coupling tensor shown in Appendix D and the Zeeman Hamiltonian.

[11]:
A = np.empty((3, 3), dtype=object)
A[0, 0] = -0.25
A[0, 1] = -1.85
A[0, 2] = -.49
A[1, 0] = A[0, 1]
A[1, 1] = 0
A[1, 2] = .01
A[2, 0] = A[0, 2]
A[2, 1] = A[1, 2]
A[2, 2] = 1.01

HhfC = (
    A[0,0]*tensor(jmat(1,'x'), qeye(3), jmat(1/2,'x')) + A[0,1]*tensor(jmat(1,'x'), qeye(3), jmat(1/2,'y')) + A[0,2]*tensor(jmat(1,'x'), qeye(3), jmat(1/2,'z'))
    + A[1,0]*tensor(jmat(1,'y'), qeye(3), jmat(1/2,'x')) + A[1,1]*tensor(jmat(1,'y'), qeye(3),jmat(1/2,'y')) + A[1,2]*tensor(jmat(1,'y'), qeye(3), jmat(1/2,'z'))
    + A[2,0]*tensor(jmat(1,'z'), qeye(3),  jmat(1/2,'x')) + A[2,1]*tensor(jmat(1,'z'), qeye(3), jmat(1/2,'y')) + A[2,2]*tensor(jmat(1,'z'), qeye(3), jmat(1/2,'z'))
    )

def Hzc(B0, theta):
    return 10.705e-3*B0*tensor(qeye(3), qeye(3), np.cos(theta*np.pi/180)*jmat(1/2,'z') + np.sin(theta*np.pi/180)*jmat(1/2,'x'))

Now we define the NV Hamiltonian and use the add_spin method to introduce the 13C nuclear spin.

[12]:
qsys_5 = NV(
    N=14,
    B0 = 18,
    units_B0='mT',
    theta=2.34,
    units_angles='deg',
)

qsys_5.add_spin(HhfC + Hzc(qsys_5.B0, qsys_5.theta))

XY8_13C_B1  = XY8(
    free_duration = np.linspace(.4, .85, 100),
    system = qsys_5,
    M=2,
    pi_pulse_duration = 1/2/w1,
    h1 = w1*qsys_5.MW_h1,
    pulse_params = {'f_pulse': qsys_5.MW_freqs[1]}
)

XY8_13C_B1.run()
Analysis(XY8_13C_B1).plot_results(title=r'XY8-2 $^{13}$C, $B_0=18$ mT, $\theta_0=2.34^\circ$')
../_images/tutorials_05_NV_ambiguous_resonances_26_0.png

Increasing the field as in Fig. 4 (a) for \(B_0=31\) mT and \(\theta_0 = 1.35^\circ\):

[13]:
qsys_6 = NV(
    N=14,
    B0 = 31,
    units_B0='mT',
    theta=1.35,
    units_angles='deg',
)

qsys_6.add_spin(HhfC + Hzc(qsys_6.B0, qsys_6.theta))

XY8_13C_B2  = XY8(
    free_duration = np.linspace(.4, .85, 100),
    system = qsys_6,
    M=2,
    pi_pulse_duration = 1/2/w1,
    h1 = w1*qsys_6.MW_h1,
    pulse_params = {'f_pulse': qsys_6.MW_freqs[1]}
)

XY8_13C_B2.run()
Analysis(XY8_13C_B2).plot_results(title=r'XY8-2 $^{13}$C, $B_0=31$ mT, $\theta_0=1.35^\circ$')
../_images/tutorials_05_NV_ambiguous_resonances_28_0.png

\(B_0=40\) mT and \(\theta_0 = 1.14^\circ\):

[14]:
qsys_7 = NV(
    N=14,
    B0 = 40,
    units_B0='mT',
    theta=1.14,
    units_angles='deg',
)

qsys_7.add_spin(HhfC + Hzc(qsys_7.B0, qsys_7.theta))

XY8_13C_B3  = XY8(
    free_duration = np.linspace(.4, .85, 100),
    system = qsys_7,
    M=2,
    pi_pulse_duration = 1/2/w1,
    h1 = w1*qsys_7.MW_h1,
    pulse_params = {'f_pulse': qsys_7.MW_freqs[1]}
)

XY8_13C_B3.run()
Analysis(XY8_13C_B3).plot_results(title=r'XY8-2 $^{13}$C, $B_0=40$ mT, $\theta_0=1.14^\circ$')
../_images/tutorials_05_NV_ambiguous_resonances_30_0.png

Now, we keep the field constant and increase the order, as in Fig. 4(b). \(M=4\):

[15]:
XY8_4_13C  = XY8(
    free_duration = np.linspace(.4, .85, 100),
    system = qsys_7,
    M=4,
    pi_pulse_duration = 1/2/w1,
    h1 = w1*qsys_7.MW_h1,
    pulse_params = {'f_pulse': qsys_7.MW_freqs[1]}
)

XY8_4_13C.run()
Analysis(XY8_4_13C).plot_results(title=r'XY8-4 $^{13}$C, $B_0=40$ mT, $\theta_0=1.14^\circ$')
../_images/tutorials_05_NV_ambiguous_resonances_32_0.png

\(M=6\):

[16]:
XY8_6_13C  = XY8(
    free_duration = np.linspace(.4, .85, 100),
    system = qsys_7,
    M=6,
    pi_pulse_duration = 1/2/w1,
    h1 = w1*qsys_7.MW_h1,
    pulse_params = {'f_pulse': qsys_7.MW_freqs[1]}
)

XY8_6_13C.run()
Analysis(XY8_6_13C).plot_results(title=r'XY8-6 $^{13}$C, $B_0=40$ mT, $\theta_0=1.14^\circ$')
../_images/tutorials_05_NV_ambiguous_resonances_34_0.png

\(M=8\):

[17]:
XY8_8_13C  = XY8(
    free_duration = np.linspace(.4, .85, 100),
    system = qsys_7,
    M=8,
    pi_pulse_duration = 1/2/w1,
    h1 = w1*qsys_7.MW_h1,
    pulse_params = {'f_pulse': qsys_7.MW_freqs[1]}
)

XY8_8_13C.run()
Analysis(XY8_8_13C).plot_results(title=r'XY8-8 $^{13}$C, $B_0=40$ mT, $\theta_0=1.14^\circ$')
../_images/tutorials_05_NV_ambiguous_resonances_36_0.png

3. RXY8 with H2

As discussed in Sec. IV C, the finite length of the pulses can lead to ambiguous resonances. Here, we define the NV Hamiltonian as usual and a time-dependent magnetic field \(B_2(t)\) as in Eq. 4.

[2]:
qsys_8 = NV(
    N=15,
    B0 = 40,
    units_B0='mT'
)

w1 = 20
f2 = 5.5
B2z = 0.3

def B2(t, B2z=B2z, f2=f2):
    return B2z*np.sin(f2*t)

H2 = [tensor(jmat(1,'z'), qeye(2)), B2]

XY8_12_H2  = XY8(
    free_duration = np.linspace(.06, 0.17, 100),
    system = qsys_8,
    M=12,
    pi_pulse_duration = 1/2/w1,
    h1 = w1*qsys_8.MW_h1,
    pulse_params = {'f_pulse': qsys_8.MW_freqs[0]},
    H2 = H2
)

# the number of CPUs for the parallel computation can be specified with the num_cpus keyword argument
XY8_12_H2.run()
Analysis(XY8_12_H2).plot_results()
../_images/tutorials_05_NV_ambiguous_resonances_39_0.png

To show that these resonances are indeed from the finite pulse duration, we now simulate it with delta pulses.

[3]:
XY8_12_H2_delta  = XY8(
    free_duration = np.linspace(.06, 0.17, 100),
    system = qsys_8,
    M=12,
    pi_pulse_duration = 0,
    Rx = qsys_8.MW_Rx[0],
    Ry = qsys_8.MW_Ry[0],
    H2 = H2
)

XY8_12_H2_delta.run()
Analysis(XY8_12_H2_delta).plot_results()
../_images/tutorials_05_NV_ambiguous_resonances_41_0.png

These ambiguous resonances can be supressed with phase randomization, for that we use the XY8 class with the RXY8 parameter set to True.

[4]:
RXY8_12_H2  = XY8(
    free_duration = np.linspace(.06, 0.17, 100),
    system = qsys_8,
    M=12,
    pi_pulse_duration = 1/2/w1,
    h1 = w1*qsys_8.MW_h1,
    pulse_params = {'f_pulse': qsys_8.MW_freqs[0]},
    H2 = H2,
    RXY8=True
)

RXY8_12_H2.run()
Analysis(RXY8_12_H2).plot_results()
../_images/tutorials_05_NV_ambiguous_resonances_43_0.png