Correlation function

In this tutorial we will compute the coherence function of the NV Center in diamond and then reproduce it from the correlation function of the noise.

The correlation function \(C(t)\) of the effective magnetic field (noise) along the \(z\)-axis can be defined as follows: \begin{equation} C(t) = \left\langle \beta_z(t)\beta_z(0) \right\rangle \end{equation}

With \(\beta_z\) given as:

\begin{equation} \beta_z(t) = U^{\dagger}(t) \left( \sum_{\{I\}}{A_{zz} I_z} \right) U(t) \end{equation}

Where \(U(t)\) is time propagator.

Within the CCE formalism, the correlation function is computed as:

\begin{equation} C(t) = \sum_{\{i\}} {\tilde C_{\{i\}}(t)} + \sum_{\{ij\}} {\tilde C_{\{ij\}}(t)}\ + \ ... \end{equation}

With contributions computed as:

\begin{equation} \tilde C_{\nu}(t) = C_{\nu}(t) - \sum_{\nu' \subset\ \nu} {\tilde C_{\nu'}(t)} \end{equation}


import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

import sys

import pycce as pc
import ase

seed = 42055
np.set_printoptions(suppress=True, precision=5)

Generate nuclear spin bath

Building a BathArray of nuclear spins from the ase.Atoms object.


from ase.build import bulk

# Generate unitcell from ase
diamond = bulk('C', 'diamond', cubic=True)
diamond = pc.bath.BathCell.from_ase(diamond)
# Add types of isotopes
diamond.add_isotopes(('13C', 0.011))
# set z direction of the defect
diamond.zdir = [1, 1, 1]
# Add the defect. remove and add atoms at the positions (in cell coordinates)
atoms = diamond.gen_supercell(200, remove=[('C', [0., 0, 0]),
                                           ('C', [0.5, 0.5, 0.5])],
                              add=('14N', [0.5, 0.5, 0.5]),
                              seed=seed)

Next, we define all of the parameters of the simulation. We are interested in the very specific regime, when all nearby nuclear spins are removed. To achieve this goal we define an inner = 20 parameter, and remove all nuclear spins within this radius.


position = np.array([0, 0, 0])
inner = 20
smallatoms = atoms[atoms.dist(position) >= inner]

parameters = dict(
    order=2, # CCE order
    r_bath=60,  # Size of the bath in A
    r_dipole=6,  # Cutoff of pairwise clusters in A
    position=position, # Position of central Spin
    alpha=[0, 0, 1], # 0 qubit state
    beta=[0, 1, 0], # 1 qubit state
    magnetic_field = 500, # magnetic field along z-axis
    pulses=1 # N pulses in CPMG sequence
) # Qubit levels

ts = np.linspace(0, 2.5, 1001)  # Time points in ms

Coherence calculations

Next, we set up Simulator objects and check convergence with respect to the CCE order.


calc = pc.Simulator(spin=1, bath=smallatoms, **parameters)

orders = [2, 3, 4]
coh = {}
for o in orders:
    calc.generate_clusters(o)
    coh[o] = calc.compute(ts, method='cce', quantity='coherence')
coh = np.abs(pd.DataFrame(coh, index=ts))
coh.index.name = 'Time (ms)'

Visually verify the convergence.


coh.plot()
plt.ylabel('Coherence');
../_images/tutorials_classical_noise_11_0.png

Noise calculations

To compute the correlation function of the noise, we call Simulator.compute method and specify quantity = 'noise'.

First we determine convergence of the correlation function with the CCE order.


for o in [1, 2, 3, 4]:
    calc.generate_clusters(o)
    noise = calc.compute(ts, method='cce', quantity='noise')
    plt.plot(ts, noise.real, label=o)
plt.xlabel('Time (ms)')
plt.ylabel('Correlation');
../_images/tutorials_classical_noise_13_0.png

The difference between third and fourth order is fairly small, we will use the fourth order for the following calculations. It will take a bit of a time, so you can grab some tea while you wait.


calc.generate_clusters(4)

noise = calc.compute(ts, method='cce', quantity='noise')
genoise = calc.compute(ts, method='gcce', quantity='noise', nbstates=0)

Compare the results obtained with CCE and gCCE approaches. Note that they are slighlity different. However, as we will see it does not impact the predicted coherence.


plt.plot(ts, noise.real, label='CCE')
plt.plot(ts, genoise.real, label='gCCE')
plt.xlabel('Time (ms)')
plt.ylabel('Correlation');
../_images/tutorials_classical_noise_17_0.png

Assuming that the noise is Gaussian, we can reproduce the coherence from the average phase squared \(\langle\phi^2\rangle\), accumulated by the spin qubit:

\(L(t)=e^{-\langle \phi^2(t) \rangle}\)

The average phase is obtained from the autocorrelation function as:

\(\langle \phi^2(t) \rangle = \int_0^t{d\tau C(\tau) F(\tau)}\)

Where \(F(\tau)\) is the correlation filter function (see Phys. Rev. A 86, 012314 (2012) for details).

PyCCE code already has implemented calculations of the phase in the pycce.filter module:

pycce.filter.gaussian_phase takes three positional arguments: - timespace - time points at which correlation function was computed; - corr - noise autocorrelation function; - npulses - number of pulses in CPMG sequence.

Here we compute the phase for the Hahn-echo experiment. Note that the implementation of gaussian_phase is not heavily optimized and can take a hot second.


import pycce.filter

chis =  pycce.filter.gaussian_phase(ts, np.abs(noise), 1)
gchis = pycce.filter.gaussian_phase(ts, np.abs(genoise), 1)

Now compare results from direct calculations of the coherence function, and the one reconstructed from the noise autocorrelation:


plt.plot(ts, np.exp(-chis).real, ls='--', label='from noise (CCE)')
plt.plot(ts, np.exp(-gchis).real, ls='--', marker='', label='from noise (generalized CCE)')
plt.plot(ts, coh[4], label='CCE')
plt.legend()
plt.xlabel('Time (ms)')
plt.ylabel('Coherence');
../_images/tutorials_classical_noise_21_0.png