solprop.pulses#

Initial pulses on either grid: shape, exact scaling and the user’s spectral phase.

Every builder here is a frozen dataclass with one method, build(grid), which returns the initial spectrum in the canonical (n_modes, n_pol, n_omega) layout of docs/dev/conventions.md §6 with both leading axes equal to one. The spectrum is in the internal FFT convention, ready to be handed to the translation layer.

Signs. Spectral phases, Taylor coefficients and delays are given in the optics convention (docs/dev/conventions.md §2): a positive group-delay dispersion chirps the pulse positively, and a positive t0 makes it arrive later. They are converted to the internal convention exactly once, when the phase factor is formed, with solprop.fourier.to_internal(); a measured absolute spectrum additionally needs the half-window origin shift and goes through solprop.fourier.spectrum_to_internal(). This module is the first of the three boundary places where the optics sign convention meets the internal FFT convention (see docs/dev/decisions.md), and nothing else in it conjugates anything.

Scaling. A pulse is specified by its energy or by its peak_power, never both. Both are realised exactly on the grid, to the round-off of one rescaling, and both are applied after the spectral phase, so the peak power of a chirped pulse is the peak power the caller asked for.

The manual page is docs/guide/setup/pulses.md.

Classes

CWPulse(*, wavelength, average_power[, ...])

A continuous-wave field, monochromatic or with a sech**2 power spectrum.

CustomPulse(*, wavelength[, energy, ...])

A pulse whose power envelope is an arbitrary callable of time.

DataPulse(*, omega, spectrum, energy[, ...])

A pulse from a measured complex spectrum, interpolated onto the grid.

GaussianPulse(*, wavelength[, energy, ...])

A (super-)Gaussian pulse of given intensity FWHM.

IncidentField(field, energy)

An incident beam, normalised over its whole plane, sampled at given points.

PlanarPulse(temporal, waist[, order, propz, ...])

Launch an astigmatic Gaussian beam into the hybrid planar grid.

Pulse(*args, **kwargs)

The one method every input pulse provides.

PulseSum(pulses)

Several pulses launched together: the input is the sum of their fields.

SechPulse(*, wavelength[, energy, ...])

A sech**2 pulse, specified by its FWHM or by its natural width.

ShapedPulse(*, wavelength[, energy, ...])

Common parameters and build path of a pulse defined by its power envelope.

SpatialPulse(temporal, waist[, order, ...])

Wrap a temporal pulse in an energy-normalised transverse beam.

class solprop.pulses.CWPulse(*, wavelength, average_power, bandwidth=0.0, seed=None)[source]#

Bases: Displayed

A continuous-wave field, monochromatic or with a sech**2 power spectrum.

The spectrum is written straight onto the grid’s own bins, so the field is periodic in the time window by construction and needs no apodisation; the average power is then realised in one pass over the flat part of the temporal window, rather than by alternating between a fixed spectral magnitude and a windowed field until the two agree. The single pass trades a slightly perturbed spectral shape for an exact power and a bounded cost.

Attributes:
wavelengthfloat

Centre wavelength, m. Must fall inside the grid’s active band.

average_powerfloat

Average power, W, over the samples where the temporal collar is exactly one.

bandwidthfloat

Full width at half maximum of the power spectrum, in wavelength, m. Zero, the default, gives a monochromatic field on the single nearest bin. It is mapped to frequency at first order, Δω = 2πcΔλ/λ² at the centre wavelength: the sech**2 spectrum has that frequency FWHM, the frequency equivalent of the requested width, and its wavelength FWHM is bandwidth only in the narrowband limit.

seedint or None

Seed of the uniform random spectral phase used when bandwidth is positive, so that a realisation is reproducible. Unused for a monochromatic field.

Raises:
PulseError

If the wavelength, power or bandwidth is invalid.

Examples

>>> import numpy as np
>>> from solprop.grid import EnvelopeGrid
>>> from solprop.pulses import CWPulse
>>> grid = EnvelopeGrid.from_limits(1.0, 1030e-9, (900e-9, 1.2e-6), 20e-12)
>>> spectrum = CWPulse(wavelength=1030e-9, average_power=5.0).build(grid)
>>> spectrum.shape[:2]
(1, 1)
average_power: float#
bandwidth: float = 0.0#
build(grid)[source]#

Return the continuous-wave spectrum on the given grid.

Parameters:
gridsolprop.grid.TimeGrid

Grid to build on.

Returns:
numpy.ndarray

Stored spectrum, shape (1, 1, n_omega), in the square root of watts.

Raises:
PulseError

If the centre wavelength falls outside the grid’s active band.

Examples

>>> import numpy as np
>>> from solprop import fourier
>>> from solprop.grid import EnvelopeGrid
>>> from solprop.pulses import CWPulse
>>> grid = EnvelopeGrid.from_limits(1.0, 1030e-9, (900e-9, 1.2e-6), 20e-12)
>>> spectrum = CWPulse(wavelength=1030e-9, average_power=5.0).build(grid)
>>> samples = fourier.inverse(spectrum[0, 0], kind=grid.kind)
>>> flat = grid.t_window == 1.0
>>> bool(np.isclose(np.mean(np.abs(samples[flat]) ** 2), 5.0, rtol=1e-12))
True
seed: int | None = None#
wavelength: float#
class solprop.pulses.CustomPulse(*, wavelength, energy=None, peak_power=None, phase=(), t0=0.0, shape_fn)[source]#

Bases: ShapedPulse

A pulse whose power envelope is an arbitrary callable of time.

Attributes:
wavelengthfloat

Centre wavelength of the pulse, m, and the reference of the Taylor phase.

shape_fncallable

I(t) returning the power envelope for an array of times in seconds. It must be finite and non-negative on the grid; its normalisation is irrelevant.

energyfloat or None

Pulse energy, J. Exactly one of energy and peak_power is given.

peak_powerfloat or None

Peak power, W, realised after the spectral phase.

phasearray_like

Optics-convention Taylor coefficients about wavelength: radians at order 0 and s**n at order n thereafter.

t0float

Delay, s, positive for later arrival.

Examples

>>> import numpy as np
>>> from solprop.pulses import CustomPulse
>>> triangle = CustomPulse(
...     wavelength=800e-9,
...     shape_fn=lambda t: np.clip(1.0 - np.abs(t) / 20e-15, 0.0, None),
...     energy=1e-6,
... )
>>> float(triangle.intensity(np.array([10e-15]))[0])
0.5
intensity(t)[source]#

Return the caller’s power envelope evaluated on the time samples.

Parameters:
tnumpy.ndarray

Time samples, s, shape (n_t,).

Returns:
numpy.ndarray

The value of shape_fn(t), shape (n_t,).

Examples

>>> import numpy as np
>>> from solprop.pulses import CustomPulse
>>> flat = CustomPulse(
...     wavelength=800e-9, shape_fn=np.ones_like, energy=1e-6
... )
>>> flat.intensity(np.zeros(3)).tolist()
[1.0, 1.0, 1.0]
shape_fn: Callable[[Float[ndarray, 'n_t']], Float[ndarray, 'n_t']]#
class solprop.pulses.DataPulse(*, omega, spectrum, energy, phase=(), t0=0.0, wavelength=None)[source]#

Bases: Displayed

A pulse from a measured complex spectrum, interpolated onto the grid.

The stored form is the spectrum because that is what a measurement gives: a spectral power density and a retrieved phase. The phase is absolute and referenced to t = 0 in the optics convention, so it fixes where in the window the pulse sits; it therefore goes through solprop.fourier.spectrum_to_internal(), which carries the half-window origin shift as well as the conjugation.

Magnitude and unwrapped phase are interpolated separately with a cubic B-spline, never the complex spectrum itself, whose real and imaginary parts oscillate. Outside the data’s own frequency range the spectrum is zero, and the grid’s spectral collar is applied so that nothing is left sitting on the window edge.

Attributes:
omeganumpy.ndarray

Angular frequency axis of the data, rad/s, shape (n_data,), strictly increasing and positive. It need not be uniform.

spectrumnumpy.ndarray

Measured complex spectrum on omega, shape (n_data,), optics convention, arbitrary units.

energyfloat

Pulse energy to scale to, J.

phasearray_like

Further optics-convention Taylor coefficients, applied on top of the phase already in the data, about wavelength; radians at order 0 and s**n thereafter.

t0float

Further delay, s, on top of the arrival time the data’s own phase implies.

wavelengthfloat or None

Reference wavelength of the Taylor phase, m. Defaults to the spectral centre of mass of the data.

Raises:
PulseError

If the two arrays disagree in length, are too short to interpolate, are not finite, or omega is not strictly increasing and positive.

Examples

>>> import numpy as np
>>> from solprop.pulses import DataPulse
>>> omega = np.linspace(1.5e15, 3.5e15, 256)
>>> intensity = np.exp(-(((omega - 2.35e15) / 1e14) ** 2))
>>> pulse = DataPulse.from_spectral_density(
...     omega, intensity, np.zeros_like(omega), energy=1e-6
... )
>>> float(round(pulse.reference_wavelength * 1e9))
802.0
build(grid)[source]#

Return the initial spectrum of the tabulated pulse on the given grid.

Parameters:
gridsolprop.grid.TimeGrid

Grid to build on.

Returns:
numpy.ndarray

Stored spectrum, shape (1, 1, n_omega), in the square root of watts.

Raises:
PulseError

If the delay leaves the window, or the data does not reach the grid’s active band at all.

Examples

>>> import numpy as np
>>> from solprop.grid import FieldGrid
>>> from solprop.pulses import DataPulse
>>> omega = np.linspace(4e14, 1.1e16, 4096)  # covers the grid's active band
>>> intensity = np.exp(-(((omega - 2.35e15) / 1e14) ** 2))
>>> pulse = DataPulse.from_spectral_density(
...     omega, intensity, np.zeros_like(omega), energy=1e-6
... )
>>> grid = FieldGrid.from_limits(1.0, 800e-9, (200e-9, 2e-6), 400e-15)
>>> pulse.build(grid).shape[:2]
(1, 1)
energy: float#
classmethod from_spectral_density(omega, intensity, spectral_phase, *, energy, phase=(), t0=0.0, wavelength=None)[source]#

Build a pulse from a spectral power density and an unwrapped spectral phase.

This is the form a spectrometer and a phase retrieval give. Negative densities, which fits and baseline subtraction can produce, are clipped to zero.

Parameters:
omeganumpy.ndarray

Angular frequency axis, rad/s, shape (n_data,), strictly increasing.

intensitynumpy.ndarray

Spectral power density on omega, arbitrary units, shape (n_data,).

spectral_phasenumpy.ndarray

Unwrapped optics-convention spectral phase on omega, rad, shape (n_data,).

energyfloat

Pulse energy to scale to, J.

phasearray_like, optional

Further Taylor coefficients about wavelength, s**n at order n.

t0float, optional

Further delay, s.

wavelengthfloat or None, optional

Reference wavelength of the Taylor phase, m.

Returns:
DataPulse

The pulse, with spectrum = sqrt(intensity) exp(i spectral_phase).

Raises:
PulseError

If the three arrays disagree in length, or the data fails validation.

Examples

>>> import numpy as np
>>> from solprop.pulses import DataPulse
>>> omega = np.linspace(2.0e15, 2.7e15, 64)
>>> pulse = DataPulse.from_spectral_density(
...     omega, np.ones_like(omega), np.zeros_like(omega), energy=1e-6
... )
>>> pulse.spectrum.shape
(64,)
classmethod from_time(t, field, *, energy, phase=(), t0=0.0, wavelength=None)[source]#

Build a pulse from measured time-domain samples on a uniform axis.

The optics-convention spectrum referenced to t = 0 is sum_n E_n exp(+i omega t_n), which is evaluated as an inverse DFT times a linear phase for the axis offset, so any t[0] is handled exactly and no assumption is made about where the pulse sits in the record. Only the non-negative frequencies are kept, and the zero-frequency bin is dropped: a spectral phase is meaningless there.

A real array is the electric field. A complex array is taken as the analytic signal in the optics convention, so that a positive frequency means a positive frequency, as everywhere else at the interface.

Parameters:
tnumpy.ndarray

Time samples, s, shape (n_data,), uniformly spaced and increasing.

fieldnumpy.ndarray

Measured field on t, arbitrary units, shape (n_data,).

energyfloat

Pulse energy to scale to, J.

phasearray_like, optional

Further Taylor coefficients about wavelength, s**n at order n.

t0float, optional

Further delay, s.

wavelengthfloat or None, optional

Reference wavelength of the Taylor phase, m.

Returns:
DataPulse

The pulse, holding the spectrum of the record.

Raises:
PulseError

If the arrays disagree in length, the axis is not uniform and increasing, or the record is too short.

Examples

>>> import numpy as np
>>> from solprop.pulses import DataPulse
>>> t = np.linspace(-100e-15, 100e-15, 1024)
>>> field = np.exp(-((t / 20e-15) ** 2)) * np.cos(2.35e15 * t)
>>> pulse = DataPulse.from_time(t, field, energy=1e-6)
>>> float(round(pulse.reference_wavelength * 1e9))
802.0
omega: Float[ndarray, 'n_data']#
phase: Sequence[float] | ndarray = ()#
property reference_wavelength: float[source]#

Return the wavelength the Taylor phase is expanded about, m.

Returns:
float

wavelength if it was given, otherwise the wavelength of the spectral centre of mass of the data.

spectrum: Complex[ndarray, 'n_data_spectrum']#
t0: float = 0.0#
wavelength: float | None = None#
class solprop.pulses.GaussianPulse(*, wavelength, energy=None, peak_power=None, phase=(), t0=0.0, fwhm, m=1)[source]#

Bases: ShapedPulse

A (super-)Gaussian pulse of given intensity FWHM.

The envelope is exp(-(t/sigma)**(2m) / 2) with sigma = fwhm / (2 (2 ln 2)**(1/2m)), which realises the requested FWHM for every order m.

Attributes:
wavelengthfloat

Centre wavelength of the pulse, m, and the reference of the Taylor phase.

fwhmfloat

Full width at half maximum of the power envelope, s.

mint

Super-Gaussian order, at least 1. The exponent of the envelope is 2m, so m = 1 is an ordinary Gaussian and large m approaches a flat top.

energyfloat or None

Pulse energy, J. Exactly one of energy and peak_power is given.

peak_powerfloat or None

Peak power, W, realised after the spectral phase.

phasearray_like

Optics-convention Taylor coefficients about wavelength: radians at order 0 and s**n at order n thereafter.

t0float

Delay, s, positive for later arrival.

Raises:
PulseError

If the duration or the order is invalid, or the scaling request is ambiguous.

Examples

>>> import numpy as np
>>> from solprop.pulses import GaussianPulse
>>> pulse = GaussianPulse(wavelength=800e-9, fwhm=30e-15, energy=1e-6)
>>> float(pulse.intensity(np.array([0.0, 15e-15]))[1])
0.5
fwhm: float#
intensity(t)[source]#

Return the (super-)Gaussian power envelope, normalised to a peak of one.

Parameters:
tnumpy.ndarray

Time samples, s, shape (n_t,).

Returns:
numpy.ndarray

Envelope values in [0, 1], shape (n_t,).

Examples

>>> import numpy as np
>>> from solprop.pulses import GaussianPulse
>>> pulse = GaussianPulse(wavelength=800e-9, fwhm=2.0, energy=1.0, m=3)
>>> float(round(pulse.intensity(np.array([1.0]))[0], 12))
0.5
m: int = 1#
class solprop.pulses.IncidentField(field, energy)[source]#

Bases: object

An incident beam, normalised over its whole plane, sampled at given points.

The result of SpatialPulse.incident().

Attributes:
fieldnumpy.ndarray

The beam’s x and y components at the points, complex128, shape (n_nodes, 2, n_omega), in the internal convention: the electric field in V/m per square root of watt times a stored spectrum in the square root of watts, the units in which a guide’s node profiles project to channel amplitudes.

energynumpy.ndarray

The energy each bin carries through the whole plane, float64, shape (n_omega,), J; its sum is the beam’s incident energy.

energy: Float[ndarray, 'n_omega']#
field: Complex[ndarray, 'n_nodes n_pol n_omega']#
class solprop.pulses.PlanarPulse(temporal, waist, order=1, propz=0.0, jones=(1 + 0j,), *, waist_x, offset_x=0.0)[source]#

Bases: SpatialPulse

Launch an astigmatic Gaussian beam into the hybrid planar grid.

The beam is exp(-((x - offset_x)/waist_x)**2) exp(-(|y|/waist)**(2 order)) in field, carrying the temporal pulse, polarised along the slab modes’ own component (y for TE, x for TM). In x it is projected onto the slab modes (solprop.modes.slab.gaussian_projection(), with signs); in y it is sampled on the grid and transformed. The temporal pulse’s energy or peak power is the incident beam’s, over the whole beam in x and the sampled y axis: what falls on the plates is lost at the entrance, so the modes carry the coupled share of it (98.8 % into p = 1 at waist_x = 0.735 a, Arnold et al. 2010), not a beam renormalised over the gap. The geometry integrates over y, so the energy is in J and the peak power in W.

Attributes:
temporalPulse

Temporal pulse shape; its energy (J) or peak power (W) is the incident beam’s.

waistfloat

The 1/e field radius in y (the 1/e^2 intensity radius), m, positive.

orderint

Super-Gaussian order in y, at least 1.

propzfloat

Must be 0: the launch is at the entrance plane; a free-space pre-propagation before a guide is refused.

jonestuple of complex

One component, a phase; the component itself is the modes’. Two components are refused, the planar state being scalar.

waist_xfloat

The 1/e field radius in x, m, positive (keyword).

offset_xfloat

The beam centre’s displacement from the mid-plane in x, m (keyword); an offset couples to even orders.

Raises:
PulseError

If a waist, the order, offset_x, propz or jones is invalid.

Examples

>>> import numpy as np
>>> from solprop.grid import EnvelopeGrid
>>> from solprop.modes import PlanarGrid, SlabMode
>>> from solprop.pulses import GaussianPulse, PlanarPulse
>>> from solprop.spatial import SpatialMedium
>>> grid = EnvelopeGrid.from_limits(0.1, 800e-9, (650e-9, 1000e-9), 400e-15)
>>> space = PlanarGrid([SlabMode(63.5e-6, order=p) for p in (1, 3)], 1e-3, 32)
>>> pulse = PlanarPulse(
...     GaussianPulse(wavelength=800e-9, fwhm=100e-15, energy=1e-3),
...     250e-6, waist_x=0.735 * 63.5e-6,
... )
>>> pulse.build(grid, space, medium=SpatialMedium()).shape
(64, 1, 256)
build(grid, space, *, medium, z=0.0, side=None, frame=None)[source]#

Build the launched (p, k_y) spectrum on a planar grid.

The requested energy or peak power is the incident beam’s physical axial flux in the fill medium, over the whole beam in x and the sampled y axis (SEP 34): the x integral of the continuous Gaussian’s angular spectrum is taken by Gauss-Hermite quadrature of its admittance. The slab modes then carry the coupled share of that beam.

Parameters:
gridsolprop.grid.TimeGrid

Field or envelope temporal grid.

spacesolprop.modes.slab.PlanarGrid

The slab modes in x and the y axis; any other grid is refused.

mediumsolprop.spatial.SpatialMedium

The fill medium at the entrance.

zfloat, optional

Position of the entrance plane in m.

side{‘before’, ‘after’} or None, optional

Side of a declared index jump at z; required there.

framesolprop.linear.Frame or None, optional

Unused: the launch is at the entrance, with no pre-propagation.

Returns:
numpy.ndarray

Modal amplitudes in sqrt(W/m), raw-DFT transformed in y, times the temporal spectrum, shape (n_p*n_y, 1, n_omega), the mode index fastest, in the internal convention.

Raises:
PulseError

If the grid is not a planar one, the temporal pulse is not scalar, or the beam has no propagating content on the y axis.

offset_x: float = 0.0#
waist_x: float#
class solprop.pulses.Pulse(*args, **kwargs)[source]#

Bases: Protocol

The one method every input pulse provides.

A pulse is a specification, not an array: it is built against a particular grid, and the same specification may be built against a field grid and an envelope grid to give the same physical pulse in the two representations.

Examples

>>> from solprop.grid import EnvelopeGrid
>>> from solprop.pulses import GaussianPulse, Pulse
>>> pulse: Pulse = GaussianPulse(wavelength=800e-9, fwhm=30e-15, energy=1e-6)
>>> grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 2e-12)
>>> pulse.build(grid).shape[:2]
(1, 1)
build(grid)[source]#

Return the initial spectrum of this pulse on the given grid.

Parameters:
gridsolprop.grid.TimeGrid

Grid to build on; its kind selects the field or envelope layout.

Returns:
numpy.ndarray

Stored spectrum, shape (n_modes, n_pol, n_omega), complex, in the square root of watts and in the internal FFT convention.

class solprop.pulses.PulseSum(pulses)[source]#

Bases: Displayed

Several pulses launched together: the input is the sum of their fields.

Two-colour and multi-colour inputs – a pump and a seed for four-wave mixing, a strong pump and a weak probe for cross-phase modulation, a pulse and its delayed replica – are the sum of independently specified pulses. Each member keeps its own centre wavelength, duration, energy or peak power, spectral phase and delay, and is built and scaled on the grid exactly as it would be alone; the spectra are then added. The energies of well-separated members therefore add, and members that overlap in time and frequency interfere, as the fields do.

Attributes:
pulsestuple of Pulse

The members, at least one; any object with a build(grid) method whose spectra share a shape, typically GaussianPulse, SechPulse, CustomPulse or DataPulse.

Examples

>>> import numpy as np
>>> from solprop import fourier
>>> from solprop.grid import FieldGrid
>>> from solprop.pulses import GaussianPulse, PulseSum
>>> grid = FieldGrid.from_limits(1.0, 800e-9, (150e-9, 3e-6), 1e-12)
>>> pump = GaussianPulse(wavelength=267e-9, fwhm=35e-15, energy=95e-6)
>>> seed = GaussianPulse(wavelength=800e-9, fwhm=35e-15, energy=45e-6)
>>> spectrum = PulseSum((pump, seed)).build(grid)
>>> energy = fourier.energy_omega(spectrum[0, 0], grid.dt, kind=grid.kind)
>>> bool(np.isclose(energy, 140e-6, rtol=1e-6))
True
build(grid)[source]#

Return the sum of the members’ spectra on the given grid.

Parameters:
gridsolprop.grid.TimeGrid

Grid to build on; every member is built on it independently.

Returns:
numpy.ndarray

Stored spectrum, the members’ common shape, in the square root of watts.

Raises:
PulseError

If the members build to spectra of different shapes.

pulses: tuple[Pulse, ...]#
class solprop.pulses.SechPulse(*, wavelength, energy=None, peak_power=None, phase=(), t0=0.0, fwhm=None, width=None)[source]#

Bases: ShapedPulse

A sech**2 pulse, specified by its FWHM or by its natural width.

Attributes:
wavelengthfloat

Centre wavelength of the pulse, m, and the reference of the Taylor phase.

fwhmfloat or None

Full width at half maximum of the power envelope, s. Exactly one of fwhm and width is given.

widthfloat or None

Natural width of sech**2(t/width), s, which is fwhm / (2 ln(1 + sqrt(2))).

energyfloat or None

Pulse energy, J. Exactly one of energy and peak_power is given.

peak_powerfloat or None

Peak power, W, realised after the spectral phase.

phasearray_like

Optics-convention Taylor coefficients about wavelength: radians at order 0 and s**n at order n thereafter.

t0float

Delay, s, positive for later arrival.

Raises:
PulseError

If both or neither duration is given, or the scaling request is ambiguous.

Examples

>>> import numpy as np
>>> from solprop.pulses import SechPulse
>>> pulse = SechPulse(wavelength=1030e-9, fwhm=100e-15, energy=10e-6)
>>> float(round(pulse.intensity(np.array([50e-15]))[0], 12))
0.5
fwhm: float | None = None#
intensity(t)[source]#

Return the sech**2 power envelope, normalised to a peak of one.

Parameters:
tnumpy.ndarray

Time samples, s, shape (n_t,).

Returns:
numpy.ndarray

Envelope values in (0, 1], shape (n_t,).

Examples

>>> import numpy as np
>>> from solprop.pulses import SechPulse
>>> pulse = SechPulse(wavelength=1030e-9, width=1.0, energy=1.0)
>>> float(pulse.intensity(np.array([0.0]))[0])
1.0
width: float | None = None#
class solprop.pulses.ShapedPulse(*, wavelength, energy=None, peak_power=None, phase=(), t0=0.0)[source]#

Bases: Displayed

Common parameters and build path of a pulse defined by its power envelope.

GaussianPulse, SechPulse and CustomPulse derive from this class: it holds the parameters they share and the whole of build() (the carrier, the spectral phase, the exact scaling and the canonical layout), so that the three differ only in one line of arithmetic. Subclass it and supply intensity() to add a shape of your own; it is abstract here.

Attributes:
wavelengthfloat

Centre wavelength of the pulse, m. Also the frequency the Taylor spectral phase is expanded about, which need not be the grid’s reference wavelength.

energyfloat or None

Pulse energy, J. Exactly one of energy and peak_power is given.

peak_powerfloat or None

Peak power, W, realised after the spectral phase is applied.

phasearray_like

Optics-convention spectral phase as Taylor coefficients about wavelength: [CEP (rad), group delay (s), GDD (s**2), TOD (s**3), ...]. Empty by default.

t0float

Delay, s, positive for later arrival. Must lie strictly inside the window.

build(grid)[source]#

Return the initial spectrum of this pulse on the given grid.

On a field grid the samples are sqrt(I(t)) cos(omega_p t), whose analytic signal is the pulse; on an envelope grid they are sqrt(I(t)) exp(i (omega_p - omega0) t), which is that analytic signal with the grid’s carrier removed (conventions.md §4). The spectral phase is applied to the stored spectrum and the scaling comes last.

Parameters:
gridsolprop.grid.TimeGrid

Grid to build on.

Returns:
numpy.ndarray

Stored spectrum, shape (1, 1, n_omega), in the square root of watts.

Raises:
PulseError

If the delay leaves the window, the envelope is not finite and non-negative, or the pulse carries nothing to scale on this grid.

Examples

>>> import numpy as np
>>> from solprop import fourier
>>> from solprop.grid import EnvelopeGrid
>>> from solprop.pulses import GaussianPulse
>>> grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 2e-12)
>>> spectrum = GaussianPulse(
...     wavelength=800e-9, fwhm=30e-15, energy=1e-6
... ).build(grid)
>>> energy = fourier.energy_omega(spectrum[0, 0], grid.dt, kind=grid.kind)
>>> bool(np.isclose(energy, 1e-6, rtol=1e-12))
True
energy: float | None = None#
intensity(t)[source]#

Return the power envelope of the pulse, in arbitrary units.

The normalisation is irrelevant: the spectrum is rescaled to the requested energy or peak power afterwards.

Parameters:
tnumpy.ndarray

Time samples, s, shape (n_t,).

Returns:
numpy.ndarray

Non-negative power envelope, shape (n_t,).

Raises:
NotImplementedError

Always; each shaped pulse supplies its own envelope.

peak_power: float | None = None#
phase: Sequence[float] | ndarray = ()#
t0: float = 0.0#
wavelength: float#
class solprop.pulses.SpatialPulse(temporal, waist, order=1, propz=0.0, jones=(1 + 0j,))[source]#

Bases: Displayed

Wrap a temporal pulse in an energy-normalised transverse beam.

Attributes:
temporalPulse

Existing temporal pulse shape. Its energy or peak power sets the whole beam’s physical energy or peak axial power through the launch plane, in the medium there (SEP 34). On a solprop.grid.LineGrid the beam is uniform in y and these are per metre of y: the energy is in J/m and the peak power in W/m (solprop.modes.slab.gaussian_to_per_unit_width() converts a beam of finite height).

waistfloat

Positive finite 1/e^2 intensity radius in m; on a line, the 1/e^2 intensity half-width in x.

orderint

Super-Gaussian order, at least 1. The field profile is exp(-(r/waist)**(2*order)), with r = |x| on a line.

propzfloat

Signed launch pre-propagation distance in m, finite. Applied by the exact longitudinal operator with the launch plane’s material frozen. Zero launches at the waist; negative values launch before it. Normalisation follows the pre-propagation, so the launch plane carries the requested quantity.

jonestuple of complex

One scalar component or two optics-convention Cartesian components (x, y). The vector is normalised to unit squared magnitude. Default is scalar.

Raises:
PulseError

If the waist, order, distance or Jones vector is invalid.

build(grid, space, *, medium, z=0.0, side=None, frame=None)[source]#

Build the beam’s transverse and temporal spectrum on a spatial grid.

The requested energy or peak power is the physical axial Poynting flux through the launch plane, in the medium there (SEP 34): what an energy meter in that medium reads. Each member of a PulseSum carries its own requested quantity. The inactive band and zero frequency are hard-masked as everywhere; a beam with more than rounding-level content in nonpropagating columns is refused, since no energy can be assigned to it.

Parameters:
gridsolprop.grid.TimeGrid

Field or envelope temporal grid.

spacesolprop.grid.RadialGrid or solprop.grid.FreeGrid

Transverse samples and their spectral integration measure. A solprop.modes.slab.PlanarGrid is refused: its launch is a PlanarPulse.

mediumsolprop.spatial.SpatialMedium

The medium at the launch plane; there is no vacuum default.

zfloat, optional

Position of the launch plane in m, at which the medium is evaluated.

side{‘before’, ‘after’} or None, optional

Side of a declared index jump at z; required there.

framesolprop.linear.Frame or None, optional

Launch frame, normally the run’s frame. None uses the grid’s co-moving frame without envelope THG.

Returns:
numpy.ndarray

Hankel/temporal spectrum in V m or Cartesian DFT/temporal spectrum in V/m, shape (n_modes, n_pol, n_omega), in the internal convention.

Raises:
PulseError

If the grid is a planar one, the temporal pulse is not scalar, the beam has no propagating content or more than rounding-level nonpropagating content, or launch pre-propagation would overflow the field.

incident(grid, xy, *, index)[source]#

Sample the beam, normalised over its whole incident plane, at given points.

The incident-field boundary of a guide launch (SEP 31, phase 1): the beam is given its requested energy or peak power over the whole transverse plane and only then sampled where a receiving guide wants it, so light that misses the guide still counts as incident and is lost, not restored. The measure is the paraxial local one, \(M_0 = Y_0\,dA/2\) with \(Y_0 = n_{\rm ref}(\omega)\varepsilon_0 c\): the axial Poynting flux of a paraxial beam in a medium of phase index \(n_{\rm ref}\), which is what an energy meter in that medium reads (SEP 34). The plane integral of the super-Gaussian profile \(u = \exp(-(r/w)^{2N})\) is exact, \(\int|u|^2 dA = \pi w^2 2^{-1/N}\Gamma(1 + 1/N)\), so no transverse window or quadrature enters the incident energy.

A one-component Jones vector is polarised along x; two components are the x and y components. The beam is centred on the origin of xy.

Parameters:
gridsolprop.grid.TimeGrid

Temporal grid.

xynumpy.ndarray

Transverse sample positions in m, shape (n_nodes, 2).

indexnumpy.ndarray

Real phase index of the launch medium at each of the grid’s frequencies, shape (n_omega,), positive on every bin that carries flux; a zero bin carries none and is masked.

Returns:
IncidentField

The normalised field at the points and the energy each bin carries through the whole plane.

Raises:
PulseError

If the beam is a PlanarPulse or is launched away from its waist, the index is not finite and non-negative on the grid, the temporal pulse is not scalar, or the beam carries no forward flux.

Examples

>>> import numpy as np
>>> from solprop.grid import FieldGrid
>>> from solprop.pulses import GaussianPulse, SpatialPulse
>>> grid = FieldGrid.from_limits(1e-3, 800e-9, (700e-9, 900e-9), 200e-15)
>>> beam = SpatialPulse(
...     GaussianPulse(wavelength=800e-9, fwhm=30e-15, energy=1e-6), 50e-6
... )
>>> index = np.where(grid.omega > 0, 1.45, 0.0)
>>> sampled = beam.incident(grid, np.zeros((1, 2)), index=index)
>>> f"{np.sum(sampled.energy):.6e} J"  # the whole plane, wherever it is sampled
'1.000000e-06 J'
jones: tuple[complex, ...] = ((1+0j),)#
order: int = 1#
propz: float = 0.0#
temporal: Pulse#
waist: float#