Interfaces Module¶
The interfaces module provides hardware abstraction layers for experimental control, with realistic noise models.
AWG Interface¶
Arbitrary Waveform Generator (AWG) interface.
The AWG generates time-dependent microwave waveforms for spin control. It stores amplitude Ω(t), phase φ(t), and detuning Δ(t) as arrays and provides interpolation for arbitrary time points.
Waveform Structure¶
The MW field in the rotating frame is characterized by: - Amplitude (Rabi frequency) Ω(t) in MHz - Phase φ(t) in radians - Detuning Δ(t) in MHz (optional)
- The resulting Hamiltonian is:
H_MW = (Ω/2)(S₊ e^(-iφ) + S₋ e^(iφ)) + Δ Sz
Pulse Shapes¶
Supported envelope shapes: - rect: Rectangular (constant amplitude) - gauss: Gaussian envelope - sinc: Sinc function (sin(x)/x) - blackman: Blackman window - hermite: Hermite-Gaussian - drag: DRAG pulse for leakage reduction
- class sim.interfaces.awg.AWGInterface(sample_rate=1000000000.0, _amplitude=<factory>, _phase=<factory>, _detuning=<factory>)[source]¶
Bases:
objectArbitrary Waveform Generator for microwave pulse control.
- Parameters:
Examples
>>> awg = AWGInterface() >>> awg.add_pulse("gauss", amplitude=10, duration=100e-9, phase=0) >>> awg.add_delay(500e-9) >>> awg.add_pulse("gauss", amplitude=10, duration=100e-9, phase=np.pi) >>> >>> # Get Rabi frequency at t=50 ns >>> omega = awg.get_omega(50e-9)
- add_pulse(shape, amplitude, duration, phase=0.0, detuning=0.0, **kwargs)[source]¶
Add a pulse to the waveform.
- Parameters:
shape (str) – Pulse shape: ‘rect’, ‘gauss’, ‘sinc’, ‘blackman’, ‘hermite’, ‘drag’
amplitude (float) – Peak Rabi frequency in MHz
duration (float) – Pulse duration in seconds
phase (float) – Pulse phase in radians. Default: 0
detuning (float) – Frequency detuning in MHz. Default: 0
**kwargs – Shape-specific parameters: - sigma: Gaussian width (default: duration/6) - beta: DRAG coefficient (default: 0.5) - order: Hermite order (default: 0)
- add_delay(duration)[source]¶
Add a delay (zero amplitude) segment.
- Parameters:
duration (float) – Delay duration in seconds
- to_mw_drive()[source]¶
Create a MicrowaveDrive term from this AWG waveform.
- Returns:
Hamiltonian term with this waveform
- Return type:
The AWG (Arbitrary Waveform Generator) interface simulates a programmable pulse generator:
from sim.interfaces import AWGInterface
# Create AWG with 1 GS/s sample rate
awg = AWGInterface(sample_rate=1e9)
# Add pulses
awg.add_pulse(
shape="gaussian",
amplitude=1.0, # Normalized amplitude
duration=50e-9, # 50 ns
phase=0
)
awg.add_delay(100e-9) # 100 ns delay
awg.add_pulse(
shape="rect",
amplitude=1.0,
duration=100e-9,
phase=np.pi/2
)
# Get waveform
t, waveform = awg.get_waveform()
# Plot
import matplotlib.pyplot as plt
plt.plot(t * 1e9, waveform.real)
plt.xlabel("Time (ns)")
plt.ylabel("Amplitude")
Waveform Generation¶
# Generate IQ waveforms
I, Q = awg.get_iq_waveform(carrier_freq=2.87e9)
# Export to file
awg.save_waveform("sequence.npz")
Laser Interface¶
Laser interface for optical control.
The laser provides optical excitation for: - Spin initialization (optical pumping) - Spin readout (fluorescence detection) - Coherent optical control (Rabi oscillations)
Laser Parameters¶
Wavelength: 532 nm (green) or 637 nm (ZPL)
Power: Typically 0.1-10 mW
Rabi frequency: Depends on power and beam waist
- The optical Rabi frequency is related to laser power by:
Ω_L = d·E₀/ℏ = d·√(2P/(ε₀cπw₀²))/ℏ
where d is the dipole moment and w₀ is the beam waist.
- class sim.interfaces.laser.LaserPulse(start, duration, power, shape='rect')[source]¶
Bases:
objectSingle laser pulse.
- class sim.interfaces.laser.LaserInterface(wavelength=532.0, power_to_rabi=10.0, linewidth=0.0, _pulses=<factory>)[source]¶
Bases:
objectLaser interface for optical control.
- Parameters:
wavelength (float) – Laser wavelength in nm. Default: 532 (green)
power_to_rabi (float) – Conversion factor from mW to MHz. Default: 10 MHz/mW
linewidth (float)
_pulses (List[LaserPulse])
Examples
>>> laser = LaserInterface() >>> laser.add_pulse(start=0, duration=1e-6, power=1.0) >>> laser.add_pulse(start=2e-6, duration=100e-9, power=0.5) >>> >>> # Get power at specific time >>> p = laser.get_power(500e-9) # 1.0 mW
- property pulses: List[LaserPulse]¶
List of scheduled pulses.
- to_optical_coupling()[source]¶
Create an OpticalCoupling term from this laser.
- Returns:
Hamiltonian term with this laser waveform
- Return type:
- schedule_initialization(t_start=0, duration=1e-06, power=1.0)[source]¶
Schedule an initialization pulse.
Typical initialization uses a few microseconds of green light to optically pump into ms=0.
The laser interface simulates optical excitation:
from sim.interfaces import LaserInterface
# Create laser (532 nm green)
laser = LaserInterface(wavelength=532e-9)
# Set power
laser.set_power(1e-3) # 1 mW
# Convert to Rabi frequency
rabi = laser.power_to_rabi(power=1e-3)
# Pulse timing
laser.pulse(duration=1e-6, delay=0)
Photon Counter¶
Photon counter interface for fluorescence detection.
Models realistic single-photon detection including: - Detection efficiency - Dark counts - Dead time - Timing jitter - Afterpulsing
Detector Characteristics¶
Typical APD (Avalanche Photodiode) parameters: - Detection efficiency: 5-20% - Dark count rate: 10-1000 Hz - Dead time: 20-100 ns - Timing jitter: 0.3-1 ns - Afterpulsing probability: 0.1-5%
SNSPD (Superconducting Nanowire): - Detection efficiency: 70-95% - Dark count rate: <1 Hz - Dead time: 10-50 ns - Timing jitter: <100 ps
- class sim.interfaces.photon_counter.PhotonCounter(efficiency=0.1, dark_count_rate=100.0, dead_time=5e-08, timing_jitter=5e-10, afterpulsing_prob=0.01, afterpulsing_delay=1e-07, _total_counts=0, _detection_times=<factory>, _last_detection=-1.0, _current_time=0.0)[source]¶
Bases:
objectRealistic photon counter for fluorescence detection.
- Parameters:
efficiency (float) – Detection efficiency (0-1). Default: 0.1 (10%)
dark_count_rate (float) – Dark count rate in Hz. Default: 100
dead_time (float) – Dead time in seconds. Default: 50e-9 (50 ns)
timing_jitter (float) – Timing jitter (std dev) in seconds. Default: 0.5e-9
afterpulsing_prob (float) – Afterpulsing probability. Default: 0.01 (1%)
afterpulsing_delay (float)
_total_counts (int)
_last_detection (float)
_current_time (float)
Examples
>>> counter = PhotonCounter(efficiency=0.1) >>> >>> # Simulate detection from excited state population >>> for rho in rho_trajectory: ... result = counter.count(rho, dt=1e-9, gamma=8e7) >>> >>> print(f"Total photons: {counter.total_counts}")
- count(rho, dt, gamma, current_time=None)[source]¶
Simulate photon detection from the current state.
- Parameters:
- Returns:
Detection result with keys: - ‘detected’: Number of photons detected in this step - ‘expected’: Expected number of photons - ‘pop_excited’: Excited state population
- Return type:
- calculate_g2(tau_max=1e-06, n_bins=100)[source]¶
Calculate g²(τ) autocorrelation function.
- Parameters:
- Returns:
(tau, g2) arrays
- Return type:
Notes
g²(0) < 1 indicates antibunching (single photon source) g²(0) > 1 indicates bunching (thermal light) g²(0) = 1 for coherent light
- __init__(efficiency=0.1, dark_count_rate=100.0, dead_time=5e-08, timing_jitter=5e-10, afterpulsing_prob=0.01, afterpulsing_delay=1e-07, _total_counts=0, _detection_times=<factory>, _last_detection=-1.0, _current_time=0.0)¶
The photon counter simulates realistic single-photon detection:
from sim.interfaces import PhotonCounter
# Create APD-like detector
counter = PhotonCounter(
efficiency=0.1, # 10% detection efficiency
dark_count_rate=100, # 100 Hz dark counts
dead_time=50e-9, # 50 ns dead time
timing_jitter=0.5e-9, # 500 ps jitter
afterpulsing_prob=0.01 # 1% afterpulsing
)
# Reset counters
counter.reset()
# Count photons from density matrix
for rho in trajectory:
result = counter.count(
rho=rho,
dt=1e-9, # 1 ns time step
gamma=8e7 # Emission rate
)
print(f"Detected: {result['detected']}")
# Get total counts
print(f"Total: {counter.total_counts}")
Detector Types¶
APD (Avalanche Photodiode):
apd = PhotonCounter(
efficiency=0.1,
dark_count_rate=100,
dead_time=50e-9,
timing_jitter=0.5e-9,
afterpulsing_prob=0.01
)
SNSPD (Superconducting Nanowire):
snspd = PhotonCounter(
efficiency=0.9,
dark_count_rate=0.1,
dead_time=20e-9,
timing_jitter=50e-12,
afterpulsing_prob=0.001
)
Analysis Functions¶
# Count rate
rate = counter.count_rate(window=1e-6) # Hz
# Histogram of detection times
counts, edges = counter.histogram(
bin_width=1e-9,
t_range=(0, 1e-6)
)
# g²(τ) autocorrelation
tau, g2 = counter.calculate_g2(
tau_max=1e-6,
n_bins=100
)
# Fano factor
F = counter.fano_factor(window=1e-6)
Complete Measurement¶
Example: ODMR measurement with all interfaces:
from sim import HamiltonianBuilder, LindbladSolver
from sim.hamiltonian.terms import ZFS, Zeeman, MicrowaveDrive
from sim.states import ground_state, projector_excited
from sim.interfaces import LaserInterface, PhotonCounter
import numpy as np
# Setup
laser = LaserInterface(wavelength=532e-9)
counter = PhotonCounter(efficiency=0.1)
# Sweep MW frequency
freqs = np.linspace(2.8, 2.94, 100) # GHz
counts = []
for f in freqs:
# Build Hamiltonian
H = HamiltonianBuilder()
H.add(ZFS(D=2.87))
H.add(Zeeman(B=10))
H.add(MicrowaveDrive(omega=1, detuning=f-2.87))
# Simulate
solver = LindbladSolver(H)
solver.add_t2_dephasing(gamma=1e6)
solver.add_optical_decay(gamma=8e7)
rho0 = ground_state()
result = solver.evolve(rho0, (0, 1e-6), n_steps=100)
# Count photons
counter.reset()
for rho in result.rho_t:
counter.count(rho, dt=10e-9, gamma=8e7)
counts.append(counter.total_counts)
# Plot ODMR spectrum
import matplotlib.pyplot as plt
plt.plot(freqs, counts)
plt.xlabel("MW Frequency (GHz)")
plt.ylabel("Photon Counts")