Pulses Module

The pulses module provides tools for creating microwave pulse sequences for coherent spin control.

Basic Pulses

Basic pulse definitions for NV center control.

Provides π and π/2 pulses for standard quantum gates.

Rotation Convention

A pulse with Rabi frequency Ω, duration τ, and phase φ performs the rotation:

R_φ(θ) = exp(-i θ/2 (cos(φ)σ_x + sin(φ)σ_y))

where θ = Ω τ is the rotation angle.

For resonant driving:

π-pulse: θ = π, causes spin flip |0⟩ ↔ |±1⟩ π/2-pulse: θ = π/2, creates superposition

class sim.pulses.basic.Pulse(shape, amplitude, duration, phase, kwargs=None)[source]

Bases: object

Representation of a single MW pulse.

Parameters:
shape

Pulse shape: ‘rect’, ‘gauss’, ‘drag’, etc.

Type:

str

amplitude

Peak Rabi frequency in MHz

Type:

float

duration

Pulse duration in seconds

Type:

float

phase

Pulse phase in radians

Type:

float

kwargs

Additional shape-specific parameters

Type:

dict

shape: str
amplitude: float
duration: float
phase: float
kwargs: Dict[str, Any] = None
rotation_angle()[source]

Calculate total rotation angle θ = Ω τ.

Return type:

float

axis_label()[source]

Get rotation axis label (X, Y, -X, -Y).

Return type:

str

__init__(shape, amplitude, duration, phase, kwargs=None)
Parameters:
Return type:

None

sim.pulses.basic.pi_pulse(rabi_freq_mhz=1.0, axis='x', shape='rect', **kwargs)[source]

Create a π pulse (180° rotation).

Parameters:
  • rabi_freq_mhz (float) – Rabi frequency in MHz. Default: 1 MHz

  • axis (str) – Rotation axis: ‘x’, ‘y’, ‘-x’, ‘-y’. Default: ‘x’

  • shape (str) – Pulse shape: ‘rect’, ‘gauss’, etc. Default: ‘rect’

  • **kwargs – Additional shape parameters (sigma, beta, etc.)

Returns:

π pulse with calculated duration

Return type:

Pulse

Examples

>>> p = pi_pulse(rabi_freq_mhz=10)
>>> print(f"Duration: {p.duration*1e9:.1f} ns")
Duration: 50.0 ns
>>> # Y-axis rotation
>>> p_y = pi_pulse(rabi_freq_mhz=20, axis='y')
sim.pulses.basic.pi_half_pulse(rabi_freq_mhz=1.0, axis='x', shape='rect', **kwargs)[source]

Create a π/2 pulse (90° rotation).

Parameters:
  • rabi_freq_mhz (float) – Rabi frequency in MHz. Default: 1 MHz

  • axis (str) – Rotation axis: ‘x’, ‘y’, ‘-x’, ‘-y’. Default: ‘x’

  • shape (str) – Pulse shape. Default: ‘rect’

  • **kwargs – Additional shape parameters

Returns:

π/2 pulse with calculated duration

Return type:

Pulse

Examples

>>> p = pi_half_pulse(rabi_freq_mhz=10)
>>> print(f"Duration: {p.duration*1e9:.1f} ns")
Duration: 25.0 ns
sim.pulses.basic.arbitrary_rotation(theta, phi=0, rabi_freq_mhz=1.0, shape='rect', **kwargs)[source]

Create a pulse for arbitrary rotation R_φ(θ).

Parameters:
  • theta (float) – Rotation angle in radians

  • phi (float) – Rotation axis azimuthal angle in radians. Default: 0 (X axis)

  • rabi_freq_mhz (float) – Rabi frequency in MHz. Default: 1 MHz

  • shape (str) – Pulse shape. Default: ‘rect’

  • **kwargs – Additional shape parameters

Returns:

Arbitrary rotation pulse

Return type:

Pulse

Examples

>>> # π/4 rotation around Y
>>> p = arbitrary_rotation(theta=np.pi/4, phi=np.pi/2)
sim.pulses.basic.composite_pi_pulse(rabi_freq_mhz=1.0, pulse_type='BB1', shape='rect', **kwargs)[source]

Create a composite π pulse for error compensation.

Parameters:
  • rabi_freq_mhz (float) – Rabi frequency in MHz

  • pulse_type (str) – Type of composite pulse: - ‘BB1’: Broadband 1 (robust to amplitude errors) - ‘CORPSE’: (robust to frequency errors)

  • shape (str) – Pulse shape

Returns:

Sequence of pulses comprising the composite pulse

Return type:

list of Pulse

Pulse Class

from sim.pulses import Pulse

# Create a pulse
pulse = Pulse(
    shape="gaussian",      # Envelope shape
    amplitude=10,          # Rabi frequency in MHz
    duration=50e-9,        # Duration in seconds
    phase=0                # Phase in radians
)

# Access properties
print(pulse.area)          # Rotation angle (rad)
print(pulse.is_pi_pulse)   # True if area ≈ π

Standard Pulses

from sim.pulses import pi_pulse, pi_half_pulse

# π-pulse (180° rotation)
pi = pi_pulse(rabi_freq_mhz=10, axis='x', shape='rect')

# π/2-pulse (90° rotation)
pi2 = pi_half_pulse(rabi_freq_mhz=10, axis='y', shape='gaussian')

Pulse Shapes

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

sim.pulses.shapes.rectangular(t, duration)[source]

Rectangular (square) pulse envelope.

Parameters:
  • t (np.ndarray) – Time array in seconds

  • duration (float) – Pulse duration in seconds

Returns:

Envelope values (1 inside pulse, 0 outside)

Return type:

np.ndarray

sim.pulses.shapes.gaussian(t, duration, sigma=None, truncate=3.0)[source]

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:

Gaussian envelope (normalized to max 1)

Return type:

np.ndarray

sim.pulses.shapes.sinc_pulse(t, duration, zero_crossings=3)[source]

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:

Sinc envelope (normalized to max 1)

Return type:

np.ndarray

sim.pulses.shapes.blackman(t, duration)[source]

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:

Blackman envelope (normalized to max 1)

Return type:

np.ndarray

sim.pulses.shapes.drag(t, duration, sigma=None, beta=0.5)[source]

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:

(I_component, Q_component) arrays

Return type:

tuple

Notes

I channel: Gaussian Q channel: Derivative of Gaussian (scaled by beta)

sim.pulses.shapes.hermite(t, duration, order=0, sigma=None)[source]

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:

Hermite-Gaussian envelope (normalized to max 1)

Return type:

np.ndarray

sim.pulses.shapes.cosine(t, duration)[source]

Cosine (Hann) window pulse envelope.

Parameters:
  • t (np.ndarray) – Time array in seconds

  • duration (float) – Total pulse duration in seconds

Returns:

Cosine envelope

Return type:

np.ndarray

sim.pulses.shapes.tanh_edges(t, duration, rise_time=0.1)[source]

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:

Tanh-edged envelope

Return type:

np.ndarray

Available Shapes

  • "rect" or "rectangular": Square pulse

  • "gaussian": Gaussian envelope

  • "drag": DRAG pulse (derivative removal by adiabatic gate)

  • "blackman": Blackman window

  • "flattop": Flat top with smooth edges

from sim.pulses.shapes import get_envelope

t = np.linspace(0, 100e-9, 1000)

# Gaussian envelope
env = get_envelope("gaussian", t, duration=50e-9, sigma=10e-9)

# DRAG correction
env_drag = get_envelope("drag", t, duration=50e-9, beta=0.5)

Pulse Sequences

Standard pulse sequences for NV center experiments.

Implements common sequences for coherence measurements and dynamical decoupling.

Sequence Types

Ramsey: Free induction decay (T2*)

π/2 - τ - π/2

Spin Echo: Hahn echo (T2)

π/2 - τ/2 - π - τ/2 - π/2

CPMG: Carr-Purcell-Meiboom-Gill (extended coherence)

π/2 - [τ/2 - π_Y - τ/2]^N - π/2

XY4/XY8: Dynamical decoupling (robust to pulse errors)

π/2 - [τ - π_X - τ - π_Y - τ - π_X - τ - π_Y]^N - π/2

class sim.pulses.sequences.PulseSequence(pulses=<factory>, delays=<factory>)[source]

Bases: object

Collection of pulses with timing.

Parameters:
pulses

List of pulses in the sequence

Type:

list of Pulse

delays

Delay after each pulse in seconds

Type:

list of float

Examples

>>> seq = PulseSequence()
>>> seq.add_pulse(pi_half_pulse(10), delay=1e-6)
>>> seq.add_pulse(pi_pulse(10), delay=1e-6)
>>> seq.add_pulse(pi_half_pulse(10))
>>> print(f"Total duration: {seq.total_duration()*1e6:.1f} μs")
pulses: List[Pulse]
delays: List[float]
add_pulse(pulse, delay=0.0)[source]

Add a pulse to the sequence.

Parameters:
  • pulse (Pulse) – Pulse to add

  • delay (float) – Delay after the pulse in seconds. Default: 0

add_delay(delay)[source]

Add a delay without a pulse.

Parameters:

delay (float)

total_duration()[source]

Calculate total sequence duration.

Return type:

float

to_awg(awg)[source]

Load the sequence into an AWG interface.

Parameters:

awg (AWGInterface) – AWG interface to load into

__init__(pulses=<factory>, delays=<factory>)
Parameters:
Return type:

None

sim.pulses.sequences.ramsey_sequence(tau, rabi_freq_mhz=1.0, final_phase=0.0, shape='rect')[source]

Create a Ramsey sequence: π/2 - τ - π/2.

Parameters:
  • tau (float) – Free evolution time in seconds

  • rabi_freq_mhz (float) – Rabi frequency for the pulses. Default: 1 MHz

  • final_phase (float) – Phase of the final π/2 pulse in radians. Default: 0

  • shape (str) – Pulse shape. Default: ‘rect’

Returns:

Ramsey sequence

Return type:

PulseSequence

Examples

>>> seq = ramsey_sequence(tau=1e-6, rabi_freq_mhz=10)
>>> print(seq.total_duration())
sim.pulses.sequences.spin_echo_sequence(tau, rabi_freq_mhz=1.0, shape='rect')[source]

Create a spin echo (Hahn echo) sequence: π/2 - τ/2 - π - τ/2 - π/2.

Parameters:
  • tau (float) – Total free evolution time in seconds

  • rabi_freq_mhz (float) – Rabi frequency for the pulses. Default: 1 MHz

  • shape (str) – Pulse shape. Default: ‘rect’

Returns:

Spin echo sequence

Return type:

PulseSequence

sim.pulses.sequences.cpmg_sequence(tau, n_pulses, rabi_freq_mhz=1.0, shape='rect')[source]

Create a CPMG sequence: π/2 - [τ - π_Y - τ]^N - π/2.

Parameters:
  • tau (float) – Delay between refocusing pulses in seconds

  • n_pulses (int) – Number of refocusing π pulses

  • rabi_freq_mhz (float) – Rabi frequency. Default: 1 MHz

  • shape (str) – Pulse shape. Default: ‘rect’

Returns:

CPMG sequence

Return type:

PulseSequence

sim.pulses.sequences.xy4_sequence(tau, n_cycles=1, rabi_freq_mhz=1.0, shape='rect')[source]

Create an XY4 dynamical decoupling sequence.

Structure: π/2 - [τ - π_X - τ - π_Y - τ - π_X - τ - π_Y]^N - π/2

Parameters:
  • tau (float) – Delay between pulses in seconds

  • n_cycles (int) – Number of XY4 cycles. Default: 1

  • rabi_freq_mhz (float) – Rabi frequency. Default: 1 MHz

  • shape (str) – Pulse shape. Default: ‘rect’

Returns:

XY4 sequence

Return type:

PulseSequence

Notes

XY4 is robust to pulse flip-angle errors due to the alternating X and Y phases.

sim.pulses.sequences.xy8_sequence(tau, n_cycles=1, rabi_freq_mhz=1.0, shape='rect')[source]

Create an XY8 dynamical decoupling sequence.

Structure: π/2 - [τ - π_X - τ - π_Y - τ - π_X - τ - π_Y -

τ - π_Y - τ - π_X - τ - π_Y - τ - π_X]^N - π/2

Parameters:
  • tau (float) – Delay between pulses in seconds

  • n_cycles (int) – Number of XY8 cycles. Default: 1

  • rabi_freq_mhz (float) – Rabi frequency. Default: 1 MHz

  • shape (str) – Pulse shape. Default: ‘rect’

Returns:

XY8 sequence

Return type:

PulseSequence

Notes

XY8 is more robust than XY4 due to the symmetric phase pattern.

sim.pulses.sequences.rabi_sequence(duration, rabi_freq_mhz=1.0, shape='rect')[source]

Create a Rabi oscillation sequence (single pulse of variable duration).

Parameters:
  • duration (float) – Pulse duration in seconds

  • rabi_freq_mhz (float) – Rabi frequency in MHz

  • shape (str) – Pulse shape

Returns:

Single-pulse Rabi sequence

Return type:

PulseSequence

PulseSequence Class

from sim.pulses import PulseSequence, pi_pulse, pi_half_pulse

seq = PulseSequence()
seq.add_pulse(pi_half_pulse(10), delay=1e-6)
seq.add_pulse(pi_pulse(10), delay=1e-6)
seq.add_pulse(pi_half_pulse(10))

print(seq.total_duration())

Standard Sequences

Ramsey

Free induction decay (T₂* measurement):

from sim.pulses.sequences import ramsey_sequence

seq = ramsey_sequence(
    tau=1e-6,              # Free evolution time
    rabi_freq_mhz=10,      # π/2 pulse Rabi frequency
    final_phase=0          # Phase of final π/2
)
\[\frac{\pi}{2}_x - \tau - \frac{\pi}{2}_\phi\]

Spin Echo (Hahn Echo)

Refocuses static inhomogeneities (T₂ measurement):

from sim.pulses.sequences import spin_echo_sequence

seq = spin_echo_sequence(
    tau=100e-6,            # Total evolution time
    rabi_freq_mhz=10
)
\[\frac{\pi}{2}_x - \frac{\tau}{2} - \pi_y - \frac{\tau}{2} - \frac{\pi}{2}_x\]

CPMG

Extended coherence with multiple refocusing pulses:

from sim.pulses.sequences import cpmg_sequence

seq = cpmg_sequence(
    tau=10e-6,             # Inter-pulse delay
    n_pulses=8,            # Number of π pulses
    rabi_freq_mhz=10
)
\[\frac{\pi}{2}_x - \left[\frac{\tau}{2} - \pi_y - \frac{\tau}{2}\right]^N - \frac{\pi}{2}_x\]

XY4

Dynamical decoupling robust to pulse errors:

from sim.pulses.sequences import xy4_sequence

seq = xy4_sequence(
    tau=1e-6,              # Inter-pulse delay
    n_cycles=4,            # Number of XY4 cycles
    rabi_freq_mhz=10
)
\[\frac{\pi}{2} - [\tau - \pi_x - \tau - \pi_y - \tau - \pi_x - \tau - \pi_y]^N - \frac{\pi}{2}\]

XY8

More robust variant of XY4:

from sim.pulses.sequences import xy8_sequence

seq = xy8_sequence(
    tau=1e-6,
    n_cycles=2,
    rabi_freq_mhz=10
)

Rabi Oscillation

Single pulse of variable duration:

from sim.pulses.sequences import rabi_sequence

seq = rabi_sequence(
    duration=100e-9,       # Pulse duration
    rabi_freq_mhz=10
)

Custom Sequences

Build arbitrary sequences:

from sim.pulses import PulseSequence, Pulse

def my_sequence(tau, n_pulses, rabi):
    seq = PulseSequence()

    # Initial π/2
    seq.add_pulse(
        Pulse(shape="gaussian", amplitude=rabi,
              duration=25e-9, phase=0),
        delay=tau
    )

    # Refocusing pulses
    for i in range(n_pulses):
        phase = np.pi/2 if i % 2 == 0 else 0
        seq.add_pulse(
            Pulse(shape="gaussian", amplitude=rabi,
                  duration=50e-9, phase=phase),
            delay=tau
        )

    # Final π/2
    seq.add_pulse(
        Pulse(shape="gaussian", amplitude=rabi,
              duration=25e-9, phase=0)
    )

    return seq

AWG Integration

Load sequences into an AWG interface:

from sim.interfaces import AWGInterface

awg = AWGInterface(sample_rate=1e9)
seq.to_awg(awg)
waveform = awg.get_waveform()