Source code for sim.pulses.shapes

# ==============================================================================
#  QUSIM - Quantum Simulator for NV Centers
#  Leon Kaiser, MSQC Goethe University, Frankfurt, Germany
#  https://msqc.cgi-host6.rz.uni-frankfurt.de
#  I.kaiser[at]em.uni-frankfurt.de
#
#  This software is provided for scientific and educational purposes.
#  Free to use, modify, and distribute with attribution.
# ==============================================================================
"""
Pulse envelope shapes for NV center control.

All shape functions return normalized envelopes (max = 1).
The actual amplitude is set when creating the pulse.

Available Shapes
----------------
rectangular : Constant amplitude, sharp edges
gaussian : Smooth Gaussian envelope
sinc : Sin(x)/x for broadband excitation
blackman : Window function with low side lobes
drag : DRAG pulse for leakage reduction
hermite : Hermite-Gaussian shapes
"""

import numpy as np
from typing import Optional, Tuple


[docs] def rectangular(t: np.ndarray, duration: float) -> np.ndarray: """ Rectangular (square) pulse envelope. Parameters ---------- t : np.ndarray Time array in seconds duration : float Pulse duration in seconds Returns ------- np.ndarray Envelope values (1 inside pulse, 0 outside) """ return np.where((t >= 0) & (t <= duration), 1.0, 0.0)
[docs] def gaussian( t: np.ndarray, duration: float, sigma: Optional[float] = None, truncate: float = 3.0 ) -> np.ndarray: """ Gaussian pulse envelope. Parameters ---------- t : np.ndarray Time array in seconds duration : float Total pulse duration in seconds sigma : float, optional Standard deviation. Default: duration/6 (3σ truncation each side) truncate : float Truncation in units of sigma. Default: 3 Returns ------- np.ndarray Gaussian envelope (normalized to max 1) """ if sigma is None: sigma = duration / (2 * truncate) t0 = duration / 2 env = np.exp(-((t - t0) / sigma) ** 2 / 2) # Zero outside duration env = np.where((t >= 0) & (t <= duration), env, 0.0) return env
[docs] def sinc_pulse( t: np.ndarray, duration: float, zero_crossings: int = 3 ) -> np.ndarray: """ Sinc pulse envelope (sin(x)/x). Good for broadband excitation with controlled frequency profile. Parameters ---------- t : np.ndarray Time array in seconds duration : float Total pulse duration in seconds zero_crossings : int Number of zero crossings on each side. Default: 3 Returns ------- np.ndarray Sinc envelope (normalized to max 1) """ t0 = duration / 2 x = zero_crossings * np.pi * (t - t0) / (duration / 2) # Avoid division by zero with np.errstate(divide='ignore', invalid='ignore'): env = np.where(x == 0, 1.0, np.sin(x) / x) # Zero outside duration env = np.where((t >= 0) & (t <= duration), env, 0.0) return env
[docs] def blackman(t: np.ndarray, duration: float) -> np.ndarray: """ Blackman window pulse envelope. Very low side lobes, good for frequency selectivity. Parameters ---------- t : np.ndarray Time array in seconds duration : float Total pulse duration in seconds Returns ------- np.ndarray Blackman envelope (normalized to max 1) """ # Blackman coefficients a0 = 0.42 a1 = 0.5 a2 = 0.08 env = (a0 - a1 * np.cos(2 * np.pi * t / duration) + a2 * np.cos(4 * np.pi * t / duration)) # Zero outside duration env = np.where((t >= 0) & (t <= duration), env, 0.0) # Normalize to max 1 env = env / np.max(env) if np.max(env) > 0 else env return env
[docs] def drag( t: np.ndarray, duration: float, sigma: Optional[float] = None, beta: float = 0.5 ) -> Tuple[np.ndarray, np.ndarray]: """ DRAG (Derivative Removal by Adiabatic Gating) pulse. Reduces leakage to non-computational states by adding a derivative component in quadrature. Parameters ---------- t : np.ndarray Time array in seconds duration : float Total pulse duration in seconds sigma : float, optional Gaussian width. Default: duration/6 beta : float DRAG coefficient. Default: 0.5 Returns ------- tuple (I_component, Q_component) arrays Notes ----- I channel: Gaussian Q channel: Derivative of Gaussian (scaled by beta) """ if sigma is None: sigma = duration / 6 t0 = duration / 2 x = (t - t0) / sigma # I channel: Gaussian I = np.exp(-x**2 / 2) # Q channel: Derivative Q = -beta * x * I / sigma # Zero outside duration mask = (t >= 0) & (t <= duration) I = np.where(mask, I, 0.0) Q = np.where(mask, Q, 0.0) return I, Q
[docs] def hermite( t: np.ndarray, duration: float, order: int = 0, sigma: Optional[float] = None ) -> np.ndarray: """ Hermite-Gaussian pulse envelope. Higher orders have more oscillations and are useful for selective excitation. Parameters ---------- t : np.ndarray Time array in seconds duration : float Total pulse duration in seconds order : int Hermite polynomial order. Default: 0 (Gaussian) sigma : float, optional Gaussian width. Default: duration/6 Returns ------- np.ndarray Hermite-Gaussian envelope (normalized to max 1) """ if sigma is None: sigma = duration / 6 t0 = duration / 2 x = (t - t0) / sigma # Hermite polynomials if order == 0: H = np.ones_like(x) elif order == 1: H = 2 * x elif order == 2: H = 4 * x**2 - 2 elif order == 3: H = 8 * x**3 - 12 * x elif order == 4: H = 16 * x**4 - 48 * x**2 + 12 else: # General case using scipy from scipy.special import hermite as hermite_poly H_func = hermite_poly(order) H = H_func(x) # Combine with Gaussian env = H * np.exp(-x**2 / 2) # Zero outside duration env = np.where((t >= 0) & (t <= duration), env, 0.0) # Normalize to max abs value = 1 max_val = np.max(np.abs(env)) if max_val > 0: env = env / max_val return env
[docs] def cosine(t: np.ndarray, duration: float) -> np.ndarray: """ Cosine (Hann) window pulse envelope. Parameters ---------- t : np.ndarray Time array in seconds duration : float Total pulse duration in seconds Returns ------- np.ndarray Cosine envelope """ env = 0.5 * (1 - np.cos(2 * np.pi * t / duration)) env = np.where((t >= 0) & (t <= duration), env, 0.0) return env
[docs] def tanh_edges( t: np.ndarray, duration: float, rise_time: float = 0.1 ) -> np.ndarray: """ Pulse with smooth tanh rise and fall. Parameters ---------- t : np.ndarray Time array in seconds duration : float Total pulse duration in seconds rise_time : float Rise/fall time as fraction of duration. Default: 0.1 Returns ------- np.ndarray Tanh-edged envelope """ tr = duration * rise_time rise = 0.5 * (1 + np.tanh((t - tr) / (tr / 4))) fall = 0.5 * (1 - np.tanh((t - duration + tr) / (tr / 4))) env = rise * fall env = np.where((t >= 0) & (t <= duration), env, 0.0) # Normalize max_val = np.max(env) if max_val > 0: env = env / max_val return env