vacspin 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vacspin/__init__.py +47 -0
- vacspin/cavity.py +174 -0
- vacspin/hamiltonian.py +132 -0
- vacspin/params.py +188 -0
- vacspin/readout.py +275 -0
- vacspin/remote.py +43 -0
- vacspin/transitions.py +115 -0
- vacspin-0.1.0.dist-info/METADATA +199 -0
- vacspin-0.1.0.dist-info/RECORD +12 -0
- vacspin-0.1.0.dist-info/WHEEL +5 -0
- vacspin-0.1.0.dist-info/licenses/LICENSE +202 -0
- vacspin-0.1.0.dist-info/top_level.txt +1 -0
vacspin/__init__.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""vacspin: group-IV colour-centre spin-photon interfaces, end to end.
|
|
2
|
+
|
|
3
|
+
From the strained spin Hamiltonian of a vacancy centre in diamond
|
|
4
|
+
(SiV-, GeV-, SnV-, PbV-), through its optical transitions and
|
|
5
|
+
cyclicity, the transition-resolved Purcell budget of a coupled
|
|
6
|
+
cavity, to exact single-shot readout statistics and remote-
|
|
7
|
+
entanglement rates -- with every physics claim in the test suite
|
|
8
|
+
pinned to a closed form, a measured anchor, or two independent code
|
|
9
|
+
paths.
|
|
10
|
+
|
|
11
|
+
Methodological basis: T. M. Mahim, M. M. Rahman and A. S. M. Mohsin,
|
|
12
|
+
"Fast single-shot readout of tin-vacancy spins with an overcoupled
|
|
13
|
+
diamond nanocavity on thin-film lithium niobate" (submitted to Optics
|
|
14
|
+
Express, 2026), and the effective-Hamiltonian and readout formalism of
|
|
15
|
+
Rosenthal et al., PRX 13, 031022 (2023) and arXiv:2403.13110.
|
|
16
|
+
"""
|
|
17
|
+
from .params import (EmissionBudget, SpinParameters, siv_hepp2014,
|
|
18
|
+
snv_emission, snv_rosenthal2023)
|
|
19
|
+
from .hamiltonian import (GAMMA_GHZ_PER_T, h_manifold, lab_to_spin,
|
|
20
|
+
qubit_frequency, qubit_frequency_perpendicular,
|
|
21
|
+
solve, zero_field_splitting)
|
|
22
|
+
from .transitions import (cyclicity, field_on_circle, rabi_rate,
|
|
23
|
+
transition_strength, transition_table)
|
|
24
|
+
from .cavity import (CavityInterface, lorentzian_suppression,
|
|
25
|
+
purcell_max, xi_pol_overlap)
|
|
26
|
+
from .readout import (fidelity, fidelity_threshold1, geometric_pmf,
|
|
27
|
+
poisson_pmf, polarization_rate, readout_counts,
|
|
28
|
+
required_efficiency, required_window)
|
|
29
|
+
from .remote import barrett_kok_success, entanglement_rate
|
|
30
|
+
|
|
31
|
+
__version__ = "0.1.0"
|
|
32
|
+
__all__ = [
|
|
33
|
+
"SpinParameters", "EmissionBudget", "snv_rosenthal2023",
|
|
34
|
+
"snv_emission", "siv_hepp2014",
|
|
35
|
+
"GAMMA_GHZ_PER_T", "h_manifold", "lab_to_spin", "solve",
|
|
36
|
+
"zero_field_splitting", "qubit_frequency",
|
|
37
|
+
"qubit_frequency_perpendicular",
|
|
38
|
+
"transition_strength", "transition_table", "cyclicity", "rabi_rate",
|
|
39
|
+
"field_on_circle",
|
|
40
|
+
"purcell_max", "lorentzian_suppression", "xi_pol_overlap",
|
|
41
|
+
"CavityInterface",
|
|
42
|
+
"polarization_rate", "readout_counts", "fidelity_threshold1",
|
|
43
|
+
"geometric_pmf", "poisson_pmf", "fidelity", "required_efficiency",
|
|
44
|
+
"required_window",
|
|
45
|
+
"barrett_kok_success", "entanglement_rate",
|
|
46
|
+
"__version__",
|
|
47
|
+
]
|
vacspin/cavity.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""The cavity-coupled interface: transition-resolved Purcell budget.
|
|
2
|
+
|
|
3
|
+
Everything between the spin and the detector, in the bad-cavity
|
|
4
|
+
(Purcell) regime, following the transition-resolved conventions of
|
|
5
|
+
Lee et al., arXiv:2511.05740 (Eqs. 1-10) as used by Mahim et al.
|
|
6
|
+
(Optics Express, 2026) for the SnV-on-TFLN interface:
|
|
7
|
+
|
|
8
|
+
F_max = (3 / 4 pi^2) (lam/n)^3 Q_L / V ideal Purcell factor
|
|
9
|
+
F_C = 1 + (F_max - 1) xi_pol xi_pos the coupled transition
|
|
10
|
+
F_C2 = same, suppressed by the cavity Lorentzian at the qubit
|
|
11
|
+
detuning (the spin-flipping partner)
|
|
12
|
+
zeta = 1 + eta_q eta_DW eta_BR (F_C - 1) lifetime reduction
|
|
13
|
+
beta = eta_q eta_DW eta_BR F_C / zeta photons into the mode
|
|
14
|
+
eta = beta eta_wg T_link eta_chip eta_det detected per decay
|
|
15
|
+
Lambda_cav = Lambda_0 zeta / zeta_flip cavity-boosted cyclicity
|
|
16
|
+
g = (1/2) sqrt(F_C kappa gamma_rad,C) coupling rate, so that
|
|
17
|
+
F_C = 4 g^2 / (kappa gamma_rad,C) holds as an identity
|
|
18
|
+
|
|
19
|
+
with V in units of (lam/n)^3 and kappa = f_cav / Q_L. `xi_pol_overlap`
|
|
20
|
+
gives the polarisation overlap sin^2(theta) (1 + sin 2 psi)/2 of a
|
|
21
|
+
<111> dipole with a linearly polarised cavity mode (2/3 at psi = 45 deg
|
|
22
|
+
for <100>-oriented diamond).
|
|
23
|
+
|
|
24
|
+
Validity is physics, not paperwork, so it is computed rather than
|
|
25
|
+
assumed: the `validity` dict carries the bad-cavity, weak-coupling and
|
|
26
|
+
spin-selectivity conditions, and `require_valid()` refuses -- with the
|
|
27
|
+
failing conditions named -- instead of returning numbers whose
|
|
28
|
+
derivation no longer applies.
|
|
29
|
+
"""
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import numpy as np
|
|
33
|
+
|
|
34
|
+
from .params import EmissionBudget
|
|
35
|
+
|
|
36
|
+
__all__ = ["purcell_max", "lorentzian_suppression", "xi_pol_overlap",
|
|
37
|
+
"CavityInterface"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def purcell_max(q_loaded, v_rel):
|
|
41
|
+
"""Ideal Purcell factor F = (3/4 pi^2) Q/V, V in (lam/n)^3."""
|
|
42
|
+
q, v = float(q_loaded), float(v_rel)
|
|
43
|
+
if q <= 0 or v <= 0:
|
|
44
|
+
raise ValueError("Q and V must be positive")
|
|
45
|
+
return 3.0 / (4.0 * np.pi ** 2) * q / v
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def lorentzian_suppression(delta_hz, f_cav_hz, q_loaded):
|
|
49
|
+
"""Purcell reduction of a transition detuned by delta from the
|
|
50
|
+
cavity: L = 1 / (1 + (2 Q delta / f_cav)^2). Exactly 1 on
|
|
51
|
+
resonance and exactly 1/2 at delta = f_cav / (2 Q) = kappa/2."""
|
|
52
|
+
if f_cav_hz <= 0 or q_loaded <= 0:
|
|
53
|
+
raise ValueError("f_cav_hz and Q must be positive")
|
|
54
|
+
x = 2.0 * float(q_loaded) * float(delta_hz) / float(f_cav_hz)
|
|
55
|
+
return 1.0 / (1.0 + x * x)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def xi_pol_overlap(theta_deg=54.7356, psi_deg=45.0):
|
|
59
|
+
"""Polarisation overlap |mu_hat . e_cav|^2 of a <111> dipole with a
|
|
60
|
+
linearly polarised in-plane cavity mode:
|
|
61
|
+
sin^2(theta) (1 + sin 2 psi) / 2, equal to 2/3 at psi = 45 deg for
|
|
62
|
+
theta = arccos(1/sqrt 3) (a <100>-oriented sample)."""
|
|
63
|
+
th = np.deg2rad(float(theta_deg))
|
|
64
|
+
ps = np.deg2rad(float(psi_deg))
|
|
65
|
+
return float(np.sin(th) ** 2 * (1.0 + np.sin(2.0 * ps)) / 2.0)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class CavityInterface:
|
|
69
|
+
"""All derived quantities of one cavity-coupled colour centre.
|
|
70
|
+
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
q_loaded : loaded quality factor of the cavity.
|
|
74
|
+
v_rel : mode volume in units of (lam/n)^3.
|
|
75
|
+
eta_wg : fraction of cavity decay leaving through the collection
|
|
76
|
+
port (kappa_wg / kappa_total), in [0, 1].
|
|
77
|
+
budget : `EmissionBudget` of the emitter (cited).
|
|
78
|
+
lambda0 : bare (no-cavity) cyclicity of the operating point --
|
|
79
|
+
compute it with `vacspin.cyclicity` from your spin parameters
|
|
80
|
+
and field, or measure it.
|
|
81
|
+
omega_q_hz : qubit frequency (Hz), the detuning of the
|
|
82
|
+
spin-flipping partner transition.
|
|
83
|
+
xi_pol, xi_pos : polarisation and position overlap of the emitter
|
|
84
|
+
with the cavity field, each in [0, 1].
|
|
85
|
+
t_link, eta_chip, eta_det : downstream transfer, routing and
|
|
86
|
+
detector efficiencies, each in [0, 1] (defaults 1: the
|
|
87
|
+
cavity-only budget).
|
|
88
|
+
delta_partner_hz : optional detuning of a competing SAME-spin
|
|
89
|
+
transition (e.g. the D line); when given, its Purcell factor
|
|
90
|
+
F_partner and the selectivity condition kappa < delta are
|
|
91
|
+
reported.
|
|
92
|
+
|
|
93
|
+
Everything is computed in __init__; `validity` holds the regime
|
|
94
|
+
checks and `require_valid()` refuses when any fails.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(self, q_loaded, v_rel, eta_wg, budget: EmissionBudget,
|
|
98
|
+
lambda0, omega_q_hz, xi_pol, xi_pos=1.0,
|
|
99
|
+
t_link=1.0, eta_chip=1.0, eta_det=1.0,
|
|
100
|
+
delta_partner_hz=None):
|
|
101
|
+
for name, v in (("eta_wg", eta_wg), ("xi_pol", xi_pol),
|
|
102
|
+
("xi_pos", xi_pos), ("t_link", t_link),
|
|
103
|
+
("eta_chip", eta_chip), ("eta_det", eta_det)):
|
|
104
|
+
if not (0.0 <= float(v) <= 1.0):
|
|
105
|
+
raise ValueError(f"{name} must be in [0, 1]")
|
|
106
|
+
if lambda0 <= 0 or not np.isfinite(lambda0):
|
|
107
|
+
raise ValueError("lambda0 must be a finite positive "
|
|
108
|
+
"cyclicity (compute or measure it)")
|
|
109
|
+
if omega_q_hz <= 0:
|
|
110
|
+
raise ValueError("omega_q_hz must be positive")
|
|
111
|
+
self.q = float(q_loaded)
|
|
112
|
+
self.v = float(v_rel)
|
|
113
|
+
self.budget = budget
|
|
114
|
+
self.f_cav_hz = 299792458.0 / (budget.zpl_nm * 1e-9)
|
|
115
|
+
self.kappa_hz = self.f_cav_hz / self.q
|
|
116
|
+
self.omega_q_hz = float(omega_q_hz)
|
|
117
|
+
|
|
118
|
+
a = budget.radiative_fraction
|
|
119
|
+
self.f_max = purcell_max(q_loaded, v_rel)
|
|
120
|
+
self.f_c = 1.0 + (self.f_max - 1.0) * xi_pol * xi_pos
|
|
121
|
+
lc2 = lorentzian_suppression(omega_q_hz, self.f_cav_hz, self.q)
|
|
122
|
+
self.f_c2 = 1.0 + (self.f_max - 1.0) * xi_pol * xi_pos * lc2
|
|
123
|
+
self.zeta = 1.0 + a * (self.f_c - 1.0)
|
|
124
|
+
zeta_flip = 1.0 + a * (self.f_c2 - 1.0)
|
|
125
|
+
self.gamma_cav = budget.gamma0 * self.zeta # 1/s
|
|
126
|
+
self.tau_cav_s = 1.0 / self.gamma_cav
|
|
127
|
+
self.lambda_cav = float(lambda0) * self.zeta / zeta_flip
|
|
128
|
+
self.beta = a * self.f_c / self.zeta
|
|
129
|
+
self.eta_wg = float(eta_wg)
|
|
130
|
+
self.eta = (self.beta * self.eta_wg * float(t_link)
|
|
131
|
+
* float(eta_chip) * float(eta_det))
|
|
132
|
+
# coupling rate defined so F_C = 4 g^2 / (kappa gamma_rad,C)
|
|
133
|
+
gamma_rad_c = budget.gamma0 * a # 1/s
|
|
134
|
+
self.g_hz = 0.5 * np.sqrt(self.f_c * self.kappa_hz * gamma_rad_c)
|
|
135
|
+
self.cooperativity = (4.0 * self.g_hz ** 2
|
|
136
|
+
/ (self.kappa_hz * budget.gamma0))
|
|
137
|
+
gamma_cav_hz = self.gamma_cav / (2.0 * np.pi)
|
|
138
|
+
self.validity = dict(
|
|
139
|
+
spin_selectivity=bool(gamma_cav_hz < omega_q_hz / 5.0),
|
|
140
|
+
bad_cavity=bool(gamma_cav_hz < self.kappa_hz / 10.0),
|
|
141
|
+
weak_coupling=bool(self.g_hz < self.kappa_hz / 10.0),
|
|
142
|
+
)
|
|
143
|
+
self.f_partner = None
|
|
144
|
+
if delta_partner_hz is not None:
|
|
145
|
+
self.f_partner = 1.0 + (self.f_max - 1.0) * xi_pol * xi_pos \
|
|
146
|
+
* lorentzian_suppression(delta_partner_hz,
|
|
147
|
+
self.f_cav_hz, self.q)
|
|
148
|
+
self.validity["partner_selectivity"] = \
|
|
149
|
+
bool(self.kappa_hz < float(delta_partner_hz))
|
|
150
|
+
|
|
151
|
+
def require_valid(self):
|
|
152
|
+
"""Refuse -- naming the failed conditions -- unless every
|
|
153
|
+
regime assumption behind the formulas above holds."""
|
|
154
|
+
bad = [k for k, ok in self.validity.items() if not ok]
|
|
155
|
+
if bad:
|
|
156
|
+
raise ValueError(
|
|
157
|
+
"interface outside its regime of validity: "
|
|
158
|
+
f"{', '.join(bad)} failed. The Purcell/rate formulas "
|
|
159
|
+
"assume the bad-cavity, weak-coupling, spin-selective "
|
|
160
|
+
"regime; change Q_L, V or the operating point.")
|
|
161
|
+
return self
|
|
162
|
+
|
|
163
|
+
def summary(self):
|
|
164
|
+
out = dict(q_loaded=self.q, v_rel=self.v, f_max=self.f_max,
|
|
165
|
+
f_c=self.f_c, f_c2=self.f_c2, zeta=self.zeta,
|
|
166
|
+
tau_cav_ns=self.tau_cav_s * 1e9,
|
|
167
|
+
lambda_cav=self.lambda_cav, beta=self.beta,
|
|
168
|
+
eta=self.eta, g_mhz=self.g_hz / 1e6,
|
|
169
|
+
kappa_ghz=self.kappa_hz / 1e9,
|
|
170
|
+
cooperativity=self.cooperativity,
|
|
171
|
+
validity=dict(self.validity))
|
|
172
|
+
if self.f_partner is not None:
|
|
173
|
+
out["f_partner"] = self.f_partner
|
|
174
|
+
return out
|
vacspin/hamiltonian.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""The group-IV effective Hamiltonian: 4x4 per manifold, pure NumPy.
|
|
2
|
+
|
|
3
|
+
Implements the effective ground/excited-manifold Hamiltonian shared by
|
|
4
|
+
all group-IV vacancy centres in diamond, exactly as written for the
|
|
5
|
+
SnV- in Rosenthal et al., Phys. Rev. X 13, 031022 (2023), Appendix B
|
|
6
|
+
(Eqs. B1-B5), in the basis {|ex up>, |ex dn>, |ey up>, |ey dn>}
|
|
7
|
+
(orbital doublet x spin 1/2):
|
|
8
|
+
|
|
9
|
+
H = H_SO + H_JT + H_Z + H_L
|
|
10
|
+
|
|
11
|
+
H_SO = -(lam/2) [[0, i], [-i, 0]] (x) sigma_z (spin-orbit)
|
|
12
|
+
H_JT = [[Ux, Uy], [Uy, -Ux]] (x) 1 (strain/JT)
|
|
13
|
+
H_Z = (gamma/2) 1 (x) [[(1+2 delta) Bz, Bx - i By],
|
|
14
|
+
[Bx + i By, -(1+2 delta) Bz]] (spin Zeeman)
|
|
15
|
+
H_L = (gamma f/2) Bz [[0, i], [-i, 0]] (x) 1 (orbital Zeeman)
|
|
16
|
+
|
|
17
|
+
with all energies in GHz (angular frequency over 2 pi), fields in
|
|
18
|
+
tesla, and gamma/2pi = 28.0 GHz/T the electron gyromagnetic ratio. The
|
|
19
|
+
field enters in the SPIN FRAME whose z axis lies along the centre's
|
|
20
|
+
<111> high-symmetry axis; `lab_to_spin` performs the exact rotation
|
|
21
|
+
from your lab frame using the (theta, phi) of the parameter set.
|
|
22
|
+
|
|
23
|
+
Exact structure this module's tests lean on rather than stored
|
|
24
|
+
numbers: the Hamiltonian is Hermitian for every input; at zero field
|
|
25
|
+
each manifold consists of two exact Kramers doublets split by the
|
|
26
|
+
closed form Delta = sqrt(lam^2 + 4 (Ux^2 + Uy^2)); the perturbative
|
|
27
|
+
qubit frequency for a purely transverse field (Rosenthal Eq. B9),
|
|
28
|
+
f = 2 gamma B_perp Ups / Delta, agrees with full diagonalisation; and
|
|
29
|
+
the frame rotation is exactly orthonormal, so |B| is preserved to
|
|
30
|
+
machine precision.
|
|
31
|
+
"""
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import numpy as np
|
|
35
|
+
|
|
36
|
+
from .params import SpinParameters
|
|
37
|
+
|
|
38
|
+
__all__ = ["GAMMA_GHZ_PER_T", "h_manifold", "lab_to_spin", "solve",
|
|
39
|
+
"zero_field_splitting", "qubit_frequency",
|
|
40
|
+
"qubit_frequency_perpendicular"]
|
|
41
|
+
|
|
42
|
+
# electron gyromagnetic ratio gamma/2pi (GHz/T), gamma = g_s mu_B / hbar
|
|
43
|
+
# with g_s ~ 2: the standard value used by Rosenthal et al. (2023).
|
|
44
|
+
GAMMA_GHZ_PER_T = 28.0
|
|
45
|
+
|
|
46
|
+
_I2 = np.eye(2)
|
|
47
|
+
_SZ = np.array([[1.0, 0.0], [0.0, -1.0]])
|
|
48
|
+
_ORB_Y = np.array([[0.0, 1j], [-1j, 0.0]]) # orbital angular momentum
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def h_manifold(lam, ups_x, ups_y, f, delta, b_spin):
|
|
52
|
+
"""4x4 manifold Hamiltonian (GHz) for a field b_spin = (Bx, By, Bz)
|
|
53
|
+
in tesla, given in the spin frame."""
|
|
54
|
+
bx, by, bz = (float(b) for b in b_spin)
|
|
55
|
+
h_so = -0.5 * lam * np.kron(_ORB_Y, _SZ)
|
|
56
|
+
h_jt = np.kron(np.array([[ups_x, ups_y], [ups_y, -ups_x]]), _I2)
|
|
57
|
+
zee = np.array([[(1 + 2 * delta) * bz, bx - 1j * by],
|
|
58
|
+
[bx + 1j * by, -(1 + 2 * delta) * bz]])
|
|
59
|
+
h_z = 0.5 * GAMMA_GHZ_PER_T * np.kron(_I2, zee)
|
|
60
|
+
h_l = 0.5 * GAMMA_GHZ_PER_T * f * bz * np.kron(_ORB_Y, _I2)
|
|
61
|
+
return h_so + h_jt + h_z + h_l
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def lab_to_spin(v_lab, theta_rad, phi_rad):
|
|
65
|
+
"""Rotate a lab-frame vector into the spin frame whose z axis
|
|
66
|
+
points along the centre's <111> axis at polar angle theta, azimuth
|
|
67
|
+
phi. Rows of the rotation are the spin-frame basis vectors in the
|
|
68
|
+
lab frame; the rotation is exactly orthonormal."""
|
|
69
|
+
ct, st = np.cos(theta_rad), np.sin(theta_rad)
|
|
70
|
+
cp, sp = np.cos(phi_rad), np.sin(phi_rad)
|
|
71
|
+
ex = np.array([ct * cp, ct * sp, -st])
|
|
72
|
+
ey = np.array([-sp, cp, 0.0])
|
|
73
|
+
ez = np.array([st * cp, st * sp, ct])
|
|
74
|
+
return np.vstack([ex, ey, ez]) @ np.asarray(v_lab, dtype=float)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def zero_field_splitting(lam, ups_x, ups_y=0.0):
|
|
78
|
+
"""Exact zero-field splitting of one manifold:
|
|
79
|
+
Delta = sqrt(lam^2 + 4 (Ux^2 + Uy^2))."""
|
|
80
|
+
return float(np.sqrt(lam ** 2 + 4.0 * (ups_x ** 2 + ups_y ** 2)))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def solve(params: SpinParameters, b_lab, strain_scale=1.0):
|
|
84
|
+
"""Eigenstructure of both manifolds at a lab-frame field (tesla).
|
|
85
|
+
|
|
86
|
+
strain_scale multiplies the strain of both manifolds (the strain-
|
|
87
|
+
engineering axis); the strain axis is taken along x, which fixes
|
|
88
|
+
the azimuthal origin of the spin frame.
|
|
89
|
+
|
|
90
|
+
Returns (evals_g, evecs_g, evals_e, evecs_e): energies in GHz
|
|
91
|
+
sorted ascending, eigenvectors as columns.
|
|
92
|
+
"""
|
|
93
|
+
if not np.all(np.isfinite(b_lab)):
|
|
94
|
+
raise ValueError("magnetic field must be finite")
|
|
95
|
+
s = float(strain_scale)
|
|
96
|
+
if s < 0:
|
|
97
|
+
raise ValueError("strain_scale must be >= 0")
|
|
98
|
+
bs = lab_to_spin(b_lab, params.theta_rad, params.phi_rad)
|
|
99
|
+
hg = h_manifold(params.lam_g, s * params.ups_g, 0.0,
|
|
100
|
+
params.f_g, params.delta_g, bs)
|
|
101
|
+
he = h_manifold(params.lam_e, s * params.ups_e, 0.0,
|
|
102
|
+
params.f_e, params.delta_e, bs)
|
|
103
|
+
eg, vg = np.linalg.eigh(hg)
|
|
104
|
+
ee, ve = np.linalg.eigh(he)
|
|
105
|
+
return eg, vg, ee, ve
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def qubit_frequency(params: SpinParameters, b_lab, strain_scale=1.0):
|
|
109
|
+
"""Ground-state qubit frequency (GHz): the splitting of the lowest
|
|
110
|
+
Kramers doublet at the given lab-frame field."""
|
|
111
|
+
eg, _, _, _ = solve(params, b_lab, strain_scale)
|
|
112
|
+
return float(eg[1] - eg[0])
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def qubit_frequency_perpendicular(params: SpinParameters, b_perp_t,
|
|
116
|
+
manifold="ground"):
|
|
117
|
+
"""Perturbative qubit frequency for a purely TRANSVERSE field
|
|
118
|
+
(Rosenthal et al. Eq. B9): f = 2 gamma B_perp Ups / Delta, valid
|
|
119
|
+
for gamma B_perp << Delta. The tests hold this against full
|
|
120
|
+
diagonalisation; use `qubit_frequency` for anything quantitative.
|
|
121
|
+
"""
|
|
122
|
+
b = float(b_perp_t)
|
|
123
|
+
if b < 0:
|
|
124
|
+
raise ValueError("b_perp_t must be >= 0")
|
|
125
|
+
if manifold == "ground":
|
|
126
|
+
lam, ups = params.lam_g, params.ups_g
|
|
127
|
+
elif manifold == "excited":
|
|
128
|
+
lam, ups = params.lam_e, params.ups_e
|
|
129
|
+
else:
|
|
130
|
+
raise ValueError('manifold must be "ground" or "excited"')
|
|
131
|
+
delta = zero_field_splitting(lam, ups)
|
|
132
|
+
return 2.0 * GAMMA_GHZ_PER_T * b * ups / delta
|
vacspin/params.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Cited parameter sets for group-IV colour-centre spin-photon interfaces.
|
|
2
|
+
|
|
3
|
+
No physical number in this package is made up, and none is accepted
|
|
4
|
+
without a source: both dataclasses refuse to exist without a
|
|
5
|
+
`reference`. Two sets ship, and each ships only what its source states:
|
|
6
|
+
|
|
7
|
+
* `snv_rosenthal2023` / `snv_emission`: the negatively charged
|
|
8
|
+
tin-vacancy (SnV-) centre in diamond, the device-fit values of
|
|
9
|
+
Table I of J. A. Rosenthal et al., Phys. Rev. X 13, 031022 (2023)
|
|
10
|
+
(arXiv:2306.13199), with the ab initio orbital-quenching reduction
|
|
11
|
+
factors of G. Thiering and A. Gali, Phys. Rev. X 8, 021063 (2018),
|
|
12
|
+
and the emission budget of Rosenthal et al., arXiv:2403.13110
|
|
13
|
+
(lifetime, quantum efficiency) with the Debye-Waller factor of
|
|
14
|
+
Goerlitz et al. and the C/D branching ratio of Lee et al.,
|
|
15
|
+
arXiv:2511.05740. This set is validated against measured numbers in
|
|
16
|
+
the test suite (zero-field splittings, qubit frequency, cyclicity,
|
|
17
|
+
Rabi rate) with no free parameters.
|
|
18
|
+
* `siv_hepp2014`: the silicon-vacancy (SiV-) centre, unstrained
|
|
19
|
+
spin-orbit splittings of C. Hepp et al., Phys. Rev. Lett. 112,
|
|
20
|
+
036405 (2014) (arXiv:1310.3106): 50 GHz ground, 260 GHz excited,
|
|
21
|
+
737 nm zero-phonon line. Strain and orbital quenching are
|
|
22
|
+
sample-specific and deliberately NOT shipped for SiV: the set
|
|
23
|
+
carries zero strain and zero quenching, stated in its reference
|
|
24
|
+
string, and you supply your measured values for field-dependent
|
|
25
|
+
work.
|
|
26
|
+
|
|
27
|
+
For any other centre (GeV-, PbV-, or your own SnV sample), populate the
|
|
28
|
+
dataclasses from your measurements or the literature; the mandatory
|
|
29
|
+
`reference` field keeps the provenance attached to every prediction.
|
|
30
|
+
"""
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import dataclasses
|
|
34
|
+
|
|
35
|
+
import numpy as np
|
|
36
|
+
|
|
37
|
+
__all__ = ["SpinParameters", "EmissionBudget", "snv_rosenthal2023",
|
|
38
|
+
"snv_emission", "siv_hepp2014"]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _check_ref(reference):
|
|
42
|
+
if not isinstance(reference, str) or len(reference.strip()) < 8:
|
|
43
|
+
raise ValueError("a real `reference` string is required: every "
|
|
44
|
+
"physical number in this package carries its "
|
|
45
|
+
"source")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclasses.dataclass(frozen=True)
|
|
49
|
+
class SpinParameters:
|
|
50
|
+
"""Effective-Hamiltonian parameters of one group-IV centre.
|
|
51
|
+
|
|
52
|
+
All group-IV vacancy centres (SiV-, GeV-, SnV-, PbV-) share the
|
|
53
|
+
same D3d level structure: an orbital doublet times spin 1/2 in
|
|
54
|
+
each manifold, split by spin-orbit coupling and strain. Units:
|
|
55
|
+
every energy in GHz (angular frequency over 2 pi), fields in
|
|
56
|
+
tesla.
|
|
57
|
+
|
|
58
|
+
lam_g, lam_e : spin-orbit constants of ground/excited manifold.
|
|
59
|
+
ups_g, ups_e : Jahn-Teller/strain energies (the x component; the
|
|
60
|
+
strain axis fixes the azimuthal origin of the spin frame).
|
|
61
|
+
f_g, f_e : orbital Zeeman quenching factors (gL * p).
|
|
62
|
+
delta_g, delta_e : spin g-factor anisotropies (gL * delta_p).
|
|
63
|
+
theta_rad, phi_rad : polar and azimuthal angle of the centre's
|
|
64
|
+
high-symmetry <111> axis in your lab frame.
|
|
65
|
+
reference : where every number comes from. Required, on purpose.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
lam_g: float
|
|
69
|
+
ups_g: float
|
|
70
|
+
lam_e: float
|
|
71
|
+
ups_e: float
|
|
72
|
+
f_g: float
|
|
73
|
+
f_e: float
|
|
74
|
+
delta_g: float
|
|
75
|
+
delta_e: float
|
|
76
|
+
theta_rad: float
|
|
77
|
+
phi_rad: float
|
|
78
|
+
reference: str
|
|
79
|
+
|
|
80
|
+
def __post_init__(self):
|
|
81
|
+
_check_ref(self.reference)
|
|
82
|
+
for name in ("lam_g", "lam_e"):
|
|
83
|
+
if getattr(self, name) <= 0:
|
|
84
|
+
raise ValueError(f"{name} must be positive (GHz)")
|
|
85
|
+
for name in ("ups_g", "ups_e"):
|
|
86
|
+
if getattr(self, name) < 0:
|
|
87
|
+
raise ValueError(f"{name} must be >= 0 (GHz); the strain "
|
|
88
|
+
"axis convention absorbs the sign")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclasses.dataclass(frozen=True)
|
|
92
|
+
class EmissionBudget:
|
|
93
|
+
"""Optical emission budget of one centre (bulk values).
|
|
94
|
+
|
|
95
|
+
tau0_s : total excited-state lifetime (s).
|
|
96
|
+
eta_q : radiative quantum efficiency in [0, 1].
|
|
97
|
+
eta_dw : Debye-Waller (zero-phonon) fraction in [0, 1].
|
|
98
|
+
eta_br : branching ratio into the cavity-coupled transition, [0, 1].
|
|
99
|
+
zpl_nm : zero-phonon-line wavelength (nm).
|
|
100
|
+
reference : where the numbers come from. Required.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
tau0_s: float
|
|
104
|
+
eta_q: float
|
|
105
|
+
eta_dw: float
|
|
106
|
+
eta_br: float
|
|
107
|
+
zpl_nm: float
|
|
108
|
+
reference: str
|
|
109
|
+
|
|
110
|
+
def __post_init__(self):
|
|
111
|
+
_check_ref(self.reference)
|
|
112
|
+
if not (self.tau0_s > 0 and np.isfinite(self.tau0_s)):
|
|
113
|
+
raise ValueError("tau0_s must be a positive lifetime in s")
|
|
114
|
+
if not (self.zpl_nm > 0 and np.isfinite(self.zpl_nm)):
|
|
115
|
+
raise ValueError("zpl_nm must be a positive wavelength in nm")
|
|
116
|
+
for name in ("eta_q", "eta_dw", "eta_br"):
|
|
117
|
+
v = getattr(self, name)
|
|
118
|
+
if not (0.0 < v <= 1.0):
|
|
119
|
+
raise ValueError(f"{name} must be in (0, 1]")
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def gamma0(self):
|
|
123
|
+
"""Total decay rate 1/tau0 (1/s)."""
|
|
124
|
+
return 1.0 / self.tau0_s
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def radiative_fraction(self):
|
|
128
|
+
"""eta_q * eta_dw * eta_br: the fraction of decays that emit a
|
|
129
|
+
zero-phonon photon on the cavity-coupled transition."""
|
|
130
|
+
return self.eta_q * self.eta_dw * self.eta_br
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def snv_rosenthal2023() -> SpinParameters:
|
|
134
|
+
"""SnV- device values, Table I of Rosenthal et al., Phys. Rev. X
|
|
135
|
+
13, 031022 (2023), with quenching reduction factors from Thiering
|
|
136
|
+
and Gali, Phys. Rev. X 8, 021063 (2018):
|
|
137
|
+
f = gL p, delta = gL delta_p. The dipole angles are the same
|
|
138
|
+
paper's device fit (theta fixed at 125.3 deg, phi fitted 37.33
|
|
139
|
+
deg). Validated against measured splittings, qubit frequency,
|
|
140
|
+
cyclicity and Rabi rate in this package's test suite."""
|
|
141
|
+
gL_g, gL_e = 0.363, 0.581
|
|
142
|
+
p_g, p_e = 0.471, 0.125
|
|
143
|
+
dp_g, dp_e = 0.042, 0.303
|
|
144
|
+
return SpinParameters(
|
|
145
|
+
lam_g=830.15, ups_g=177.67, lam_e=2988.0, ups_e=134.00,
|
|
146
|
+
f_g=gL_g * p_g, f_e=gL_e * p_e,
|
|
147
|
+
delta_g=gL_g * dp_g, delta_e=gL_e * dp_e,
|
|
148
|
+
theta_rad=np.deg2rad(125.3), phi_rad=np.deg2rad(37.33),
|
|
149
|
+
reference="Rosenthal et al., PRX 13, 031022 (2023), Table I "
|
|
150
|
+
"(device values); quenching: Thiering & Gali, PRX 8, "
|
|
151
|
+
"021063 (2018)")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def snv_emission() -> EmissionBudget:
|
|
155
|
+
"""SnV- emission budget: lifetime 4.5 ns and quantum efficiency
|
|
156
|
+
0.8 from Rosenthal et al., arXiv:2403.13110 (Table II);
|
|
157
|
+
Debye-Waller factor 0.57 (Goerlitz et al. 2020, as used by Lee et
|
|
158
|
+
al.); C/D branching ratio 0.75 (Lee et al., arXiv:2511.05740);
|
|
159
|
+
zero-phonon line 619 nm."""
|
|
160
|
+
return EmissionBudget(
|
|
161
|
+
tau0_s=4.5e-9, eta_q=0.80, eta_dw=0.57, eta_br=0.75,
|
|
162
|
+
zpl_nm=619.0,
|
|
163
|
+
reference="Rosenthal et al., arXiv:2403.13110 (tau, eta_q); "
|
|
164
|
+
"Goerlitz et al. 2020 (eta_DW); Lee et al., "
|
|
165
|
+
"arXiv:2511.05740 (eta_BR)")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def siv_hepp2014(theta_rad=None, phi_rad=None) -> SpinParameters:
|
|
169
|
+
"""SiV- unstrained model values of Hepp et al., PRL 112, 036405
|
|
170
|
+
(2014): spin-orbit splittings 50 GHz (ground) and 260 GHz
|
|
171
|
+
(excited) at zero strain. Strain and orbital quenching are
|
|
172
|
+
sample-specific and NOT shipped: this set carries ups = 0 and
|
|
173
|
+
f = delta = 0, stated here on purpose -- supply your measured
|
|
174
|
+
values for quantitative field-dependent work. Default orientation:
|
|
175
|
+
the <111> axis of a (001)-oriented sample (theta = arccos(1/sqrt 3),
|
|
176
|
+
phi = 45 deg), exact crystallography, overridable."""
|
|
177
|
+
if theta_rad is None:
|
|
178
|
+
theta_rad = float(np.arccos(1.0 / np.sqrt(3.0)))
|
|
179
|
+
if phi_rad is None:
|
|
180
|
+
phi_rad = float(np.pi / 4)
|
|
181
|
+
return SpinParameters(
|
|
182
|
+
lam_g=50.0, ups_g=0.0, lam_e=260.0, ups_e=0.0,
|
|
183
|
+
f_g=0.0, f_e=0.0, delta_g=0.0, delta_e=0.0,
|
|
184
|
+
theta_rad=theta_rad, phi_rad=phi_rad,
|
|
185
|
+
reference="Hepp et al., PRL 112, 036405 (2014): unstrained "
|
|
186
|
+
"splittings 50/260 GHz, ZPL 737 nm; strain and "
|
|
187
|
+
"orbital quenching deliberately not shipped (sample-"
|
|
188
|
+
"specific) -- supply measured values")
|