Sensing Protocols¶
The sensing subpackage orchestrates complete sensing experiments by combining spin-Hamiltonian computations with pulse-sequence timing and shot-noise SNR estimation.
SpinDefectSim.sensing.protocols¶
SensingExperiment¶
Container for a single lock-in sensing experiment. Holds two branches — a signal branch (field with the quantity to sense) and a reference branch (field without it) — and computes ODMR observables for both.
Inherits: PhysicalParams, PlottingMixin
Constructor¶
SensingExperiment(
sp_with: SpinParams,
sp_no: SpinParams,
E_fields_Vpm: np.ndarray,
defaults: Optional[Defaults] = None,
quantization_axes: Optional[np.ndarray] = None,
B_extra_fields_with: Optional[np.ndarray] = None,
B_extra_fields_no: Optional[np.ndarray] = None,
)
Parameters:
| Name | Type | Description |
|---|---|---|
sp_with |
SpinParams |
Spin parameters for the signal branch (field on) |
sp_no |
SpinParams |
Spin parameters for the reference branch (field off) |
E_fields_Vpm |
np.ndarray |
Local E-field at each defect, shape (N, 3), in V/m |
defaults |
Defaults \| None |
Global defaults |
quantization_axes |
np.ndarray \| None |
Per-defect z′-axes, shape (N, 3) |
B_extra_fields_with |
np.ndarray \| None |
Per-defect stray B-field for signal branch, shape (N, 3), in T |
B_extra_fields_no |
np.ndarray \| None |
Per-defect stray B-field for reference branch, shape (N, 3), in T |
Lazy-cached properties:
transitions_with— list of N transition-frequency arrays for the signal branchtransitions_no— list of N transition-frequency arrays for the reference branch
cw_odmr¶
Compute CW ODMR spectra for both branches and their difference.
Parameters:
| Name | Type | Description |
|---|---|---|
f_axis_Hz |
np.ndarray |
MW frequency axis (Hz) |
Returns: (pl_with, pl_no, dpl) — each 1-D array over f_axis_Hz
| Output | Description |
|---|---|
pl_with |
Normalised PL spectrum, signal branch |
pl_no |
Normalised PL spectrum, reference branch |
dpl |
Lock-in difference: pl_with − pl_no |
Example:
ramsey¶
Compute the Ramsey free-induction decay (FID) lock-in signal ΔS(τ) for the ensemble.
The free-precession time axis is automatically chosen if not supplied, spanning [0, 2 × T₂*] with 200 points.
Parameters:
| Name | Type | Description |
|---|---|---|
tau_range_s |
np.ndarray \| None |
Free-precession time values (s); if None, auto-generated from defaults.T2star |
Returns: (tau_s, S_with, S_no, dS, tau_opt, dS_peak)
| Output | Description |
|---|---|
tau_s |
Free-precession time axis (s) |
S_with |
Echo amplitude vs τ, signal branch |
S_no |
Echo amplitude vs τ, reference branch |
dS |
Lock-in difference: S_with − S_no |
tau_opt |
τ that maximises |dS| (s) |
dS_peak |
Peak differential signal at tau_opt |
echo_static¶
Compute the Hahn-echo lock-in signal ΔS(τ) for a static (DC) field measurement.
Returns the same (tau_s, S_with, S_no, dS, tau_opt, dS_peak) tuple as ramsey, but uses defaults.T2echo for the coherence time and the echo decoherence envelope.
echo_odmr_lockIn¶
Frequency-domain echo-detected lock-in spectrum.
Returns: (dpl, pl_with, pl_no) — same convention as cw_odmr, but using T₂-limited linewidths
snr¶
Signal-to-noise ratio after N_avg averaging cycles.
Parameters:
| Name | Type | Description |
|---|---|---|
delta_S |
np.ndarray |
Differential signal |
N_avg |
float |
Number of averaging cycles |
Returns: np.ndarray same shape as delta_S
n_avg_to_detect¶
Number of averaging cycles needed to reach snr_target.
Parameters:
| Name | Type | Description |
|---|---|---|
delta_S |
np.ndarray |
Differential signal |
snr_target |
float |
Target SNR (default 5σ) |
Returns: np.ndarray same shape as delta_S
SpinDefectSim.sensing.sequences¶
Pulse sequence classes track hardware gate times (π/2 pulses, π pulses, readout window) and compute per-shot timing and repetition rates.
PulseSequence (abstract base)¶
Constructor fields (all sequences inherit these)¶
| Attribute | Default | Description |
|---|---|---|
t_pi_half_s |
10e-9 |
π/2 pulse duration (s) |
t_pi_s |
20e-9 |
π pulse duration (s) |
t_readout_s |
300e-9 |
Optical readout window (s) |
t_init_s |
1.0e-6 |
Initialization laser pulse (s) |
total_time¶
Wall-clock time per shot:
Parameters:
| Name | Type | Description |
|---|---|---|
tau_s |
float \| np.ndarray |
Free-precession time (s) |
Returns: total shot time (same type as input)
repetition_rate¶
Shot repetition rate R(τ) = 1 / T_shot(τ) in Hz.
n_avg_in_time¶
Number of averaging cycles achievable in a total integration time T_int_s (s).
pulse_time_s¶
Total time spent in MW pulses per shot: \(n_{\pi/2} \cdot t_{\pi/2} + n_\pi \cdot t_\pi\).
summary¶
Return a dict of all timing quantities at a given τ.
RamseySequence¶
Topology: init → π/2 → [τ] → π/2 → readout
| Property | Value |
|---|---|
| n_pi_half_pulses | 2 |
| n_pi_pulses | 0 |
| n_free_precession_intervals | 1 |
Example:
seq = RamseySequence(t_pi_half_s=12e-9, t_readout_s=500e-9)
print(seq.total_time(200e-9)) # shot time at τ = 200 ns
print(seq)
HahnEchoSequence¶
Topology: init → π/2 → [τ] → π → [τ] → π/2 → readout
| Property | Value |
|---|---|
| n_pi_half_pulses | 2 |
| n_pi_pulses | 1 |
| n_free_precession_intervals | 2 |
XY8Sequence¶
XY-8 dynamical decoupling: π/2 → [τ/2 – (X π – τ – Y π – τ – X π – τ – Y π – τ)×2 – τ/2] → π/2
| Property | Value |
|---|---|
| n_pi_half_pulses | 2 |
| n_pi_pulses | 8 |
| n_free_precession_intervals | 16 |
Note: The free-precession intervals in XY8 are each τ/2, so total_time(tau_s) uses half the delay for each of the 16 intervals; internally n_fp = 16 and the caller supplies τ/2.
SpinDefectSim.sensing.snr¶
Low-level SNR functions. These are also available as methods on SensingExperiment.
noise_floor¶
from SpinDefectSim.sensing.snr import noise_floor
def noise_floor(
contrast: Optional[float] = None,
n_photons: Optional[int] = None,
) -> float
Single-shot photon-shot-noise floor:
Parameters:
| Name | Type | Description |
|---|---|---|
contrast |
float \| None |
CW ODMR contrast C; if None, uses DEFAULT.get_contrast() |
n_photons |
int \| None |
Photons per readout; if None, uses DEFAULT.n_photons |
Returns: float — noise floor σ
snr¶
from SpinDefectSim.sensing.snr import snr
def snr(
delta_S: np.ndarray,
N_avg: float,
contrast: Optional[float] = None,
n_photons: Optional[int] = None,
) -> np.ndarray
Signal-to-noise ratio:
Parameters:
| Name | Type | Description |
|---|---|---|
delta_S |
np.ndarray |
Differential signal ΔS |
N_avg |
float |
Number of averaging cycles |
contrast |
float \| None |
Override contrast |
n_photons |
int \| None |
Override photon count |
Returns: np.ndarray same shape as delta_S
n_avg_for_threshold¶
from SpinDefectSim.sensing.snr import n_avg_for_threshold
def n_avg_for_threshold(
delta_S: np.ndarray,
snr_target: float = 5.0,
contrast: Optional[float] = None,
n_photons: Optional[int] = None,
) -> np.ndarray
Number of averages needed to reach snr_target:
Returns: np.ndarray same shape as delta_S
Example:
from SpinDefectSim.sensing.snr import n_avg_for_threshold
from SpinDefectSim.sensing.sequences import HahnEchoSequence
N = n_avg_for_threshold(dS_peak, snr_target=5.0, contrast=0.02, n_photons=500)
seq = HahnEchoSequence()
T_total = N * seq.total_time(tau_opt)
print(f"Integration time: {T_total * 1e3:.1f} ms")