Laser Fields

GASFIR represents laser pulses as objects derived from the abstract base class gasfir.Pulse. The concrete implementations are:

  • gasfir.CosNPulse — linearly polarized cosN envelope pulse.

  • gasfir.EllipticalCosNPulse — elliptically polarized cosN envelope pulse.

  • gasfir.DataPulse — pulse from user-supplied E(t) or A(t) numerical data.

  • SumOfPulses — superposition of any number of Pulse objects (gasfir.pulse).

  • gasfir.TIPTOE — pump–probe delay scanner with Nyquist-sampled delay grid.

Creating a Pulse

The convenience factory gasfir.create_pulse() is the recommended entry point:

from gasfir import create_pulse

# create_pulse(wavelength_nm, intensity_W_per_cm2, CEP_rad, duration_optical_cycles)
laser = create_pulse(800, 1e14, 0, 6)

Constructing Directly

For full control over pulse parameters use the class constructor directly. Note that the constructor takes the FWHM in atomic units (not optical cycles); use gasfir.OptCyc_au() to convert from optical cycles:

from gasfir import CosNPulse, OptCyc_au

laser = CosNPulse(
    central_wavelength=800,            # nm
    peak_intensity=1e14,               # W/cm²
    cep=0.0,                           # rad
    FWHM=OptCyc_au(30, 800),           # FWHM in a.u. (30 optical cycles)
    envelope_N=2,                      # power of the cos envelope (default 8)
)

Accessing Field Properties

All Pulse objects expose cached accessors:

import numpy as np

t = laser.get_tgrid(dt=0.25)          # time grid in atomic units
E = laser.get_electric_field(t)        # electric field
A = laser.get_vector_potential(t)      # vector potential

Results are cached internally by grid hash, so repeated calls with the same grid are free.

Units

All internal quantities are in atomic units unless stated otherwise. Use the gasfir.AtomicUnits class for unit conversions:

from gasfir import AtomicUnits

t_fs = t * AtomicUnits.fs          # convert from a.u. to femtoseconds
E_SI = E * AtomicUnits.Volts_per_meter  # convert to V/m

Pump–probe delay scanning — TIPTOE

gasfir.TIPTOE holds a strong pump fixed and scans a weak probe in time, returning a SumOfPulses at each delay. The companion method return_delay_array() applies the Nyquist sampling theorem to produce a correctly-sampled delay grid:

from gasfir import TIPTOE, create_pulse, get_diabatic_ionization_probability, get_parameters

pump  = create_pulse(800, 3e14, 0, 3)   # strong pump
probe = create_pulse(800, 1e13, 0, 1)   # weak probe
scanner = TIPTOE(pump, probe)

# Nyquist-sampled delays: step = T₀/4 by default (4 samples/cycle)
delays = scanner.return_delay_array()
params = get_parameters("H_SFA")

probs = [
    get_diabatic_ionization_probability(pulse=scanner.at_delay(tau), param_dict=params)
    for tau in delays
]

The step size is T₀ / oversample where T₀ = / max(ω_pump, ω_probe) and oversample=4 (default, giving 2× Nyquist margin). Pass a larger value for carrier-envelope resolved scans:

delays_fine = scanner.return_delay_array(oversample=8)

Numerical / data-driven pulses

gasfir.DataPulse wraps an arbitrary numerically-sampled field so it can be used wherever a Pulse is accepted. Supply either the electric field E(t) or the vector potential A(t) on a uniform time grid — both in atomic units:

import numpy as np
from gasfir import DataPulse

# Build a simple sinusoidal A(t) that vanishes at both ends
t = np.linspace(0, 2000, 80_000)          # atomic units
A = np.sin(np.pi * t / t[-1]) * np.sin(0.057 * t)  # vanishes at t=0 and t=T
pulse = DataPulse(t=t, A=A)

# Use it exactly like any other Pulse
from gasfir import get_diabatic_ionization_probability, get_parameters
prob = get_diabatic_ionization_probability(pulse=pulse, param_dict=get_parameters("H_SFA"))

Physical requirement — boundary condition on A. The strong-field ionization integrals require \(A(t) \to 0\) at both the start and end of the time window. If you supply E(t) instead, GASFIR integrates it numerically to obtain A(t); ensure the pulse is switched on and off smoothly so the resulting A satisfies the boundary condition.

Combining pulses. gasfir.SumOfPulses adds two or more Pulse objects together. To create a combination that no analytic class supports, sample the sum onto a grid and feed the result back as a DataPulse:

from gasfir import DataPulse, create_pulse
from gasfir.pulse import SumOfPulses

fundamental = create_pulse(800, 1e14, 0, 6)
harmonic    = create_pulse(400, 2e13, 0, 6)
combined    = SumOfPulses([fundamental, harmonic])  # takes a list

t = combined.get_tgrid(dt=0.25)
A = combined.get_vector_potential(t)
data_pulse = DataPulse(t=t, A=A)   # now usable with any GASFIR routine