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.
Functions
|
Read a measured spectral power density and phase from a text file. |
Classes
|
A continuous-wave field, monochromatic or with a |
|
A pulse whose power envelope is an arbitrary callable of time. |
|
A pulse from a measured complex spectrum, interpolated onto the grid. |
|
A (super-)Gaussian pulse of given intensity FWHM. |
|
An incident beam, normalised over its whole plane, sampled at given points. |
|
Launch an astigmatic Gaussian beam into the hybrid planar grid. |
|
The one method every input pulse provides. |
|
Several pulses launched together: the input is the sum of their fields. |
|
A |
|
Common parameters and build path of a pulse defined by its power envelope. |
|
Wrap a temporal pulse in an energy-normalised transverse beam. |
|
A pulse from a measured field in time, transformed exactly onto the grid. |
- class solprop.pulses.CWPulse(*, wavelength, average_power, bandwidth=0.0, seed=None)[source]#
Bases:
DisplayedA continuous-wave field, monochromatic or with a
sech**2power 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 gives the requested spectral shape and the requested power at 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: thesech**2spectrum has that frequency FWHM, the frequency equivalent of the requested width, and its wavelength FWHM isbandwidthonly in the narrowband limit.- seedint or None
Seed of the uniform random spectral phase used when
bandwidthis 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)
- 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
- class solprop.pulses.CustomPulse(*, wavelength, energy=None, peak_power=None, phase=(), t0=0.0, shape_fn)[source]#
Bases:
ShapedPulseA 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
energyandpeak_poweris 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 ands**nat ordernthereafter.- 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]
- class solprop.pulses.DataPulse(*, omega, spectrum, energy, spectral_phase=None, phase=(), t0=0.0, wavelength=None)[source]#
Bases:
DisplayedA 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 = 0in the optics convention, so it fixes where in the window the pulse sits; it therefore goes throughsolprop.fourier.spectrum_to_internal(), which carries the half-window origin shift as well as the conjugation.Magnitude and 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.
The phase interpolated is
spectral_phasewhen the pulse holds one, asfrom_spectral_density()makes it, however fast that phase varies. Otherwise it isnumpy.unwrap(numpy.angle(spectrum)), which cannot tell a phase change of more than pi between neighbouring samples from a smaller one of the other sign: such a spectrum, of a strongly chirped pulse or one far fromt = 0on a coarse axis, is built with the wrong phase and without any warning. Give that phase throughfrom_spectral_density(), or sample it more finely.A measured field in time is a
TimeDataPulse, which needs no interpolation.- 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.- spectral_phasenumpy.ndarray or None
Optics-convention phase of
spectrum, unwrapped, rad, shape(n_data,), interpolated as it stands. It must agree withspectrummodulo2 pi.None, the default, recovers the phase fromspectrumby unwrapping it.- 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 ands**nthereafter.- 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 arrays disagree in length, are too short to interpolate, are not finite,
omegais not strictly increasing and positive, orspectral_phaseis not the phase ofspectrum.
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)
- 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. The pulse keeps the phase as given and
build()interpolates it directly, so it may change by any amount between samples, provided a cubic spline resolves it.- 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
Continuous (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**nat ordern.- 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)and the phase held asspectral_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,)
- omega: Float[ndarray, 'n_data']#
- property reference_wavelength: float[source]#
Return the wavelength the Taylor phase is expanded about, m.
- Returns:
- float
wavelengthif it was given, otherwise the wavelength of the spectral centre of mass of the data.
- spectrum: Complex[ndarray, 'n_data_spectrum']#
- class solprop.pulses.GaussianPulse(*, wavelength, energy=None, peak_power=None, phase=(), t0=0.0, fwhm, m=1)[source]#
Bases:
ShapedPulseA (super-)Gaussian pulse of given intensity FWHM.
The envelope is
exp(-(t/sigma)**(2m) / 2)withsigma = fwhm / (2 (2 ln 2)**(1/2m)), which realises the requested FWHM for every orderm.- 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, som = 1is an ordinary Gaussian and largemapproaches a flat top.- energyfloat or None
Pulse energy, J. Exactly one of
energyandpeak_poweris 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 ands**nat ordernthereafter.- 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
- 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
- class solprop.pulses.IncidentField(field, energy)[source]#
Bases:
objectAn 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,), displacement=(0.0, 0.0), tilt=(0.0, 0.0), *, waist_x, offset_x=0.0)[source]#
Bases:
SpatialPulseLaunch 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 (yfor TE,xfor 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 % intop = 1atwaist_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.
- class solprop.pulses.Pulse(*args, **kwargs)[source]#
Bases:
ProtocolThe 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
kindselects 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:
DisplayedSeveral 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, typicallyGaussianPulse,SechPulse,CustomPulse,DataPulseorTimeDataPulse.
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.
- class solprop.pulses.SechPulse(*, wavelength, energy=None, peak_power=None, phase=(), t0=0.0, fwhm=None, width=None)[source]#
Bases:
ShapedPulseA
sech**2pulse, 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
fwhmandwidthis given.- widthfloat or None
Natural width of
sech**2(t/width), s, which isfwhm / (2 ln(1 + sqrt(2))).- energyfloat or None
Pulse energy, J. Exactly one of
energyandpeak_poweris 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 ands**nat ordernthereafter.- 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
- intensity(t)[source]#
Return the
sech**2power 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
- class solprop.pulses.ShapedPulse(*, wavelength, energy=None, peak_power=None, phase=(), t0=0.0)[source]#
Bases:
DisplayedCommon parameters and build path of a pulse defined by its power envelope.
GaussianPulse,SechPulseandCustomPulsederive from this class: it holds the parameters they share and the whole ofbuild()(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 supplyintensity()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
energyandpeak_poweris 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 aresqrt(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
- 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.
- class solprop.pulses.SpatialPulse(temporal, waist, order=1, propz=0.0, jones=(1 + 0j,), displacement=(0.0, 0.0), tilt=(0.0, 0.0))[source]#
Bases:
DisplayedWrap 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.LineGridthe 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)), withr = |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.
- displacementtuple of float
(d_x, d_y)in m, finite: the beam’s centre at the launch plane. A positive value moves it towards +x (+y). Default none.- tilttuple of float
(theta_x, theta_y)in rad, each below pi/2 in magnitude: a positive tilt makes the beam propagate towards +x (+y). Default none.
- Raises:
- PulseError
If the waist, order, distance, Jones vector, displacement or tilt is invalid.
Notes
Displacement and tilt are those of
solprop.coupling.elements.Alignment, applied at the launch plane after propz: the tilt multiplies the field byexp(i k(omega) (x sin(theta_x) + y sin(theta_y))),kthe launch medium’s real wave number, so every frequency leaves at the same angle, and the displacement shifts the field exactly in the periodic Cartesian representation. The energy or peak power is set after both. They are applied on aFreeGridonly: a radial grid has no direction to move along, a line grid no y, and aPlanarPulsehas its own offset_x. A tilted beam’s transverse wave numbers must lie inside the grid’s band, the moved beam must be negligible at the window’s edges, and its pulse-front delay across the beam must fit in half the time window; otherwisebuild()refuses it.- property aligned: bool[source]#
Return whether the beam is displaced or tilted.
- Returns:
- bool
True when either has a nonzero component.
- 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
PulseSumcarries 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.PlanarGridis refused: its launch is aPlanarPulse.- 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, launch pre-propagation would overflow the field, or a displacement or tilt is refused (see the class notes).
- 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
PlanarPulseor 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'
- class solprop.pulses.TimeDataPulse(*, t, field, energy, phase=(), t0=0.0, wavelength=None)[source]#
Bases:
DisplayedA pulse from a measured field in time, transformed exactly onto the grid.
The stored form is the record, and
build()takes out its meanE_meanand evaluates the transform in the optics convention,sum_n (E_n - E_mean) exp(+i omega t_n), at the grid’s own frequencies. Nothing is interpolated, so the record may hold any number of pulses at any separation, however deep the fringes between them, and each lands where it sat for anyt[0]. That is the difference from aDataPulse: a spline through the magnitude and the phase of a spectrum cannot follow a fringe to its null.The mean is taken out because a baseline is an offset of the instrument, not part of a field that propagates, and left in it would be a step at each end of the record, whose spectrum reaches the band. A background that varies across the record stays, and is such a step where it does not vanish at the ends. So is the mean of a pulse with a net area, such as a half-cycle pulse, which this changes.
The spectrum is zero at frequencies that are not positive and above the record’s Nyquist frequency
pi / dt, and the grid’s spectral collar is applied. The record’s ends are its ends: a pulse that reaches them is cut there. A record longer than the grid’s window folds back into it, as any transform sampled at the grid’s spacing must, and the build warns when that takes more than a millionth of the energy.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.
- Attributes:
- tnumpy.ndarray
Time samples, s, shape
(n_data,), uniformly spaced and increasing.- fieldnumpy.ndarray
Measured field on
t, arbitrary units, shape(n_data,), real or complex.- energyfloat
Pulse energy to scale to, J.
- phasearray_like
Further optics-convention Taylor coefficients, applied on top of the phase already in the record, about
wavelength; radians at order 0 ands**nthereafter.- t0float
Further delay, s, on top of the arrival time the record’s own axis implies.
- wavelengthfloat or None
Reference wavelength of the Taylor phase, m. Defaults to the spectral centre of mass of the record.
- Raises:
- PulseError
If the arrays disagree in length, the record is too short or not finite, the axis is not uniform and increasing, or the record has nothing at positive frequencies.
Examples
>>> import numpy as np >>> from solprop.pulses import TimeDataPulse >>> t = np.linspace(-100e-15, 100e-15, 1024) >>> field = np.exp(-((t / 20e-15) ** 2)) * np.cos(2.35e15 * t) >>> pulse = TimeDataPulse(t=t, field=field, energy=1e-6) >>> float(round(pulse.reference_wavelength * 1e9)) 802.0
- build(grid)[source]#
Return the initial spectrum of the recorded 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 record’s band does not reach the grid’s active band at all.
Examples
>>> import numpy as np >>> from solprop.grid import FieldGrid >>> from solprop.pulses import TimeDataPulse >>> t = np.linspace(-100e-15, 100e-15, 1024) >>> field = np.exp(-((t / 20e-15) ** 2)) * np.cos(2.35e15 * t) >>> pulse = TimeDataPulse(t=t, field=field, energy=1e-6) >>> grid = FieldGrid.from_limits(1.0, 800e-9, (200e-9, 2e-6), 400e-15) >>> pulse.build(grid).shape[:2] (1, 1)
- field: Inexact[ndarray, 'n_data_field']#
- property reference_wavelength: float[source]#
Return the wavelength the Taylor phase is expanded about, m.
- Returns:
- float
wavelengthif it was given, otherwise the wavelength of the spectral centre of mass of the record.
- t: Float[ndarray, 'n_data']#
- solprop.pulses.load_spectrum(path, *, energy, axis='frequency', axis_unit=None, columns=None, phase_unit='rad', phase_convention='optics', wrapped=None, floor=0.0001)[source]#
Read a measured spectral power density and phase from a text file.
The default layout is the usual output of a spectrometer and a phase retrieval, in three columns: frequency in Hz, spectral power density in arbitrary units (
energysets the scale) and spectral phase in radians. A file with two columns has a flat phase, and further columns are ignored. The pulse is built throughDataPulse.from_spectral_density(), so the phase is interpolated as read, never re-derived from a complex spectrum.Format. Columns are separated by commas on a line that holds one, otherwise by whitespace. Everything after a
#is a comment, blank lines are skipped, and the first line that is not a comment may be a header of column names, which is skipped when none of its fields is a number. The rows may come in either order along the axis: they are sorted.Axis. With
axis="wavelength"the first column is a wavelength, and the power density per unit wavelength is converted to one per unit angular frequency,P_omega = P_lambda lambda**2 / (2 pi c), so that the energy in any band is the same on either axis.Phase. The phase is in the optics convention (
docs/dev/conventions.mdsection 2), so a delaytauis+omega tauand a positive group-delay dispersion is a positive chirp.phase_convention="fft"reads the opposite sign, which is that of a file written for Luna.jl. A column is taken as wrapped and unwrapped whenwrappedsays so, or, by default, when all its values lie within2 pi(with a milliradian for the rounding of the file’s last digit); any other column is taken as it stands, so a large chirp given unwrapped survives at any sampling a spline resolves. A wrapped column is unwrapped only over the samples whose power is abovefloortimes the peak of the power column: between them the phase is interpolated linearly across any gap, and beyond the first and last it is held at their value, so the random phase of the noise floor cannot add a turn to the band.Power. Negative values, which a background subtraction leaves where there is no signal, are clipped to zero; one more negative than a tenth of the peak is refused, because that is not such noise.
- Parameters:
- pathstr, os.PathLike or io.TextIOBase
The file, UTF-8 text, or an open text stream such as an
io.StringIO, which is read from where it stands.- energyfloat
Pulse energy to scale to, J.
- axis{“frequency”, “wavelength”}, optional
What the axis column holds.
- axis_unitstr or None, optional
Unit of the axis column.
Noneis the axis’s SI unit: Hz for a frequency, m for a wavelength. A frequency may be in"Hz","GHz","THz","PHz"or, as an angular frequency,"rad/s"or"rad/fs"; a wavelength in"m","um"(or"µm") or"nm".- columnssequence of int or None, optional
Zero-based indices of the axis, power and, optionally, phase columns, as
(axis, power)or(axis, power, phase); two indices give a flat phase.Noneis(0, 1, 2), or(0, 1)for a two-column file.- phase_unit{“rad”, “deg”}, optional
Unit of the phase column. A wrapped phase in degrees spans 360 and would otherwise be taken as unwrapped radians.
- phase_convention{“optics”, “fft”}, optional
Sign convention of the phase column.
- wrappedbool or None, optional
Whether the phase column is wrapped;
Nonedecides from its range.- floorfloat, optional
Share of the peak power, in
[0, 1), below which a sample is noise for the unwrapping of a wrapped phase.
- Returns:
- DataPulse
The pulse, holding the read phase as its
spectral_phase.
- Raises:
- PulseError
If there is no file at
path; a line after the header is not a row of numbers, or the rows differ in length; the file has fewer than two columns orcolumnsasks for one it does not have; a value read is not finite; there are fewer than four rows; an axis value is not positive or repeats; the power column has no positive value or is more negative than a tenth of its peak; or a keyword is not one of its documented values.
- Warns:
- SolpropWarning
If a wrapped phase looks undersampled: some step between neighbouring samples above the floor lies within a tenth of pi of
+-pi, and the samples beyond the innermost such step, outward from the peak, hold more than a thousandth of the power. Such a phase cannot be recovered.
See also
DataPulse.from_spectral_densityThe same pulse from arrays.
Examples
A Gaussian spectrum written over wavelength in nanometres, with a header line:
>>> import io >>> import numpy as np >>> from solprop.pulses import load_spectrum >>> wavelength = np.linspace(700.0, 900.0, 201) >>> power = np.exp(-(((wavelength - 800.0) / 20.0) ** 2)) >>> file = io.StringIO() >>> np.savetxt(file, np.column_stack([wavelength, power]), header="nm power") >>> _ = file.seek(0) >>> pulse = load_spectrum(file, energy=1e-6, axis="wavelength", axis_unit="nm") >>> pulse.spectrum.shape (201,) >>> float(np.ptp(pulse.spectral_phase)) 0.0