From c94fe54d4148452c0686d871ecac7e7acaa38c0a Mon Sep 17 00:00:00 2001 From: RTSandberg Date: Fri, 12 Apr 2024 17:35:13 -0700 Subject: [PATCH 01/18] begin refactoring speckle lasers --- lasy/profiles/__init__.py | 4 +- lasy/profiles/speckled/__init__.py | 9 + lasy/profiles/speckled/fm_ssd.py | 104 +++++++ lasy/profiles/speckled/gp_isi.py | 36 +++ lasy/profiles/speckled/gp_rpm_ssd.py | 0 .../old_speckle_profile.py} | 2 +- lasy/profiles/speckled/rpp_cpp.py | 37 +++ lasy/profiles/speckled/speckle_profile.py | 211 ++++++++++++++ .../speckled/stochastic_process_utilities.py | 32 ++ tests/old_test_speckles.py | 273 ++++++++++++++++++ tests/test_speckles.py | 159 +++++----- 11 files changed, 781 insertions(+), 86 deletions(-) create mode 100644 lasy/profiles/speckled/__init__.py create mode 100644 lasy/profiles/speckled/fm_ssd.py create mode 100644 lasy/profiles/speckled/gp_isi.py create mode 100644 lasy/profiles/speckled/gp_rpm_ssd.py rename lasy/profiles/{speckle_profile.py => speckled/old_speckle_profile.py} (99%) create mode 100644 lasy/profiles/speckled/rpp_cpp.py create mode 100644 lasy/profiles/speckled/speckle_profile.py create mode 100644 lasy/profiles/speckled/stochastic_process_utilities.py create mode 100644 tests/old_test_speckles.py diff --git a/lasy/profiles/__init__.py b/lasy/profiles/__init__.py index 9f45c997b..9acedbb83 100644 --- a/lasy/profiles/__init__.py +++ b/lasy/profiles/__init__.py @@ -2,12 +2,12 @@ from .gaussian_profile import GaussianProfile from .from_array_profile import FromArrayProfile from .from_openpmd_profile import FromOpenPMDProfile -from .speckle_profile import SpeckleProfile +from .speckled.speckle_profile import SpeckleProfile __all__ = [ "CombinedLongitudinalTransverseProfile", "GaussianProfile", "FromArrayProfile", "FromOpenPMDProfile", - "SpeckleProfile", + # "SpeckleProfile", ] diff --git a/lasy/profiles/speckled/__init__.py b/lasy/profiles/speckled/__init__.py new file mode 100644 index 000000000..68f50d0c3 --- /dev/null +++ b/lasy/profiles/speckled/__init__.py @@ -0,0 +1,9 @@ +from .speckle_profile import SpeckleProfile +from .rpp_cpp import PhasePlateProfile +from .fm_ssd import FMSSDProfile + +__all__ = [ + "SpeckleProfile", + "PhasePlateProfile", + "FMSSDProfile", +] diff --git a/lasy/profiles/speckled/fm_ssd.py b/lasy/profiles/speckled/fm_ssd.py new file mode 100644 index 000000000..3990ff962 --- /dev/null +++ b/lasy/profiles/speckled/fm_ssd.py @@ -0,0 +1,104 @@ +import numpy as np +from .speckle_profile import SpeckleProfile + +class FMSSDProfile(SpeckleProfile): + """Generate a speckled laser profile with smoothing by spectral dispersion (SSD). + + This has temporal smoothing. + + Parameters + ---------- + + relative_laser_bandwidth : float + Bandwidth :math:`\Delta_\nu` of the laser pulse, relative to central frequency. + Only used if ``temporal_smoothing_type`` is ``'FM SSD'``, ``'GP RPM SSD'`` or ``'GP ISI'``. + + phase_modulation_amplitude :list of 2 floats + Amplitudes :math:`\delta_{x},\delta_{y}` of phase modulation in each transverse direction. + Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. + + number_color_cycles : list of 2 floats + Number of color cycles :math:`N_{cc,x},N_{cc,y}` of SSD spectrum to include in modulation + Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. + + transverse_bandwidth_distribution: list of 2 floats + Determines how much SSD is distributed in the :math:`x` and :math:`y` directions. + if `transverse_bandwidth_distribution=[a,b]`, then the SSD frequency modulation is :math:`a/\sqrt{a^2+b^2}` in :math:`x` and :math:`b/\sqrt{a^2+b^2}` in :math:`y`. + Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. + + """ + def __init__(self, *speckle_args, + relative_laser_bandwidth, + phase_modulation_amplitude, + number_color_cycles, + transverse_bandwidth_distribution, + ): + super().__init__(*speckle_args) + self.laser_bandwidth = relative_laser_bandwidth + # the amplitude of phase along each direction + self.phase_modulation_amplitude = phase_modulation_amplitude + # number of color cycles + self.number_color_cycles = number_color_cycles + # bandwidth distributed with respect to the two transverse direction + self.transverse_bandwidth_distribution = ( + transverse_bandwidth_distribution + ) + normalization = np.sqrt( + self.transverse_bandwidth_distribution[0] ** 2 + + self.transverse_bandwidth_distribution[1] ** 2 + ) + frac = [ + self.transverse_bandwidth_distribution[0] / normalization, + self.transverse_bandwidth_distribution[1] / normalization, + ] + self.phase_modulation_frequency = [ + self.laser_bandwidth * sf * 0.5 / pma + for sf, pma in zip(frac, self.phase_modulation_amplitude) + ] + self.time_delay = ( + ( + self.number_color_cycles[0] + / self.phase_modulation_frequency[0] + if self.phase_modulation_frequency[0] > 0 + else 0 + ), + ( + self.number_color_cycles[1] + / self.phase_modulation_frequency[1] + if self.phase_modulation_frequency[1] > 0 + else 0 + ), + ) + self.x_y_dephasing = np.random.standard_normal(2) * np.pi + + def beamlets_complex_amplitude( + self, t_now, + ): + """Calculate complex amplitude of the beamlets in the near-field, before propagating to the focal plane. + + Parameters + ---------- + + Returns + ------- + array of complex numbers giving beamlet amplitude and phases in the near-field + """ + phase_plate = np.random.uniform( + -np.pi, np.pi, size=self.n_beamlets[0] * self.n_beamlets[1] + ).reshape(self.n_beamlets) + + phase_t = self.phase_modulation_amplitude[0] * np.sin( + self.x_y_dephasing[0] + + 2 * np.pi * self.phase_modulation_frequency[0] + * ( + t_now - self.X_lens_matrix * self.time_delay[0] / self.n_beamlets[0] + ) + ) + self.phase_modulation_amplitude[1] * np.sin( + self.x_y_dephasing[1] + + 2 * np.pi * self.phase_modulation_frequency[1] + * ( + t_now - self.Y_lens_matrix * self.time_delay[1] / self.n_beamlets[1] + ) + ) + return np.exp(1j * (phase_plate + phase_t)) + \ No newline at end of file diff --git a/lasy/profiles/speckled/gp_isi.py b/lasy/profiles/speckled/gp_isi.py new file mode 100644 index 000000000..9bdb17c80 --- /dev/null +++ b/lasy/profiles/speckled/gp_isi.py @@ -0,0 +1,36 @@ +import numpy as np +from .speckle_profile import SpeckleProfile + +class GPISIProfile(SpeckleProfile): + """Generate a speckled laser profile with smoothing by spectral dispersion (SSD). + + This has temporal smoothing. + + Parameters + ---------- + + relative_laser_bandwidth : float + Bandwidth :math:`\Delta_\nu` of the laser pulse, relative to central frequency. + + """ + def __init__(self, *speckle_args, + relative_laser_bandwidth, + ): + super().__init__(*speckle_args) + self.laser_bandwidth = relative_laser_bandwidth + + def beamlets_complex_amplitude( + self, t_now, + ): + """Calculate complex amplitude of the beamlets in the near-field, before propagating to the focal plane. + + Parameters + ---------- + + Returns + ------- + array of complex numbers giving beamlet amplitude and phases in the near-field + """ + + return + \ No newline at end of file diff --git a/lasy/profiles/speckled/gp_rpm_ssd.py b/lasy/profiles/speckled/gp_rpm_ssd.py new file mode 100644 index 000000000..e69de29bb diff --git a/lasy/profiles/speckle_profile.py b/lasy/profiles/speckled/old_speckle_profile.py similarity index 99% rename from lasy/profiles/speckle_profile.py rename to lasy/profiles/speckled/old_speckle_profile.py index 5f808ea06..92a26db30 100644 --- a/lasy/profiles/speckle_profile.py +++ b/lasy/profiles/speckled/old_speckle_profile.py @@ -1,7 +1,7 @@ import numpy as np from scipy.constants import c -from .profile import Profile +from ..profile import Profile def gen_gaussian_time_series(t_num, dt, fwhm, rms_mean): diff --git a/lasy/profiles/speckled/rpp_cpp.py b/lasy/profiles/speckled/rpp_cpp.py new file mode 100644 index 000000000..df03b7407 --- /dev/null +++ b/lasy/profiles/speckled/rpp_cpp.py @@ -0,0 +1,37 @@ +import numpy as np +from .speckle_profile import SpeckleProfile + +class PhasePlateProfile(SpeckleProfile): + """Generate a speckled laser profile with a random phase plate. + + This has no temporal smoothing. + + Parameters + ---------- + rpp_cpp: string, keyword only, can be 'rpp' or 'cpp' + """ + def __init__(self, *speckle_args, rpp_cpp): + super().__init__(*speckle_args) + self.rpp_cpp = rpp_cpp + + def beamlets_complex_amplitude( + self, t_now, + ): + """Calculate complex amplitude of the beamlets in the near-field, before propagating to the focal plane. + + Parameters + ---------- + + Returns + ------- + array of complex numbers giving beamlet amplitude and phases in the near-field + """ + if self.rpp_cpp.upper() == "RPP": + phase_plate = np.random.choice([0, np.pi], self.n_beamlets) + else: + phase_plate = np.random.uniform( + -np.pi, np.pi, size=self.n_beamlets[0] * self.n_beamlets[1] + ).reshape(self.n_beamlets) + exp_phase_plate = np.exp(1j * phase_plate) + return exp_phase_plate + \ No newline at end of file diff --git a/lasy/profiles/speckled/speckle_profile.py b/lasy/profiles/speckled/speckle_profile.py new file mode 100644 index 000000000..4565e74a8 --- /dev/null +++ b/lasy/profiles/speckled/speckle_profile.py @@ -0,0 +1,211 @@ +import numpy as np +from scipy.constants import c + +from ..profile import Profile + +###NOTES### +# would be good to discuss bandwidth meanings in ISI vs SSM +# how to include doc strings of parent class +# reference r in doc string +# temporal envelope +# should all arguments be specified for every class? Should non-base arguments be kw only arguments? +########### + +class SpeckleProfile(Profile): + r""" + Derived class for the profile of a speckled laser pulse. + + Speckled lasers are used to mitigate laser-plasma interactions in fusion and ion acceleration contexts. + More on the subject can be found in chapter 9 of `P. Michel, Introduction to Laser-Plasma Interactions `__. + A speckled laser beam is a laser that is deliberately divided transversely into :math:`N_{bx}\times N_{by}` beamlets in the near-field. + The phase plate provides a different phase to each beamlet, with index :math:`ml`, which then propagate to the far field and combine incoherently. + + The electric field in the focal plane, as a function of time :math:`t` and the coordinates + :math:`\boldsymbol{x}_\perp=(x,y)` transverse to the direction of propagation, is: + + .. math:: + + \begin{aligned} + E_u(\boldsymbol{x}_\perp,t) &= Re\left[ E_0 + {\rm sinc}\left(\frac{\pi x}{\Delta x}\right) + {\rm sinc}\left(\frac{\pi y}{\Delta y}\right)\times p_u + \right. + \\ + & \times\sum_{m,l=1}^{N_{bx}, N_{by}} A_{ml} + \exp\left(i\boldsymbol{k}_{\perp ml}\cdot\boldsymbol{x}_\perp + + i\phi_{ml}(t)\right) + \Bigg] + \end{aligned} + + where :math:`u` is either :math:`x` or :math:`y`, :math:`p_u` is + the polarization vector, and :math:`Re` represent the real part [Michel, Eqns. 9.11, 87, 94]. + Several quantities are computed internally to the code depending on the + method of smoothing chosen, including the beamlet amplitude :math:`A_{ml}`, + the beamlet wavenumber :math:`k_{\perp ml}`, + the relative phase contribution :math:`\phi_{ml}(t)` of beamlet :math:`ml` induced by the phase plate and temporal smoothing. + The beam widths are :math:`\Delta x=\frac{\lambda_0fN_{bx}}{D_{x}}`, + :math:`\Delta y=\frac{\lambda_0fN_{by}}{D_{y}}`. + The other parameters in these formulas are defined below. + + This is an adapation of work by `Han Wen `__ to LASY. + + + Notes + ----- + This assumes a flat-top rectangular laser and so a rectangular arrangement of beamlets in the near-field. + + Parameters + ---------- + wavelength : float (in meters) + The main laser wavelength :math:`\lambda_0` of the laser, which + defines :math:`\omega_0` in the above formula, according to + :math:`\omega_0 = 2\pi c/\lambda_0`. + + pol : list of 2 complex numbers (dimensionless) + Polarization vector. It corresponds to :math:`p_u` in the above + formula ; :math:`p_x` is the first element of the list and + :math:`p_y` is the second element of the list. Using complex + numbers enables elliptical polarizations. + + laser_energy : float (in Joules) + The total energy of the laser pulse. The amplitude of the laser + field (:math:`E_0` in the above formula) is automatically + calculated so that the pulse has the prescribed energy. + + focal_length : float (in meters) + Focal length of lens :math:`f` just after the RPP/CPP. + + beam_aperture : list of 2 floats (in meters) + Widths :math:`D_x,D_y` of the rectangular beam in the near-field, i.e., size of the illuminated region of the RPP/CPP. + + n_beamlets : list of 2 integers + Number of RPP/CPP elements :math:`N_{bx},N_{by}` in each direction, in the near field. + + do_include_transverse_envelope : boolean, (optional, default False) + Whether to include the transverse sinc envelope or not. + I.e. whether it is assumed to be close enough to the laser axis to neglect the transverse field decay. + """ + + def __init__( + self, + wavelength, + pol, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, + do_include_transverse_envelope, + ): + super().__init__(wavelength, pol) + self.laser_energy = laser_energy + self.focal_length = focal_length + self.beam_aperture = np.array(beam_aperture, dtype="float") + self.n_beamlets = np.array(n_beamlets, dtype="int") + + self.do_include_transverse_envelope = do_include_transverse_envelope + + self.x_lens_list = np.linspace( + -0.5 * (self.n_beamlets[0] - 1), + 0.5 * (self.n_beamlets[0] - 1), + num=self.n_beamlets[0], + ) + self.y_lens_list = np.linspace( + -0.5 * (self.n_beamlets[1] - 1), + 0.5 * (self.n_beamlets[1] - 1), + num=self.n_beamlets[1], + ) + self.Y_lens_matrix, self.X_lens_matrix = np.meshgrid( + self.y_lens_list, self.x_lens_list + ) + self.Y_lens_index_matrix, self.X_lens_index_matrix = np.meshgrid( + np.arange(self.n_beamlets[1], dtype=float), + np.arange(self.n_beamlets[0], dtype=float), + ) + + def beamlets_complex_amplitude( + self, t_now, + ): + """Calculate complex amplitude of the beamlets in the near-field, before propagating to the focal plane. + + Parameters + ---------- + + Returns + ------- + array of complex numbers giving beamlet amplitude and phases in the near-field + """ + return np.ones_like(self.X_lens_matrix) + + def generate_speckle_pattern(self, t_now, x, y): + """Calculate the speckle pattern in the focal plane. + + Calculates the complex envelope defining the laser pulse in the focal plane at time `t=t_now`. + This function first gets the beamlet complex amplitudes and phases with the function `beamlets_complex_amplitude` + then propagates the the beamlets to the focal plane. + + Parameters + ---------- + t_now: float, time at which to calculate the speckle pattern + exp_phase_plate: 2d array of complex numbers giving the RPP / CPP phase contributions to the beamlets + x: 3d array of x-positions in focal plane + y: 3d array of y-positions in focal plane + series_time: 1d array of times at which the stochastic process was sampled to generate the time series + time_series: array of random phase and/or amplitudes as determined by the smoothing type + + Returns + ------- + speckle_amp: 2D array of complex numbers defining the laser envelope at focus at time `t_now` + """ + lambda_fnum = self.lambda0 * self.focal_length / self.beam_aperture + X_focus_matrix = x[:, :, 0] / lambda_fnum[0] + Y_focus_matrix = y[:, :, 0] / lambda_fnum[1] + x_focus_list = X_focus_matrix[:, 0] + y_focus_list = Y_focus_matrix[0, :] + x_phase_focus_matrix = np.exp( + -2 * np.pi * 1j / self.n_beamlets[0] * self.x_lens_list[:, np.newaxis] * x_focus_list[np.newaxis, :] + ) + y_phase_focus_matrix = np.exp( + -2 * np.pi * 1j / self.n_beamlets[1] * self.y_lens_list[:, np.newaxis] * y_focus_list[np.newaxis, :] + ) + + bca = self.beamlets_complex_amplitude(t_now) + speckle_amp = np.einsum( + "jk,jl->kl", + np.einsum("ij,ik->jk", bca, x_phase_focus_matrix), + y_phase_focus_matrix, + ) + if self.do_include_transverse_envelope: + speckle_amp = ( + np.sinc(X_focus_matrix / self.n_beamlets[0]) + * np.sinc(Y_focus_matrix / self.n_beamlets[1]) + * speckle_amp + ) + return speckle_amp + + def evaluate(self, x, y, t): + """ + Return the envelope field of the laser. + + Parameters + ---------- + x, y, t: ndarrays of floats + Define points on which to evaluate the envelope + These arrays need to all have the same shape. + + Returns + ------- + envelope: ndarray of complex numbers + Contains the value of the envelope at the specified points + This array has the same shape as the arrays x, y, t + """ + # General parameters + t_norm = t[0, 0, :] * c / self.lambda0 + + envelope = np.zeros(x.shape, dtype=complex) + for i, t_i in enumerate(t_norm): + envelope[:, :, i] = self.generate_speckle_pattern( + t_i, + x=x, + y=y, + ) + return envelope diff --git a/lasy/profiles/speckled/stochastic_process_utilities.py b/lasy/profiles/speckled/stochastic_process_utilities.py new file mode 100644 index 000000000..3eebd32c3 --- /dev/null +++ b/lasy/profiles/speckled/stochastic_process_utilities.py @@ -0,0 +1,32 @@ +import numpy as np + +def gen_gaussian_time_series(t_num, dt, fwhm, rms_mean): + """Generate a discrete time series that has gaussian power spectrum. + + Credit Han Wen + + Parameters + ---------- + t_num: number of grid points in time + fwhm: full width half maximum of the power spectrum + rms_mean: root-mean-square average of the spectrum + + Returns + ------- + temporal_amplitude: a time series array of complex numbers with shape [t_num] + """ + if fwhm == 0.0: + temporal_amplitude = np.zeros(t_num, dtype=np.complex128) + else: + omega = np.fft.fftshift(np.fft.fftfreq(t_num, d=dt)) + psd = np.exp(-np.log(2) * 0.5 * np.square(omega / fwhm * 2 * np.pi)) + spectral_amplitude = np.array(psd) * ( + np.random.normal(size=t_num) + 1j * np.random.normal(size=t_num) + ) + temporal_amplitude = np.fft.ifftshift( + np.fft.fft(np.fft.fftshift(spectral_amplitude)) + ) + temporal_amplitude *= rms_mean / np.sqrt( + np.mean(np.square(np.abs(temporal_amplitude))) + ) + return temporal_amplitude diff --git a/tests/old_test_speckles.py b/tests/old_test_speckles.py new file mode 100644 index 000000000..80df1b389 --- /dev/null +++ b/tests/old_test_speckles.py @@ -0,0 +1,273 @@ +import numpy as np + +from lasy.laser import Laser +from lasy.profiles.speckled.speckle_profile import SpeckleProfile +import pytest +from scipy.constants import c + + +@pytest.mark.parametrize( + "temporal_smoothing_type", ["RPP", "CPP", "FM SSD", "GP RPM SSD", "GP ISI"] +) +def test_intensity_distribution(temporal_smoothing_type): + """Test whether the spatial intensity distribution and statisticis are correct. + + The distribution should be exponential, 1/ exp(-I/) [Michel, 9.35]. + The real and imaginary parts of the envelope [Michel, Eqn. 9.26] and their product [9.30] should all be 0 on average. + """ + + wavelength = 0.351e-6 # Laser wavelength in meters + polarization = (1, 0) # Linearly polarized in the x direction + focal_length = 3.5 # m + beam_aperture = [0.35, 0.5] # m + n_beamlets = [24, 32] + relative_laser_bandwidth = 0.005 + laser_energy = 1.0 # J (this is the laser energy stored in the box defined by `lo` and `hi` below) + + ssd_phase_modulation_amplitude = (4.1, 4.5) + ssd_number_color_cycles = [1.4, 1.0] + ssd_transverse_bandwidth_distribution = [1.8, 1.0] + + profile = SpeckleProfile( + wavelength, + polarization, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, + temporal_smoothing_type=temporal_smoothing_type, + relative_laser_bandwidth=relative_laser_bandwidth, + ssd_phase_modulation_amplitude=ssd_phase_modulation_amplitude, + ssd_number_color_cycles=ssd_number_color_cycles, + ssd_transverse_bandwidth_distribution=ssd_transverse_bandwidth_distribution, + ) + dimensions = "xyt" + dx = wavelength * focal_length / beam_aperture[0] + dy = wavelength * focal_length / beam_aperture[1] + Lx = 1.8 * dx * n_beamlets[0] + Ly = 3.1 * dy * n_beamlets[1] + nu_laser = c / wavelength + t_max = 50 / nu_laser + lo = (0, 0, 0) + hi = (Lx, Ly, t_max) + num_points = (200, 250, 2) + + laser = Laser(dimensions, lo, hi, num_points, profile) + + F = laser.grid.field + + # get spatial statistics + # = 0 = = + e_r = np.real(F) + e_i = np.imag(F) + er_ei = e_r * e_i + assert np.max(abs(e_r.mean(axis=(0, 1)) / e_r.std(axis=(0, 1)))) < 1.0e-1 + assert np.max(abs(e_i.mean(axis=(0, 1)) / e_i.std(axis=(0, 1)))) < 1.0e-1 + assert np.max(abs(er_ei.mean(axis=(0, 1)) / er_ei.std(axis=(0, 1)))) < 1.0e-1 + + # # compare intensity distribution with expected 1/ exp(-I/) + env_I = abs(F) ** 2 + I_vec = env_I.flatten() + mean_I = I_vec.mean() + N_hist = 200 + counts_np, bins_np = np.histogram(I_vec, bins=N_hist, density=True) + I_dist = 1.0 / mean_I * np.exp(-bins_np / mean_I) + error_I_dist = np.max(abs(counts_np - I_dist[:-1])) + assert error_I_dist < 2.0e-4 + + +@pytest.mark.parametrize( + "temporal_smoothing_type", ["RPP", "CPP", "FM SSD", "GP RPM SSD", "GP ISI"] +) +def test_spatial_correlation(temporal_smoothing_type): + """Tests whether the speckles have the correct shape. + + The speckle shape is measured over one period, since the spatial profile is periodic. + The correct speckle shape for a rectangular laser, + determined by the autocorrelation, is the product of sinc functions [Michel, Eqn. 9.16]. + """ + wavelength = 0.351e-6 # Laser wavelength in meters + polarization = (1, 0) # Linearly polarized in the x direction + laser_energy = 1.0 # J (this is the laser energy stored in the box defined by `lo` and `hi` below) + focal_length = 3.5 # m + beam_aperture = [0.35, 0.35] # m + n_beamlets = [24, 32] + relative_laser_bandwidth = 0.005 + + ssd_phase_modulation_amplitude = (4.1, 4.1) + ssd_number_color_cycles = [1.4, 1.0] + ssd_transverse_bandwidth_distribution = [1.0, 1.0] + + profile = SpeckleProfile( + wavelength, + polarization, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, + temporal_smoothing_type=temporal_smoothing_type, + relative_laser_bandwidth=relative_laser_bandwidth, # 0.005 + ssd_phase_modulation_amplitude=ssd_phase_modulation_amplitude, + ssd_number_color_cycles=ssd_number_color_cycles, + ssd_transverse_bandwidth_distribution=ssd_transverse_bandwidth_distribution, + ) + dimensions = "xyt" + dx = wavelength * focal_length / beam_aperture[0] + dy = wavelength * focal_length / beam_aperture[1] + Lx = dx * n_beamlets[0] + Ly = dy * n_beamlets[1] + nu_laser = c / wavelength + tu = 1 / relative_laser_bandwidth / 50 / nu_laser + t_max = 200 * tu + lo = (0, 0, 0) + hi = (Lx, Ly, t_max) + num_points = (200, 200, 300) + + laser = Laser(dimensions, lo, hi, num_points, profile) + F = laser.grid.field + + # compare speckle profile / autocorrelation + # compute autocorrelation using Wiener-Khinchin Theorem + + fft_abs_all = abs(np.fft.fft2(F, axes=(0, 1))) ** 2 + ifft_abs = abs(np.fft.ifft2(fft_abs_all, axes=(0, 1))) ** 2 + acorr2_3d = np.fft.fftshift(ifft_abs, axes=(0, 1)) + acorr2_3d_norm = acorr2_3d / np.max(acorr2_3d, axis=(0, 1)) + + # compare with theoretical speckle profile + x_list = np.linspace( + -n_beamlets[0] / 2 + 0.5, n_beamlets[0] / 2 - 0.5, num_points[0], endpoint=False + ) + y_list = np.linspace( + -n_beamlets[1] / 2 + 0.5, n_beamlets[1] / 2 - 0.5, num_points[1], endpoint=False + ) + X, Y = np.meshgrid(x_list, y_list) + acorr_theor = np.sinc(X) ** 2 * np.sinc(Y) ** 2 + error_auto_correlation = np.max(abs(acorr_theor[:, :, np.newaxis] - acorr2_3d_norm)) + + assert error_auto_correlation < 5.0e-1 + + +@pytest.mark.parametrize( + "temporal_smoothing_type", ["RPP", "CPP", "FM SSD", "GP RPM SSD", "GP ISI"] +) +def test_sinc_zeros(temporal_smoothing_type): + """Test whether the transverse sinc envelope has the correct width + + The transverse envelope for the rectangular laser has the form + + ..math:: + + {\rm sinc}\left(\frac{\pi x}{\Delta x}\right) + {\rm sinc}\left(\frac{\pi y}{\Delta y}\right) + + [Michel, Eqns. 9.11, 87, 94]. + This has widths + + ..math:: + + \Delta x=\lambda_0fN_{bx}/D_x, + \Delta y=\lambda_0fN_{by}/D_y + """ + wavelength = 0.351e-6 # Laser wavelength in meters + polarization = (1, 0) # Linearly polarized in the x direction + laser_energy = 1.0 # J (this is the laser energy stored in the box defined by `lo` and `hi` below) + focal_length = 3.5 # m + beam_aperture = [0.35, 0.35] # m + n_beamlets = [24, 48] + relative_laser_bandwidth = 0.005 + ssd_phase_modulation_amplitude = (4.1, 4.1) + ssd_number_color_cycles = [1.4, 1.0] + ssd_transverse_bandwidth_distribution = [1.0, 1.0] + + profile = SpeckleProfile( + wavelength, + polarization, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, + temporal_smoothing_type=temporal_smoothing_type, + relative_laser_bandwidth=relative_laser_bandwidth, + ssd_phase_modulation_amplitude=ssd_phase_modulation_amplitude, + ssd_number_color_cycles=ssd_number_color_cycles, + ssd_transverse_bandwidth_distribution=ssd_transverse_bandwidth_distribution, + do_include_transverse_envelope=True, + ) + dimensions = "xyt" + dx = wavelength * focal_length / beam_aperture[0] + dy = wavelength * focal_length / beam_aperture[1] + Lx = dx * n_beamlets[0] + Ly = dy * n_beamlets[1] + nu_laser = c / wavelength + tu = 1 / relative_laser_bandwidth / 50 / nu_laser + t_max = 200 * tu + lo = (-Lx, -Ly, 0) + hi = (Lx, Ly, t_max) + num_points = (300, 300, 10) + + laser = Laser(dimensions, lo, hi, num_points, profile) + F = laser.grid.field + + assert abs(F[0, :, :]).max() / abs(F).max() < 1.0e-8 + assert abs(F[-1, :, :]).max() / abs(F).max() < 1.0e-8 + assert abs(F[:, 0, :]).max() / abs(F).max() < 1.0e-8 + assert abs(F[:, -1, :]).max() / abs(F).max() < 1.0e-8 + + +def test_FM_SSD_periodicity(): + """Test that the frequency modulated Smoothing by spectral dispersion (SSD) has the correct temporal frequency.""" + wavelength = 0.351e-6 # Laser wavelength in meters + polarization = (1, 0) # Linearly polarized in the x direction + laser_energy = 1.0 # J (this is the laser energy stored in the box defined by `lo` and `hi` below) + focal_length = 3.5 # m + beam_aperture = [0.35, 0.35] # m + n_beamlets = [24, 32] + temporal_smoothing_type = "FM SSD" + relative_laser_bandwidth = 0.005 + + ssd_phase_modulation_amplitude = [4.1, 4.1] + ssd_number_color_cycles = [1.4, 1.0] + ssd_transverse_bandwidth_distribution = [1.0, 1.0] + + laser_profile = SpeckleProfile( + wavelength, + polarization, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, + temporal_smoothing_type=temporal_smoothing_type, + relative_laser_bandwidth=relative_laser_bandwidth, + ssd_phase_modulation_amplitude=ssd_phase_modulation_amplitude, + ssd_number_color_cycles=ssd_number_color_cycles, + ssd_transverse_bandwidth_distribution=ssd_transverse_bandwidth_distribution, + ) + nu_laser = c / wavelength + ssd_frac = np.sqrt( + ssd_transverse_bandwidth_distribution[0] ** 2 + + ssd_transverse_bandwidth_distribution[1] ** 2 + ) + ssd_frac = ( + ssd_transverse_bandwidth_distribution[0] / ssd_frac, + ssd_transverse_bandwidth_distribution[1] / ssd_frac, + ) + phase_mod_freq = [ + relative_laser_bandwidth * sf * 0.5 / pma + for sf, pma in zip(ssd_frac, ssd_phase_modulation_amplitude) + ] + t_max = 1.0 / phase_mod_freq[0] / nu_laser + + dimensions = "xyt" + dx = wavelength * focal_length / beam_aperture[0] + dy = wavelength * focal_length / beam_aperture[1] + Lx = dx * n_beamlets[0] + Ly = dy * n_beamlets[1] + lo = (0, 0, 0) # Lower bounds of the simulation box + hi = (Lx, Ly, t_max) # Upper bounds of the simulation box + num_points = (160, 200, 400) # Number of points in each dimension + + laser = Laser(dimensions, lo, hi, num_points, laser_profile) + F = laser.grid.field + period_error = abs(F[:, :, 0] - F[:, :, -1]).max() / abs(F).max() + assert period_error < 1.0e-8 diff --git a/tests/test_speckles.py b/tests/test_speckles.py index 63dac7b85..a2a0e4ed4 100644 --- a/tests/test_speckles.py +++ b/tests/test_speckles.py @@ -1,13 +1,14 @@ import numpy as np from lasy.laser import Laser -from lasy.profiles.speckle_profile import SpeckleProfile +from lasy.profiles.speckled import PhasePlateProfile +from lasy.profiles.speckled import FMSSDProfile import pytest from scipy.constants import c @pytest.mark.parametrize( - "temporal_smoothing_type", ["RPP", "CPP", "FM SSD", "GP RPM SSD", "GP ISI"] + "temporal_smoothing_type", ["RPP", "CPP", "FM SSD"] ) def test_intensity_distribution(temporal_smoothing_type): """Test whether the spatial intensity distribution and statisticis are correct. @@ -18,29 +19,28 @@ def test_intensity_distribution(temporal_smoothing_type): wavelength = 0.351e-6 # Laser wavelength in meters polarization = (1, 0) # Linearly polarized in the x direction + laser_energy = 1.0 # J (this is the laser energy stored in the box defined by `lo` and `hi` below) focal_length = 3.5 # m beam_aperture = [0.35, 0.5] # m n_beamlets = [24, 32] - relative_laser_bandwidth = 0.005 - laser_energy = 1.0 # J (this is the laser energy stored in the box defined by `lo` and `hi` below) + do_sinc_profile=False + speckle_args = (wavelength, polarization, laser_energy, focal_length, beam_aperture, n_beamlets,do_sinc_profile) - ssd_phase_modulation_amplitude = (4.1, 4.5) - ssd_number_color_cycles = [1.4, 1.0] - ssd_transverse_bandwidth_distribution = [1.8, 1.0] - - profile = SpeckleProfile( - wavelength, - polarization, - laser_energy, - focal_length, - beam_aperture, - n_beamlets, - temporal_smoothing_type=temporal_smoothing_type, - relative_laser_bandwidth=relative_laser_bandwidth, - ssd_phase_modulation_amplitude=ssd_phase_modulation_amplitude, - ssd_number_color_cycles=ssd_number_color_cycles, - ssd_transverse_bandwidth_distribution=ssd_transverse_bandwidth_distribution, - ) + relative_laser_bandwidth = 0.005 + phase_modulation_amplitude = (4.1, 4.5) + number_color_cycles = [1.4, 1.0] + transverse_bandwidth_distribution = [1.8, 1.0] + + if temporal_smoothing_type.upper() in ["RPP", "CPP"]: + profile = PhasePlateProfile(*speckle_args,rpp_cpp=temporal_smoothing_type) + elif temporal_smoothing_type == "FM SSD": + profile = FMSSDProfile( + *speckle_args, + relative_laser_bandwidth=relative_laser_bandwidth, + phase_modulation_amplitude=phase_modulation_amplitude, + number_color_cycles=number_color_cycles, + transverse_bandwidth_distribution=transverse_bandwidth_distribution, + ) dimensions = "xyt" dx = wavelength * focal_length / beam_aperture[0] dy = wavelength * focal_length / beam_aperture[1] @@ -77,7 +77,7 @@ def test_intensity_distribution(temporal_smoothing_type): @pytest.mark.parametrize( - "temporal_smoothing_type", ["RPP", "CPP", "FM SSD", "GP RPM SSD", "GP ISI"] + "temporal_smoothing_type", ["RPP", "CPP", "FM SSD"] #, "GP RPM SSD", "GP ISI"] ) def test_spatial_correlation(temporal_smoothing_type): """Tests whether the speckles have the correct shape. @@ -92,25 +92,24 @@ def test_spatial_correlation(temporal_smoothing_type): focal_length = 3.5 # m beam_aperture = [0.35, 0.35] # m n_beamlets = [24, 32] - relative_laser_bandwidth = 0.005 + do_sinc_profile=False + speckle_args = (wavelength, polarization, laser_energy, focal_length, beam_aperture, n_beamlets,do_sinc_profile) - ssd_phase_modulation_amplitude = (4.1, 4.1) - ssd_number_color_cycles = [1.4, 1.0] - ssd_transverse_bandwidth_distribution = [1.0, 1.0] - - profile = SpeckleProfile( - wavelength, - polarization, - laser_energy, - focal_length, - beam_aperture, - n_beamlets, - temporal_smoothing_type=temporal_smoothing_type, - relative_laser_bandwidth=relative_laser_bandwidth, # 0.005 - ssd_phase_modulation_amplitude=ssd_phase_modulation_amplitude, - ssd_number_color_cycles=ssd_number_color_cycles, - ssd_transverse_bandwidth_distribution=ssd_transverse_bandwidth_distribution, - ) + relative_laser_bandwidth = 0.005 + phase_modulation_amplitude = (4.1, 4.1) + number_color_cycles = [1.4, 1.0] + transverse_bandwidth_distribution = [1.0, 1.0] + + if temporal_smoothing_type.upper() in ["RPP", "CPP"]: + profile = PhasePlateProfile(*speckle_args,rpp_cpp=temporal_smoothing_type) + elif temporal_smoothing_type == "FM SSD": + profile = FMSSDProfile( + *speckle_args, + relative_laser_bandwidth=relative_laser_bandwidth, + phase_modulation_amplitude=phase_modulation_amplitude, + number_color_cycles=number_color_cycles, + transverse_bandwidth_distribution=transverse_bandwidth_distribution, + ) dimensions = "xyt" dx = wavelength * focal_length / beam_aperture[0] dy = wavelength * focal_length / beam_aperture[1] @@ -149,7 +148,7 @@ def test_spatial_correlation(temporal_smoothing_type): @pytest.mark.parametrize( - "temporal_smoothing_type", ["RPP", "CPP", "FM SSD", "GP RPM SSD", "GP ISI"] + "temporal_smoothing_type", ["RPP", "CPP", "FM SSD"]#, "GP RPM SSD", "GP ISI"] ) def test_sinc_zeros(temporal_smoothing_type): """Test whether the transverse sinc envelope has the correct width @@ -175,25 +174,24 @@ def test_sinc_zeros(temporal_smoothing_type): focal_length = 3.5 # m beam_aperture = [0.35, 0.35] # m n_beamlets = [24, 48] + do_sinc_profile=True + speckle_args = (wavelength, polarization, laser_energy, focal_length, beam_aperture, n_beamlets,do_sinc_profile) + relative_laser_bandwidth = 0.005 - ssd_phase_modulation_amplitude = (4.1, 4.1) - ssd_number_color_cycles = [1.4, 1.0] - ssd_transverse_bandwidth_distribution = [1.0, 1.0] - - profile = SpeckleProfile( - wavelength, - polarization, - laser_energy, - focal_length, - beam_aperture, - n_beamlets, - temporal_smoothing_type=temporal_smoothing_type, - relative_laser_bandwidth=relative_laser_bandwidth, - ssd_phase_modulation_amplitude=ssd_phase_modulation_amplitude, - ssd_number_color_cycles=ssd_number_color_cycles, - ssd_transverse_bandwidth_distribution=ssd_transverse_bandwidth_distribution, - do_include_transverse_envelope=True, - ) + phase_modulation_amplitude = (4.1, 4.1) + number_color_cycles = [1.4, 1.0] + transverse_bandwidth_distribution = [1.0, 1.0] + + if temporal_smoothing_type.upper() in ["RPP", "CPP"]: + profile = PhasePlateProfile(*speckle_args,rpp_cpp=temporal_smoothing_type) + elif temporal_smoothing_type == "FM SSD": + profile = FMSSDProfile( + *speckle_args, + relative_laser_bandwidth=relative_laser_bandwidth, + phase_modulation_amplitude=phase_modulation_amplitude, + number_color_cycles=number_color_cycles, + transverse_bandwidth_distribution=transverse_bandwidth_distribution, + ) dimensions = "xyt" dx = wavelength * focal_length / beam_aperture[0] dy = wavelength * focal_length / beam_aperture[1] @@ -215,7 +213,7 @@ def test_sinc_zeros(temporal_smoothing_type): assert abs(F[:, -1, :]).max() / abs(F).max() < 1.0e-8 -def test_FM_SSD_periodicity(): +def test_FM_periodicity(): """Test that the frequency modulated Smoothing by spectral dispersion (SSD) has the correct temporal frequency.""" wavelength = 0.351e-6 # Laser wavelength in meters polarization = (1, 0) # Linearly polarized in the x direction @@ -223,38 +221,33 @@ def test_FM_SSD_periodicity(): focal_length = 3.5 # m beam_aperture = [0.35, 0.35] # m n_beamlets = [24, 32] - temporal_smoothing_type = "FM SSD" + do_sinc_profile=False + speckle_args = (wavelength, polarization, laser_energy, focal_length, beam_aperture, n_beamlets,do_sinc_profile) + relative_laser_bandwidth = 0.005 + phase_modulation_amplitude = [4.1, 4.1] + number_color_cycles = [1.4, 1.0] + transverse_bandwidth_distribution = [1.0, 1.0] - ssd_phase_modulation_amplitude = [4.1, 4.1] - ssd_number_color_cycles = [1.4, 1.0] - ssd_transverse_bandwidth_distribution = [1.0, 1.0] - - laser_profile = SpeckleProfile( - wavelength, - polarization, - laser_energy, - focal_length, - beam_aperture, - n_beamlets, - temporal_smoothing_type=temporal_smoothing_type, + laser_profile = FMSSDProfile( + *speckle_args, relative_laser_bandwidth=relative_laser_bandwidth, - ssd_phase_modulation_amplitude=ssd_phase_modulation_amplitude, - ssd_number_color_cycles=ssd_number_color_cycles, - ssd_transverse_bandwidth_distribution=ssd_transverse_bandwidth_distribution, + phase_modulation_amplitude=phase_modulation_amplitude, + number_color_cycles=number_color_cycles, + transverse_bandwidth_distribution=transverse_bandwidth_distribution, ) nu_laser = c / wavelength - ssd_frac = np.sqrt( - ssd_transverse_bandwidth_distribution[0] ** 2 - + ssd_transverse_bandwidth_distribution[1] ** 2 + frac = np.sqrt( + transverse_bandwidth_distribution[0] ** 2 + + transverse_bandwidth_distribution[1] ** 2 ) - ssd_frac = ( - ssd_transverse_bandwidth_distribution[0] / ssd_frac, - ssd_transverse_bandwidth_distribution[1] / ssd_frac, + frac = ( + transverse_bandwidth_distribution[0] / frac, + transverse_bandwidth_distribution[1] / frac, ) phase_mod_freq = [ relative_laser_bandwidth * sf * 0.5 / pma - for sf, pma in zip(ssd_frac, ssd_phase_modulation_amplitude) + for sf, pma in zip(frac, phase_modulation_amplitude) ] t_max = 1.0 / phase_mod_freq[0] / nu_laser From 82fcaddfb25523b1e4f23f24fd729d1a66787a34 Mon Sep 17 00:00:00 2001 From: RTSandberg Date: Thu, 18 Apr 2024 17:15:08 -0700 Subject: [PATCH 02/18] change speckle structure and update documentation --- docs/source/api/profiles/index.rst | 2 +- docs/source/api/profiles/speckled.rst | 4 - docs/source/api/profiles/speckled/fm_ssd.rst | 9 + docs/source/api/profiles/speckled/gp_isi.rst | 9 + .../api/profiles/speckled/gp_rpm_ssd.rst | 9 + docs/source/api/profiles/speckled/index.rst | 13 + docs/source/api/profiles/speckled/rpp_cpp.rst | 9 + .../source/api/profiles/speckled/speckled.rst | 8 + lasy/profiles/speckled/__init__.py | 8 +- lasy/profiles/speckled/fm_ssd.py | 52 +- lasy/profiles/speckled/gp_isi.py | 70 ++- lasy/profiles/speckled/gp_rpm_ssd.py | 182 ++++++ lasy/profiles/speckled/old_speckle_profile.py | 540 ------------------ lasy/profiles/speckled/rpp_cpp.py | 7 +- lasy/profiles/speckled/speckle_profile.py | 23 +- .../speckled/stochastic_process_utilities.py | 2 +- tests/test_laser_profiles.py | 9 +- tests/test_speckles.py | 92 ++- 18 files changed, 441 insertions(+), 607 deletions(-) delete mode 100644 docs/source/api/profiles/speckled.rst create mode 100644 docs/source/api/profiles/speckled/fm_ssd.rst create mode 100644 docs/source/api/profiles/speckled/gp_isi.rst create mode 100644 docs/source/api/profiles/speckled/gp_rpm_ssd.rst create mode 100644 docs/source/api/profiles/speckled/index.rst create mode 100644 docs/source/api/profiles/speckled/rpp_cpp.rst create mode 100644 docs/source/api/profiles/speckled/speckled.rst delete mode 100644 lasy/profiles/speckled/old_speckle_profile.py diff --git a/docs/source/api/profiles/index.rst b/docs/source/api/profiles/index.rst index a93251fc4..3ee152a60 100644 --- a/docs/source/api/profiles/index.rst +++ b/docs/source/api/profiles/index.rst @@ -10,4 +10,4 @@ Laser Profiles combined_profile longitudinal/index transverse/index - speckled + speckled/index diff --git a/docs/source/api/profiles/speckled.rst b/docs/source/api/profiles/speckled.rst deleted file mode 100644 index 450573000..000000000 --- a/docs/source/api/profiles/speckled.rst +++ /dev/null @@ -1,4 +0,0 @@ -Speckled Laser Profile -====================== - -.. autoclass:: lasy.profiles.SpeckleProfile diff --git a/docs/source/api/profiles/speckled/fm_ssd.rst b/docs/source/api/profiles/speckled/fm_ssd.rst new file mode 100644 index 000000000..ab494e708 --- /dev/null +++ b/docs/source/api/profiles/speckled/fm_ssd.rst @@ -0,0 +1,9 @@ +Frequency-modulated Smoothing by Spectral Dispersion (FM-SSD) Laser Profile +=========================================================================== + +.. autoclass:: lasy.profiles.SpeckleProfile + :no-index: + :members: + +.. autoclass:: lasy.profiles.speckled.FM_SSD_Profile + :members: diff --git a/docs/source/api/profiles/speckled/gp_isi.rst b/docs/source/api/profiles/speckled/gp_isi.rst new file mode 100644 index 000000000..1906903ca --- /dev/null +++ b/docs/source/api/profiles/speckled/gp_isi.rst @@ -0,0 +1,9 @@ +Smoothing by Induced Spatial Incoherence (ISI) Laser Profile +============================================================ + +.. autoclass:: lasy.profiles.SpeckleProfile + :no-index: + :members: + +.. autoclass:: lasy.profiles.speckled.GP_ISI_Profile + :members: diff --git a/docs/source/api/profiles/speckled/gp_rpm_ssd.rst b/docs/source/api/profiles/speckled/gp_rpm_ssd.rst new file mode 100644 index 000000000..62f1413f0 --- /dev/null +++ b/docs/source/api/profiles/speckled/gp_rpm_ssd.rst @@ -0,0 +1,9 @@ +Random Phase Modulated (RPM) Smoothing by Spectral Dispersion (FM-SSD) Laser Profile +==================================================================================== + +.. autoclass:: lasy.profiles.SpeckleProfile + :no-index: + :members: + +.. autoclass:: lasy.profiles.speckled.GP_RPM_SSD_Profile + :members: diff --git a/docs/source/api/profiles/speckled/index.rst b/docs/source/api/profiles/speckled/index.rst new file mode 100644 index 000000000..45b09da2e --- /dev/null +++ b/docs/source/api/profiles/speckled/index.rst @@ -0,0 +1,13 @@ +Speckled Laser Profiles +======================= + +.. toctree:: + :maxdepth: 4 + :hidden: + + speckled + rpp_cpp + fm_ssd + gp_rpm_ssd + gp_isi + \ No newline at end of file diff --git a/docs/source/api/profiles/speckled/rpp_cpp.rst b/docs/source/api/profiles/speckled/rpp_cpp.rst new file mode 100644 index 000000000..c85f06e7c --- /dev/null +++ b/docs/source/api/profiles/speckled/rpp_cpp.rst @@ -0,0 +1,9 @@ +RPP/CPP only Laser Profile +========================== + +.. autoclass:: lasy.profiles.SpeckleProfile + :no-index: + :members: + +.. autoclass:: lasy.profiles.speckled.PhasePlateProfile + :members: diff --git a/docs/source/api/profiles/speckled/speckled.rst b/docs/source/api/profiles/speckled/speckled.rst new file mode 100644 index 000000000..98b4e9a62 --- /dev/null +++ b/docs/source/api/profiles/speckled/speckled.rst @@ -0,0 +1,8 @@ +Base Speckled Laser Profile +=========================== + +The `beamlets_complex_amplitude` function can be modified to create custom speckle profiles. +See the other profiles for examples. + +.. autoclass:: lasy.profiles.SpeckleProfile + :members: diff --git a/lasy/profiles/speckled/__init__.py b/lasy/profiles/speckled/__init__.py index 68f50d0c3..5d015ad12 100644 --- a/lasy/profiles/speckled/__init__.py +++ b/lasy/profiles/speckled/__init__.py @@ -1,9 +1,13 @@ from .speckle_profile import SpeckleProfile from .rpp_cpp import PhasePlateProfile -from .fm_ssd import FMSSDProfile +from .fm_ssd import FM_SSD_Profile +from .gp_rpm_ssd import GP_RPM_SSD_Profile +from .gp_isi import GP_ISI_Profile __all__ = [ "SpeckleProfile", "PhasePlateProfile", - "FMSSDProfile", + "FM_SSD_Profile", + "GP_RPM_SSD_Profile", + "GP_ISI_Profile", ] diff --git a/lasy/profiles/speckled/fm_ssd.py b/lasy/profiles/speckled/fm_ssd.py index 3990ff962..fd868f2b7 100644 --- a/lasy/profiles/speckled/fm_ssd.py +++ b/lasy/profiles/speckled/fm_ssd.py @@ -1,31 +1,55 @@ import numpy as np from .speckle_profile import SpeckleProfile -class FMSSDProfile(SpeckleProfile): - """Generate a speckled laser profile with smoothing by spectral dispersion (SSD). +class FM_SSD_Profile(SpeckleProfile): + r"""Generate a speckled laser profile with smoothing by frequency modulated (FM) spectral dispersion (SSD). - This has temporal smoothing. + In frequency-modulated smoothing by spectral dispersion, or FM-SSD, the amplitude of the beamlets is always :math:`A_{ml}(t)=1`. + There are two contributions to the phase :math:`\phi_{ml}` of each beamlet: + + .. math:: + + \phi_{ml}(t)=\phi_{PP,ml}+\phi_{SSD,ml}. + + The phase plate part :math:`\phi_{PP,ml}` is the initial phase delay from the randomly sized phase plate sections, + drawn from uniform distribution on the interval :math:`[0,2\pi]`. + The temporal smoothing is from the SSD term: + + .. math:: + + \begin{aligned} + \phi_{SSD,ml}(t)&=\delta_{x} \sin\left(\omega_{x} t + 2\pi\frac{mN_{cc,x}}{N_{bx}}\right)\\ + &+\delta_{y} \sin\left(\omega_{y} t + 2\pi\frac{lN_{cc,y}}{N_{by}}\right). + \end{aligned} + + The modulation frequencies :math:`\omega_x,\omega_y` are determined by the + laser bandwidth and modulation amplitudes according to the relation + + .. math:: + + \omega_x = \frac{\Delta_\nu r_x }{2\delta_x}, + \omega_y = \frac{\Delta_\nu r_y }{2\delta_y}, + + where :math:`\Delta_\nu` is the relative bandwidth of the laser pulse + and :math:`r_x, r_y` are additional rotation factors supplied by the user + in the `transverse_bandwidth_distribution` parameter that determine + how much of the modulation is in x and how much is in y. [Michel, Eqn. 9.69] Parameters ---------- relative_laser_bandwidth : float - Bandwidth :math:`\Delta_\nu` of the laser pulse, relative to central frequency. - Only used if ``temporal_smoothing_type`` is ``'FM SSD'``, ``'GP RPM SSD'`` or ``'GP ISI'``. + Resulting bandwidth :math:`\Delta_\nu` of the laser pulse, relative to central frequency, due to the frequency modulation. phase_modulation_amplitude :list of 2 floats Amplitudes :math:`\delta_{x},\delta_{y}` of phase modulation in each transverse direction. - Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. number_color_cycles : list of 2 floats Number of color cycles :math:`N_{cc,x},N_{cc,y}` of SSD spectrum to include in modulation - Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. transverse_bandwidth_distribution: list of 2 floats Determines how much SSD is distributed in the :math:`x` and :math:`y` directions. - if `transverse_bandwidth_distribution=[a,b]`, then the SSD frequency modulation is :math:`a/\sqrt{a^2+b^2}` in :math:`x` and :math:`b/\sqrt{a^2+b^2}` in :math:`y`. - Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. - + if `transverse_bandwidth_distribution=[a,b]`, then the SSD frequency modulation is :math:`r_x=a/\sqrt{a^2+b^2}` in :math:`x` and :math:`r_y=b/\sqrt{a^2+b^2}` in :math:`y`. """ def __init__(self, *speckle_args, relative_laser_bandwidth, @@ -70,6 +94,9 @@ def __init__(self, *speckle_args, ), ) self.x_y_dephasing = np.random.standard_normal(2) * np.pi + self.phase_plate = np.random.uniform( + -np.pi, np.pi, size=self.n_beamlets[0] * self.n_beamlets[1] + ).reshape(self.n_beamlets) def beamlets_complex_amplitude( self, t_now, @@ -83,9 +110,6 @@ def beamlets_complex_amplitude( ------- array of complex numbers giving beamlet amplitude and phases in the near-field """ - phase_plate = np.random.uniform( - -np.pi, np.pi, size=self.n_beamlets[0] * self.n_beamlets[1] - ).reshape(self.n_beamlets) phase_t = self.phase_modulation_amplitude[0] * np.sin( self.x_y_dephasing[0] @@ -100,5 +124,5 @@ def beamlets_complex_amplitude( t_now - self.Y_lens_matrix * self.time_delay[1] / self.n_beamlets[1] ) ) - return np.exp(1j * (phase_plate + phase_t)) + return np.exp(1j * (self.phase_plate + phase_t)) \ No newline at end of file diff --git a/lasy/profiles/speckled/gp_isi.py b/lasy/profiles/speckled/gp_isi.py index 9bdb17c80..08296aa87 100644 --- a/lasy/profiles/speckled/gp_isi.py +++ b/lasy/profiles/speckled/gp_isi.py @@ -1,16 +1,21 @@ import numpy as np from .speckle_profile import SpeckleProfile +from .stochastic_process_utilities import gen_gaussian_time_series -class GPISIProfile(SpeckleProfile): - """Generate a speckled laser profile with smoothing by spectral dispersion (SSD). +class GP_ISI_Profile(SpeckleProfile): + r"""Generate a speckled laser profile with smoothing inspired by Induced Spatial Incoherence (ISI). - This has temporal smoothing. + This is a smoothing technique with temporal stochastic variation in the beamlet phases and amplitudes + to simulate the random phase differences and amplitudes the beamlets pick up when passing through ISI echelons. + In this case, :math:`\phi_{ml}(t)` and :math:`A_{ml}(t)` are chosen randomly. + Practically, this is done by drawing the complex amplitudes :math:\tilde A_{ml}(t)` + from a stochastic process with Guassian power spectral density having mean of 1 and FWHM of twice the laser bandwidth. Parameters ---------- relative_laser_bandwidth : float - Bandwidth :math:`\Delta_\nu` of the laser pulse, relative to central frequency. + Bandwidth :math:`\Delta_\nu` of the incoming laser pulse, relative to the central frequency. """ def __init__(self, *speckle_args, @@ -18,7 +23,60 @@ def __init__(self, *speckle_args, ): super().__init__(*speckle_args) self.laser_bandwidth = relative_laser_bandwidth - + self.dt_update = 1 / self.laser_bandwidth / 50 + return + + def init_gaussian_time_series( + self, + series_time, + ): + r"""Initialize a time series sampled from a Gaussian process. + + At every time specified by the input `series_time`, calculate the random phases and/or amplitudes. + + * This function returns a time series with random phase offsets in x and y at each time. + The phase offsets are real-valued and centered around the user supplied ``phase_modulation_amplitude`` + :math:`\delta_{x},\delta_{y}`, with distribution FWHM ``phase_modulation_frequency``. + + Parameters + ---------- + series_time: array of times at which to sample from Gaussian process + + Returns + ------- + array-like, the supplied `series_time` + array-like, either with 2 random numbers at every time + """ + complex_amp = np.stack( + [ + np.stack( + [ + gen_gaussian_time_series( + series_time.size, + self.dt_update, + 2 * self.laser_bandwidth, + 1, + ) + for _i in range(self.n_beamlets[1]) + ] + ) + for _j in range(self.n_beamlets[0]) + ] + ) + return complex_amp + + def setup_for_evaluation(self, t_norm): + self.x_y_dephasing = np.random.standard_normal(2) * np.pi + self.phase_plate = np.random.uniform( + -np.pi, np.pi, size=self.n_beamlets[0] * self.n_beamlets[1] + ).reshape(self.n_beamlets) + + t_max = t_norm[-1] + series_time = np.arange(0, t_max + self.dt_update, self.dt_update) + + self.time_series = self.init_gaussian_time_series(series_time) + return + def beamlets_complex_amplitude( self, t_now, ): @@ -32,5 +90,5 @@ def beamlets_complex_amplitude( array of complex numbers giving beamlet amplitude and phases in the near-field """ - return + return self.time_series[:, :, int(round(t_now / self.dt_update))] \ No newline at end of file diff --git a/lasy/profiles/speckled/gp_rpm_ssd.py b/lasy/profiles/speckled/gp_rpm_ssd.py index e69de29bb..7f2796570 100644 --- a/lasy/profiles/speckled/gp_rpm_ssd.py +++ b/lasy/profiles/speckled/gp_rpm_ssd.py @@ -0,0 +1,182 @@ +import numpy as np +from .speckle_profile import SpeckleProfile +from .stochastic_process_utilities import gen_gaussian_time_series + +class GP_RPM_SSD_Profile(SpeckleProfile): + r"""Generate a speckled laser profile with smoothing by a random phase modulated (RPM) spectral dispersion (SSD). + + This provides a version of smoothing by spectral dispersion (SSD) where the phases are randomly modulated. + Here the amplitude of the beamlets is always :math:`A_{ml}(t)=1`. + There are two contributions to the phase :math:`\phi_{ml}` of each beamlet: + + ..math:: + + \phi_{ml}(t) = \phi_{PP,ml} + \phi_{SSD,ml}. + + The phase plate part :math:`\phi_{PP,ml}` is the initial phase delay from the randomly sized phase plate sections, + drawn from uniform distribution on the interval :math:`[0,2\pi]`. + The phases :math:`\phi_{SSD,ml}(t)` are drawn from a stochastic process + with Gaussian power spectrum with means :math:`\delta_x,\delta_y` given by the `phase_modulation_amplitude` argument + and FWHM given by the modulation frequencies :math:`\omega_x,\omega_y`. + The modulation frequencies :math:`\omega_x,\omega_y` are determined by the + laser bandwidth and modulation amplitudes according to the relation + + .. math:: + + \omega_x = \frac{\Delta_\nu r_x }{2\delta_x}, + \omega_y = \frac{\Delta_\nu r_y }{2\delta_y}, + + where :math:`\Delta_\nu` is the resulting relative bandwidth of the laser pulse + and :math:`r_x, r_y` are additional rotation factors supplied by the user + in the `transverse_bandwidth_distribution` parameter that determine + how much of the modulation is in x and how much is in y. [Michel, Eqn. 9.69] + + Parameters + ---------- + + relative_laser_bandwidth : float + Bandwidth :math:`\Delta_\nu` of the laser pulse, relative to central frequency. + Only used if ``temporal_smoothing_type`` is ``'FM SSD'``, ``'GP RPM SSD'`` or ``'GP ISI'``. + + phase_modulation_amplitude :list of 2 floats + Amplitudes :math:`\delta_{x},\delta_{y}` of phase modulation in each transverse direction. + Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. + + number_color_cycles : list of 2 floats + Number of color cycles :math:`N_{cc,x},N_{cc,y}` of SSD spectrum to include in modulation + Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. + + transverse_bandwidth_distribution: list of 2 floats + Determines how much SSD is distributed in the :math:`x` and :math:`y` directions. + if `transverse_bandwidth_distribution=[a,b]`, then the SSD frequency modulation is :math:`a/\sqrt{a^2+b^2}` in :math:`x` and :math:`b/\sqrt{a^2+b^2}` in :math:`y`. + Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. + """ + def __init__(self, *speckle_args, + relative_laser_bandwidth, + phase_modulation_amplitude, + number_color_cycles, + transverse_bandwidth_distribution, + ): + super().__init__(*speckle_args) + self.laser_bandwidth = relative_laser_bandwidth + # the amplitude of phase along each direction + self.phase_modulation_amplitude = phase_modulation_amplitude + # number of color cycles + self.number_color_cycles = number_color_cycles + # bandwidth distributed with respect to the two transverse direction + self.transverse_bandwidth_distribution = ( + transverse_bandwidth_distribution + ) + normalization = np.sqrt( + self.transverse_bandwidth_distribution[0] ** 2 + + self.transverse_bandwidth_distribution[1] ** 2 + ) + frac = [ + self.transverse_bandwidth_distribution[0] / normalization, + self.transverse_bandwidth_distribution[1] / normalization, + ] + self.phase_modulation_frequency = [ + self.laser_bandwidth * sf * 0.5 / pma + for sf, pma in zip(frac, self.phase_modulation_amplitude) + ] + self.time_delay = ( + ( + self.number_color_cycles[0] + / self.phase_modulation_frequency[0] + if self.phase_modulation_frequency[0] > 0 + else 0 + ), + ( + self.number_color_cycles[1] + / self.phase_modulation_frequency[1] + if self.phase_modulation_frequency[1] > 0 + else 0 + ), + ) + self.dt_update = 1 / self.laser_bandwidth / 50 + return + + def init_gaussian_time_series( + self, + series_time, + ): + r"""Initialize a time series sampled from a Gaussian process with the correct power spectral density. + + At every time specified by the input `series_time`, calculate the random phases and/or amplitudes. + + This function returns a time series with random phase offsets in x and y at each time. + The phase offsets are real-valued and centered around the user supplied ``phase_modulation_amplitude`` + :math:`\delta_{x},\delta_{y}`. + The time series has Gaussian power spectral density, or autocorrelation, with a FWHM ``phase_modulation_frequency``. + + Parameters + ---------- + series_time: array of times at which to sample from Gaussian process + time_delay: only required for "SSD" type smoothing + phase_modulation_frequency: only required for "SSD" type smoothing + + Returns + ------- + array-like, the supplied `series_time` with some padding at the end for "SSD" smoothing + array-like, 2 random numbers at every time + """ + pm_phase0 = gen_gaussian_time_series( + series_time.size + int(np.sum(self.time_delay) / self.dt_update) + 2, + self.dt_update, + 2 * np.pi * self.phase_modulation_frequency[0], + self.phase_modulation_amplitude[0], + ) + pm_phase1 = gen_gaussian_time_series( + series_time.size + int(np.sum(self.time_delay) / self.dt_update) + 2, + self.dt_update, + 2 * np.pi * self.phase_modulation_frequency[1], + self.phase_modulation_amplitude[1], + ) + time_interp = np.arange( + start=0, + stop=series_time[-1] + np.sum(self.time_delay) + 3 * self.dt_update, + step=self.dt_update, + )[: pm_phase0.size] + return ( + time_interp, + [ + (np.real(pm_phase0) + np.imag(pm_phase0)) / np.sqrt(2), + (np.real(pm_phase1) + np.imag(pm_phase1)) / np.sqrt(2), + ], + ) + + def setup_for_evaluation(self, t_norm): + self.x_y_dephasing = np.random.standard_normal(2) * np.pi + self.phase_plate = np.random.uniform( + -np.pi, np.pi, size=self.n_beamlets[0] * self.n_beamlets[1] + ).reshape(self.n_beamlets) + + t_max = t_norm[-1] + series_time = np.arange(0, t_max + self.dt_update, self.dt_update) + + self.series_time, self.time_series = self.init_gaussian_time_series(series_time) + return + + def beamlets_complex_amplitude( + self, t_now, + ): + """Calculate complex amplitude of the beamlets in the near-field, before propagating to the focal plane. + + Parameters + ---------- + + Returns + ------- + array of complex numbers giving beamlet amplitude and phases in the near-field + """ + phase_t = np.interp( + t_now + self.X_lens_index_matrix * self.time_delay[0] / self.n_beamlets[0], + self.series_time, + self.time_series[0], + ) + np.interp( + t_now + self.Y_lens_index_matrix * self.time_delay[1] / self.n_beamlets[1], + self.series_time, + self.time_series[1], + ) + return np.exp(1j * (self.phase_plate + phase_t)) + \ No newline at end of file diff --git a/lasy/profiles/speckled/old_speckle_profile.py b/lasy/profiles/speckled/old_speckle_profile.py deleted file mode 100644 index 92a26db30..000000000 --- a/lasy/profiles/speckled/old_speckle_profile.py +++ /dev/null @@ -1,540 +0,0 @@ -import numpy as np -from scipy.constants import c - -from ..profile import Profile - - -def gen_gaussian_time_series(t_num, dt, fwhm, rms_mean): - """Generate a discrete time series that has gaussian power spectrum. - - Parameters - ---------- - t_num: number of grid points in time - fwhm: full width half maximum of the power spectrum - rms_mean: root-mean-square average of the spectrum - - Returns - ------- - temporal_amplitude: a time series array of complex numbers with shape [t_num] - """ - if fwhm == 0.0: - temporal_amplitude = np.zeros(t_num, dtype=np.complex128) - else: - omega = np.fft.fftshift(np.fft.fftfreq(t_num, d=dt)) - psd = np.exp(-np.log(2) * 0.5 * np.square(omega / fwhm * 2 * np.pi)) - spectral_amplitude = np.array(psd) * ( - np.random.normal(size=t_num) + 1j * np.random.normal(size=t_num) - ) - temporal_amplitude = np.fft.ifftshift( - np.fft.fft(np.fft.fftshift(spectral_amplitude)) - ) - temporal_amplitude *= rms_mean / np.sqrt( - np.mean(np.square(np.abs(temporal_amplitude))) - ) - return temporal_amplitude - - -class SpeckleProfile(Profile): - r""" - Class for the profile of a speckled laser pulse. - - Speckled lasers are used to mitigate laser-plasma interactions in fusion and ion acceleration contexts. - More on the subject can be found in chapter 9 of `P. Michel, Introduction to Laser-Plasma Interactions `__. - A speckled laser beam is a laser that is deliberately divided transversely into :math:`N_{bx}\times N_{by}` beamlets in the near-field. - The phase plate provides a different phase to each beamlet, with index :math:`ml`, which then propagate to the far field and combine incoherently. - - The electric field in the focal plane, as a function of time :math:`t` and the coordinates - :math:`\boldsymbol{x}_\perp=(x,y)` transverse to the direction of propagation, is: - - .. math:: - - \begin{aligned} - E_u(\boldsymbol{x}_\perp,t) &= Re\left[ E_0 - {\rm sinc}\left(\frac{\pi x}{\Delta x}\right) - {\rm sinc}\left(\frac{\pi y}{\Delta y}\right)\times p_u - \right. - \\ - & \times\sum_{m,l=1}^{N_{bx}, N_{by}} A_{ml} - \exp\left(i\boldsymbol{k}_{\perp ml}\cdot\boldsymbol{x}_\perp - + i\phi_{{\rm RPP/CPP},ml}+i\psi_{{\rm SSD},ml}(t)\right) - \Bigg] - \end{aligned} - - where :math:`u` is either :math:`x` or :math:`y`, :math:`p_u` is - the polarization vector, and :math:`Re` represent the real part [Michel, Eqns. 9.11, 87, 94]. - Several quantities are computed internally to the code depending on the - method of smoothing chosen, including the beamlet amplitude :math:`A_{ml}`, - the beamlet wavenumber :math:`k_{\perp ml}`, - the relative phase contribution :math:`\phi_{{\rm RPP/CPP},ml}` of beamlet :math:`ml` induced by the phase plate, - and the phase contribution to beamlet :math:`ml`, :math:`\psi_{{\rm SSD},ml}(t)`, from the temporal smoothing. - The beam widths are :math:`\Delta x=\frac{\lambda_0fN_{bx}}{D_{x}}`, - :math:`\Delta y=\frac{\lambda_0fN_{by}}{D_{y}}`. - The other parameters in these formulas are defined below. - - This profile admits several options for calculating the amplitudes and phases of the beamlets: - - * Random phase plates (RPP), ``'RPP'``: - Here the phase plate contribution to the phase of beamlet :math:`ml` is :math:`\phi_{{\rm RPP},ml}\in\{0,\pi\}` (drawn randomly for each :math:`m,l`), :math:`\psi_{{\rm SSD},ml}(t)=0`, and :math:`A_{ml}=1` - * Continuous phase plates (CPP), ``'CPP'``: - :math:`\phi_{{\rm CPP},ml}\in[0,\pi]` (drawn randomly with uniform probability between :math:`0` and :math:`\pi`, for each :math:`m,l`), :math:`\psi_{{\rm SSD},ml}(t)=0`, and :math:`A_{ml}=1` - * CPP + Smoothing by spectral dispersion (SSD), ``'FM SSD'``: - :math:`\phi_{{\rm CPP},ml}\in[0,\pi]` (drawn randomly with uniform probability between :math:`0` and :math:`\pi`, for each :math:`m,l`), - - .. math:: - - \begin{aligned} - \psi_{{\rm SSD},ml}(t)&=\delta_{x} \sin\left(\omega_{x} t + 2\pi\frac{mN_{cc,x}}{N_{bx}}\right)\\ - &+\delta_{y} \sin\left(\omega_{y} t + 2\pi\frac{lN_{cc,y}}{N_{by}}\right), - \end{aligned} - - and :math:`A_{ml}=1`. The modulation frequencies :math:`\omega_x,\omega_y` are determined by the - laser bandwidth and modulation amplitudes by the relation - - .. math:: - - \omega_x = \frac{\Delta_\nu r }{2\delta_x} - - where :math:`\Delta_\nu` is the relative bandwidth of the laser pulse and :math:`r` is an additional rotation factor - supplied by the user that determines how much of the modulation is in x and how much is in y. [Michel, Eqn. 9.69] - - * Gaussian Process Randomly phase-modulated SSD, ``'GP RPM SSD'``: - CPP + a generalization of SSD that has temporal stochastic variation in the beamlet phases; that is, :math:`\phi_{{\rm CPP},ml}\in[0,\pi]`, :math:`\psi_{{\rm SSD},ml}(t)` is sampled from a Gaussian stochastic process, and :math:`A_{ml}=1` - * Induced spatial incoherence (ISI), ``'GP ISI'``: - a smoothing technique with temporal stochastic variation in the beamlet phases and amplitudes; that is, :math:`\phi_{{\rm CPP},ml}=0`, :math:`\psi_{{\rm SSD},ml}(t)=0`, and the :math:`A_{ml}` are complex amplitudes sampled from a Gaussian stochastic process to simulate the random phase differences and amplitudes the beamlets pick up when passing through the ISI echelons - - This is an adapation of work by `Han Wen `__ to LASY. - - - Notes - ----- - This assumes a flat-top rectangular laser and so a rectangular arrangement of beamlets in the near-field. - - Parameters - ---------- - wavelength : float (in meters) - The main laser wavelength :math:`\lambda_0` of the laser, which - defines :math:`\omega_0` in the above formula, according to - :math:`\omega_0 = 2\pi c/\lambda_0`. - - pol : list of 2 complex numbers (dimensionless) - Polarization vector. It corresponds to :math:`p_u` in the above - formula ; :math:`p_x` is the first element of the list and - :math:`p_y` is the second element of the list. Using complex - numbers enables elliptical polarizations. - - laser_energy : float (in Joules) - The total energy of the laser pulse. The amplitude of the laser - field (:math:`E_0` in the above formula) is automatically - calculated so that the pulse has the prescribed energy. - - focal_length : float (in meters) - Focal length of lens :math:`f` just after the RPP/CPP. - - beam_aperture : list of 2 floats (in meters) - Widths :math:`D_x,D_y` of the rectangular beam in the near-field, i.e., size of the illuminated region of the RPP/CPP. - - n_beamlets : list of 2 integers - Number of RPP/CPP elements :math:`N_{bx},N_{by}` in each direction, in the near field. - - temporal_smoothing_type : string - Which method for beamlet production and evolution is used. - Can be ``'RPP'``, ``'CPP'``, ``'FM SSD'``, ``'GP RPM SSD'``, or ``'GP ISI'``. - (See the above bullet list for an explanation of each of these methods.) - - relative_laser_bandwidth : float - Bandwidth :math:`\Delta_\nu` of the laser pulse, relative to central frequency. - Only used if ``temporal_smoothing_type`` is ``'FM SSD'``, ``'GP RPM SSD'`` or ``'GP ISI'``. - - ssd_phase_modulation_amplitude :list of 2 floats - Amplitudes :math:`\delta_{x},\delta_{y}` of phase modulation in each transverse direction. - Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. - - ssd_number_color_cycles : list of 2 floats - Number of color cycles :math:`N_{cc,x},N_{cc,y}` of SSD spectrum to include in modulation - Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. - - ssd_transverse_bandwidth_distribution: list of 2 floats - Determines how much SSD is distributed in the :math:`x` and :math:`y` directions. - if `ssd_transverse_bandwidth_distribution=[a,b]`, then the SSD frequency modulation is :math:`a/\sqrt{a^2+b^2}` in :math:`x` and :math:`b/\sqrt{a^2+b^2}` in :math:`y`. - Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. - - do_include_transverse_envelope : boolean, (optional, default False) - Whether to include the transverse sinc envelope or not. - I.e. whether it is assumed to be close enough to the laser axis to neglect the transverse field decay. - """ - - supported_smoothing = "RPP", "CPP", "FM SSD", "GP RPM SSD", "GP ISI" - - def __init__( - self, - wavelength, - pol, - laser_energy, - focal_length, - beam_aperture, - n_beamlets, - temporal_smoothing_type, - relative_laser_bandwidth, - ssd_phase_modulation_amplitude=None, - ssd_number_color_cycles=None, - ssd_transverse_bandwidth_distribution=None, - do_include_transverse_envelope=False, - ): - super().__init__(wavelength, pol) - self.laser_energy = laser_energy - self.focal_length = focal_length - self.beam_aperture = np.array(beam_aperture, dtype="float") - self.n_beamlets = np.array(n_beamlets, dtype="int") - self.temporal_smoothing_type = temporal_smoothing_type - self.laser_bandwidth = relative_laser_bandwidth - - # time interval to update the speckle pattern, roughly update 50 times every bandwidth cycle - self.dt_update = 1 / self.laser_bandwidth / 50 - self.do_include_transverse_envelope = do_include_transverse_envelope - - self.x_lens_list = np.linspace( - -0.5 * (self.n_beamlets[0] - 1), - 0.5 * (self.n_beamlets[0] - 1), - num=self.n_beamlets[0], - ) - self.y_lens_list = np.linspace( - -0.5 * (self.n_beamlets[1] - 1), - 0.5 * (self.n_beamlets[1] - 1), - num=self.n_beamlets[1], - ) - self.Y_lens_matrix, self.X_lens_matrix = np.meshgrid( - self.y_lens_list, self.x_lens_list - ) - self.Y_lens_index_matrix, self.X_lens_index_matrix = np.meshgrid( - np.arange(self.n_beamlets[1], dtype=float), - np.arange(self.n_beamlets[0], dtype=float), - ) - - if "SSD" in self.temporal_smoothing_type.upper(): - # Initialize SSD parameters - # the amplitude of phase along each direction - self.ssd_phase_modulation_amplitude = ssd_phase_modulation_amplitude - # number of color cycles - self.ssd_number_color_cycles = ssd_number_color_cycles - # bandwidth distributed with respect to the two transverse direction - self.ssd_transverse_bandwidth_distribution = ( - ssd_transverse_bandwidth_distribution - ) - ssd_normalization = np.sqrt( - self.ssd_transverse_bandwidth_distribution[0] ** 2 - + self.ssd_transverse_bandwidth_distribution[1] ** 2 - ) - ssd_frac = [ - self.ssd_transverse_bandwidth_distribution[0] / ssd_normalization, - self.ssd_transverse_bandwidth_distribution[1] / ssd_normalization, - ] - self.ssd_phase_modulation_frequency = [ - self.laser_bandwidth * sf * 0.5 / pma - for sf, pma in zip(ssd_frac, self.ssd_phase_modulation_amplitude) - ] - self.ssd_time_delay = ( - ( - self.ssd_number_color_cycles[0] - / self.ssd_phase_modulation_frequency[0] - if self.ssd_phase_modulation_frequency[0] > 0 - else 0 - ), - ( - self.ssd_number_color_cycles[1] - / self.ssd_phase_modulation_frequency[1] - if self.ssd_phase_modulation_frequency[1] > 0 - else 0 - ), - ) - - # Check user input - assert ( - temporal_smoothing_type.upper() in SpeckleProfile.supported_smoothing - ), "Only support one of the following: " + ", ".join( - SpeckleProfile.supported_smoothing - ) - assert relative_laser_bandwidth > 0, "laser_bandwidth must be greater than 0" - assert np.size(n_beamlets) == 2, "has to be a size 2 array" - if "SSD" in self.temporal_smoothing_type.upper(): - assert ( - ssd_number_color_cycles is not None - ), "must supply `ssd_number_color_cycles` to use SSD" - assert ( - ssd_transverse_bandwidth_distribution is not None - ), "must supply `ssd_transverse_bandwidth_distribution` to use SSD" - assert ( - ssd_phase_modulation_amplitude is not None - ), "must supply `ssd_phase_modulation_amplitude` to use SSD" - for q in ( - ssd_number_color_cycles, - ssd_transverse_bandwidth_distribution, - ssd_phase_modulation_amplitude, - ): - assert np.size(q) == 2, "has to be a size 2 array" - assert q[0] > 0 or q[1] > 0, "cannot be all zeros" - - def init_gaussian_time_series( - self, - series_time, - ): - r"""Initialize a time series sampled from a Gaussian process. - - At every time specified by the input `series_time`, calculate the random phase and/or amplitudes as determined by the smoothing type. - - * If the smoothing type is "SSD", then this function returns a time series with random phase offsets in x and y at each time. - The phase offsets are real-valued and centered around the user supplied ``ssd_phase_modulation_amplitude`` - :math:`\delta_{x},\delta_{y}`, with distribution FWHM ``ssd_phase_modulation_frequency``. - - If the smoothing type is "ISI", this function returns a time series with complex numbers defining beamlet phase and amplitude. - Each beamlet has a complex number whose magnitude is the amplitude of the beamlet and - whose phase gives the beamlet phase offset. - The complex numbers have mean 1 and FWHM given by twice the laser bandwidth - - Parameters - ---------- - series_time: array of times at which to sample from Gaussian process - ssd_time_delay: only required for "SSD" type smoothing - ssd_phase_modulation_frequency: only required for "SSD" type smoothing - - Returns - ------- - array-like, either the supplied `series_time` if "ISI" smoothing or `series_time` with some padding at the end for "SSD" smoothing - array-like, either with 2 (for "SSD" smoothing) or `n_beamlets[0] x n_beamlets[1]` ("ISI" smoothing) random numbers at every time - """ - if "SSD" in self.temporal_smoothing_type.upper(): - pm_phase0 = gen_gaussian_time_series( - series_time.size - + int(np.sum(self.ssd_time_delay) / self.dt_update) - + 2, - self.dt_update, - 2 * np.pi * self.ssd_phase_modulation_frequency[0], - self.ssd_phase_modulation_amplitude[0], - ) - pm_phase1 = gen_gaussian_time_series( - series_time.size - + int(np.sum(self.ssd_time_delay) / self.dt_update) - + 2, - self.dt_update, - 2 * np.pi * self.ssd_phase_modulation_frequency[1], - self.ssd_phase_modulation_amplitude[1], - ) - time_interp = np.arange( - start=0, - stop=series_time[-1] + np.sum(self.ssd_time_delay) + 3 * self.dt_update, - step=self.dt_update, - )[: pm_phase0.size] - return ( - time_interp, - [ - (np.real(pm_phase0) + np.imag(pm_phase0)) / np.sqrt(2), - (np.real(pm_phase1) + np.imag(pm_phase1)) / np.sqrt(2), - ], - ) - elif "ISI" in self.temporal_smoothing_type.upper(): - complex_amp = np.stack( - [ - np.stack( - [ - gen_gaussian_time_series( - series_time.size, - self.dt_update, - 2 * self.laser_bandwidth, - 1, - ) - for _i in range(self.n_beamlets[1]) - ] - ) - for _j in range(self.n_beamlets[0]) - ] - ) - return series_time, complex_amp - else: - return series_time, None - - def beamlets_complex_amplitude( - self, t_now, series_time, time_series, temporal_smoothing_type="FM SSD" - ): - """Calculate complex amplitude of the beamlets in the near-field, before propagating to the focal plane. - - If the temporal smoothing type is "RPP" or "CPP", this returns a matrix of ones, giving no modification to the amplitude. - If the temporal smoothing type is "FM SSD", this returns the complex phases as calculated in, for example, Introduction to Laser-Plasma Interactions eqn. 9.87. - If the temporal smoothing type is "GP RPM FM ", this returns complex phases modeled as random variables. - If the temporal smoothing type is "ISI", this returns an array of random complex numbers that gives both amplitude and phase of the beamlets. - - Parameters - ---------- - t_now: float, time at which to calculate the complex amplitude of the beamlets - series_time: 1d array of times at which the stochastic process was sampled to generate the time series - time_series: array of random phase and/or amplitudes as determined by the smoothing type - temporal_smoothing_type: string, what type of temporal smoothing to perform. - - Returns - ------- - array of complex numbers giving beamlet amplitude and phases in the near-field - """ - if any( - rpp_type in temporal_smoothing_type.upper() for rpp_type in ["RPP", "CPP"] - ): - return np.ones_like(self.X_lens_matrix) - if temporal_smoothing_type.upper() == "FM SSD": - phase_t = self.ssd_phase_modulation_amplitude[0] * np.sin( - self.ssd_x_y_dephasing[0] - + 2 - * np.pi - * self.ssd_phase_modulation_frequency[0] - * ( - t_now - - self.X_lens_matrix * self.ssd_time_delay[0] / self.n_beamlets[0] - ) - ) + self.ssd_phase_modulation_amplitude[1] * np.sin( - self.ssd_x_y_dephasing[1] - + 2 - * np.pi - * self.ssd_phase_modulation_frequency[1] - * ( - t_now - - self.Y_lens_matrix * self.ssd_time_delay[1] / self.n_beamlets[1] - ) - ) - return np.exp(1j * phase_t) - elif temporal_smoothing_type.upper() == "GP RPM SSD": - phase_t = np.interp( - t_now - + self.X_lens_index_matrix - * self.ssd_time_delay[0] - / self.n_beamlets[0], - series_time, - time_series[0], - ) + np.interp( - t_now - + self.Y_lens_index_matrix - * self.ssd_time_delay[1] - / self.n_beamlets[1], - series_time, - time_series[1], - ) - return np.exp(1j * phase_t) - elif temporal_smoothing_type.upper() == "GP ISI": - return time_series[:, :, int(round(t_now / self.dt_update))] - else: - raise NotImplementedError - - def generate_speckle_pattern( - self, t_now, exp_phase_plate, x, y, series_time, time_series - ): - """Calculate the speckle pattern in the focal plane. - - Calculates the complex envelope defining the laser pulse in the focal plane at time `t=t_now`. - This function first gets the beamlet complex amplitudes and phases with the function `beamlets_complex_amplitude` - then propagates the the beamlets to the focal plane. - - Parameters - ---------- - t_now: float, time at which to calculate the speckle pattern - exp_phase_plate: 2d array of complex numbers giving the RPP / CPP phase contributions to the beamlets - x: 3d array of x-positions in focal plane - y: 3d array of y-positions in focal plane - series_time: 1d array of times at which the stochastic process was sampled to generate the time series - time_series: array of random phase and/or amplitudes as determined by the smoothing type - - Returns - ------- - speckle_amp: 2D array of complex numbers defining the laser envelope at focus at time `t_now` - """ - lambda_fnum = self.lambda0 * self.focal_length / self.beam_aperture - X_focus_matrix = x[:, :, 0] / lambda_fnum[0] - Y_focus_matrix = y[:, :, 0] / lambda_fnum[1] - x_focus_list = X_focus_matrix[:, 0] - y_focus_list = Y_focus_matrix[0, :] - x_phase_focus_matrix = np.exp( - -2 - * np.pi - * 1j - / self.n_beamlets[0] - * self.x_lens_list[:, np.newaxis] - * x_focus_list[np.newaxis, :] - ) - y_phase_focus_matrix = np.exp( - -2 - * np.pi - * 1j - / self.n_beamlets[1] - * self.y_lens_list[:, np.newaxis] - * y_focus_list[np.newaxis, :] - ) - - bca = self.beamlets_complex_amplitude( - t_now, - series_time=series_time, - time_series=time_series, - temporal_smoothing_type=self.temporal_smoothing_type, - ) - speckle_amp = np.einsum( - "jk,jl->kl", - np.einsum("ij,ik->jk", bca * exp_phase_plate, x_phase_focus_matrix), - y_phase_focus_matrix, - ) - if self.do_include_transverse_envelope: - speckle_amp = ( - np.sinc(X_focus_matrix / self.n_beamlets[0]) - * np.sinc(Y_focus_matrix / self.n_beamlets[1]) - * speckle_amp - ) - return speckle_amp - - def evaluate(self, x, y, t): - """ - Return the envelope field of the laser. - - Parameters - ---------- - x, y, t: ndarrays of floats - Define points on which to evaluate the envelope - These arrays need to all have the same shape. - - Returns - ------- - envelope: ndarray of complex numbers - Contains the value of the envelope at the specified points - This array has the same shape as the arrays x, y, t - """ - # General parameters - t_norm = t[0, 0, :] * c / self.lambda0 - t_max = t_norm[-1] - - # Calculate auxiliary parameters - if "RPP" == self.temporal_smoothing_type.upper(): - phase_plate = np.random.choice([0, np.pi], self.n_beamlets) - elif any( - cpp_smoothing_type in self.temporal_smoothing_type.upper() - for cpp_smoothing_type in ["CPP", "SSD"] - ): - phase_plate = np.random.uniform( - -np.pi, np.pi, size=self.n_beamlets[0] * self.n_beamlets[1] - ).reshape(self.n_beamlets) - elif "ISI" in self.temporal_smoothing_type.upper(): - phase_plate = np.zeros(self.n_beamlets) # ISI does not require phase plates - else: - raise NotImplementedError - exp_phase_plate = np.exp(1j * phase_plate) - if self.temporal_smoothing_type.upper() == "FM SSD": - self.ssd_x_y_dephasing = np.random.standard_normal(2) * np.pi - - series_time = np.arange(0, t_max + self.dt_update, self.dt_update) - - if "GP" in self.temporal_smoothing_type.upper(): - new_series_time, time_series = self.init_gaussian_time_series(series_time) - else: - new_series_time, time_series = series_time, None - - envelope = np.zeros(x.shape, dtype=complex) - for i, t_i in enumerate(t_norm): - envelope[:, :, i] = self.generate_speckle_pattern( - t_i, - exp_phase_plate=exp_phase_plate, - x=x, - y=y, - series_time=new_series_time, - time_series=time_series, - ) - return envelope diff --git a/lasy/profiles/speckled/rpp_cpp.py b/lasy/profiles/speckled/rpp_cpp.py index df03b7407..1ea814be9 100644 --- a/lasy/profiles/speckled/rpp_cpp.py +++ b/lasy/profiles/speckled/rpp_cpp.py @@ -5,10 +5,15 @@ class PhasePlateProfile(SpeckleProfile): """Generate a speckled laser profile with a random phase plate. This has no temporal smoothing. + The amplitude of the beamlets is always :math:`A_{ml}(t)=1` and + the relative phases of the beamlets, resulting from the randomly sized phase plate sections, + are assigned randomly. + If the user specifies Random Phase Plate (RPP: `rpp`), the beamlet phases are drawn with equal probabilities from the set :math:`{0,2\pi}`. + If the user specifies Continuous Phase Plate (CPP: `cpp`), the beamlet phases are drawn from a uniform distribution on the interval :math:`[0,2\pi]`. Parameters ---------- - rpp_cpp: string, keyword only, can be 'rpp' or 'cpp' + rpp_cpp: string, keyword only, can be 'rpp' or 'cpp', whether to assign beamlet phases according to RPP or CPP scheme """ def __init__(self, *speckle_args, rpp_cpp): super().__init__(*speckle_args) diff --git a/lasy/profiles/speckled/speckle_profile.py b/lasy/profiles/speckled/speckle_profile.py index 4565e74a8..255800929 100644 --- a/lasy/profiles/speckled/speckle_profile.py +++ b/lasy/profiles/speckled/speckle_profile.py @@ -1,16 +1,7 @@ import numpy as np from scipy.constants import c - from ..profile import Profile -###NOTES### -# would be good to discuss bandwidth meanings in ISI vs SSM -# how to include doc strings of parent class -# reference r in doc string -# temporal envelope -# should all arguments be specified for every class? Should non-base arguments be kw only arguments? -########### - class SpeckleProfile(Profile): r""" Derived class for the profile of a speckled laser pulse. @@ -31,7 +22,7 @@ class SpeckleProfile(Profile): {\rm sinc}\left(\frac{\pi y}{\Delta y}\right)\times p_u \right. \\ - & \times\sum_{m,l=1}^{N_{bx}, N_{by}} A_{ml} + & \times\sum_{m,l=1}^{N_{bx}, N_{by}} A_{ml}(t) \exp\left(i\boldsymbol{k}_{\perp ml}\cdot\boldsymbol{x}_\perp + i\phi_{ml}(t)\right) \Bigg] @@ -40,14 +31,14 @@ class SpeckleProfile(Profile): where :math:`u` is either :math:`x` or :math:`y`, :math:`p_u` is the polarization vector, and :math:`Re` represent the real part [Michel, Eqns. 9.11, 87, 94]. Several quantities are computed internally to the code depending on the - method of smoothing chosen, including the beamlet amplitude :math:`A_{ml}`, + method of smoothing chosen, including the beamlet amplitude :math:`A_{ml}(t)`, the beamlet wavenumber :math:`k_{\perp ml}`, the relative phase contribution :math:`\phi_{ml}(t)` of beamlet :math:`ml` induced by the phase plate and temporal smoothing. The beam widths are :math:`\Delta x=\frac{\lambda_0fN_{bx}}{D_{x}}`, :math:`\Delta y=\frac{\lambda_0fN_{by}}{D_{y}}`. The other parameters in these formulas are defined below. - This is an adapation of work by `Han Wen `__ to LASY. + This is an adaptation of work by `Han Wen `__ to LASY. Notes @@ -126,15 +117,22 @@ def beamlets_complex_amplitude( self, t_now, ): """Calculate complex amplitude of the beamlets in the near-field, before propagating to the focal plane. + This function can be overwritten to define custom speckled laser objects. Parameters ---------- + t_now: float, time at which to evaluate complex amplitude Returns ------- array of complex numbers giving beamlet amplitude and phases in the near-field """ return np.ones_like(self.X_lens_matrix) + + def setup_for_evaluation(self, t_norm): + val = 20 + print("Im setting up for evaluation!!") + return val + 5 def generate_speckle_pattern(self, t_now, x, y): """Calculate the speckle pattern in the focal plane. @@ -200,6 +198,7 @@ def evaluate(self, x, y, t): """ # General parameters t_norm = t[0, 0, :] * c / self.lambda0 + self.setup_for_evaluation(t_norm) envelope = np.zeros(x.shape, dtype=complex) for i, t_i in enumerate(t_norm): diff --git a/lasy/profiles/speckled/stochastic_process_utilities.py b/lasy/profiles/speckled/stochastic_process_utilities.py index 3eebd32c3..b99f32377 100644 --- a/lasy/profiles/speckled/stochastic_process_utilities.py +++ b/lasy/profiles/speckled/stochastic_process_utilities.py @@ -3,7 +3,7 @@ def gen_gaussian_time_series(t_num, dt, fwhm, rms_mean): """Generate a discrete time series that has gaussian power spectrum. - Credit Han Wen + Credit Han Wen, possibly NRL Parameters ---------- diff --git a/tests/test_laser_profiles.py b/tests/test_laser_profiles.py index eca27bcea..678ec3ecc 100644 --- a/tests/test_laser_profiles.py +++ b/tests/test_laser_profiles.py @@ -7,7 +7,7 @@ from lasy.laser import Laser from lasy.profiles.profile import Profile, SummedProfile, ScaledProfile -from lasy.profiles import GaussianProfile, FromArrayProfile, SpeckleProfile +from lasy.profiles import GaussianProfile, FromArrayProfile from lasy.profiles.longitudinal import ( GaussianLongitudinalProfile, SuperGaussianLongitudinalProfile, @@ -24,6 +24,7 @@ SummedTransverseProfile, ScaledTransverseProfile, ) +from lasy.profiles.speckled import GP_ISI_Profile from lasy.utils.exp_data_utils import find_center_of_mass @@ -314,17 +315,17 @@ def test_speckle_profile(): focal_length = 3.5 # m beam_aperture = [0.35, 0.5] # m n_beamlets = [24, 32] - temporal_smoothing_type = "GP ISI" + do_sinc_profile=False relative_laser_bandwidth = 0.005 - profile = SpeckleProfile( + profile = GP_ISI_Profile( wavelength, polarization, laser_energy, focal_length, beam_aperture, n_beamlets, - temporal_smoothing_type=temporal_smoothing_type, + do_sinc_profile, relative_laser_bandwidth=relative_laser_bandwidth, ) dimensions = "xyt" diff --git a/tests/test_speckles.py b/tests/test_speckles.py index a2a0e4ed4..88f395cf1 100644 --- a/tests/test_speckles.py +++ b/tests/test_speckles.py @@ -1,14 +1,17 @@ import numpy as np - from lasy.laser import Laser -from lasy.profiles.speckled import PhasePlateProfile -from lasy.profiles.speckled import FMSSDProfile +from lasy.profiles.speckled import ( + FM_SSD_Profile, + GP_ISI_Profile, + GP_RPM_SSD_Profile, + PhasePlateProfile, +) import pytest from scipy.constants import c @pytest.mark.parametrize( - "temporal_smoothing_type", ["RPP", "CPP", "FM SSD"] + "temporal_smoothing_type", ["RPP", "CPP", "FM SSD", "GP RPM SSD", "GP ISI"] ) def test_intensity_distribution(temporal_smoothing_type): """Test whether the spatial intensity distribution and statisticis are correct. @@ -30,17 +33,32 @@ def test_intensity_distribution(temporal_smoothing_type): phase_modulation_amplitude = (4.1, 4.5) number_color_cycles = [1.4, 1.0] transverse_bandwidth_distribution = [1.8, 1.0] + ssd_args = { + "relative_laser_bandwidth":relative_laser_bandwidth, + "phase_modulation_amplitude":phase_modulation_amplitude, + "number_color_cycles":number_color_cycles, + "transverse_bandwidth_distribution":transverse_bandwidth_distribution, + } if temporal_smoothing_type.upper() in ["RPP", "CPP"]: profile = PhasePlateProfile(*speckle_args,rpp_cpp=temporal_smoothing_type) - elif temporal_smoothing_type == "FM SSD": - profile = FMSSDProfile( + elif temporal_smoothing_type.upper() == "FM SSD": + profile = FM_SSD_Profile( + *speckle_args, + **ssd_args, + ) + elif temporal_smoothing_type.upper() == "GP RPM SSD": + profile = GP_RPM_SSD_Profile( + *speckle_args, + **ssd_args, + ) + elif temporal_smoothing_type.upper() == "GP ISI": + profile = GP_ISI_Profile( *speckle_args, relative_laser_bandwidth=relative_laser_bandwidth, - phase_modulation_amplitude=phase_modulation_amplitude, - number_color_cycles=number_color_cycles, - transverse_bandwidth_distribution=transverse_bandwidth_distribution, ) + else: + print('invalid smoothing type provided') dimensions = "xyt" dx = wavelength * focal_length / beam_aperture[0] dy = wavelength * focal_length / beam_aperture[1] @@ -77,7 +95,7 @@ def test_intensity_distribution(temporal_smoothing_type): @pytest.mark.parametrize( - "temporal_smoothing_type", ["RPP", "CPP", "FM SSD"] #, "GP RPM SSD", "GP ISI"] + "temporal_smoothing_type", ["RPP", "CPP", "FM SSD", "GP RPM SSD", "GP ISI"] ) def test_spatial_correlation(temporal_smoothing_type): """Tests whether the speckles have the correct shape. @@ -99,17 +117,32 @@ def test_spatial_correlation(temporal_smoothing_type): phase_modulation_amplitude = (4.1, 4.1) number_color_cycles = [1.4, 1.0] transverse_bandwidth_distribution = [1.0, 1.0] + ssd_args = { + "relative_laser_bandwidth":relative_laser_bandwidth, + "phase_modulation_amplitude":phase_modulation_amplitude, + "number_color_cycles":number_color_cycles, + "transverse_bandwidth_distribution":transverse_bandwidth_distribution, + } if temporal_smoothing_type.upper() in ["RPP", "CPP"]: profile = PhasePlateProfile(*speckle_args,rpp_cpp=temporal_smoothing_type) - elif temporal_smoothing_type == "FM SSD": - profile = FMSSDProfile( + elif temporal_smoothing_type.upper() == "FM SSD": + profile = FM_SSD_Profile( + *speckle_args, + **ssd_args, + ) + elif temporal_smoothing_type.upper() == "GP RPM SSD": + profile = GP_RPM_SSD_Profile( + *speckle_args, + **ssd_args, + ) + elif temporal_smoothing_type.upper() == "GP ISI": + profile = GP_ISI_Profile( *speckle_args, relative_laser_bandwidth=relative_laser_bandwidth, - phase_modulation_amplitude=phase_modulation_amplitude, - number_color_cycles=number_color_cycles, - transverse_bandwidth_distribution=transverse_bandwidth_distribution, ) + else: + print('invalid smoothing type provided') dimensions = "xyt" dx = wavelength * focal_length / beam_aperture[0] dy = wavelength * focal_length / beam_aperture[1] @@ -148,7 +181,7 @@ def test_spatial_correlation(temporal_smoothing_type): @pytest.mark.parametrize( - "temporal_smoothing_type", ["RPP", "CPP", "FM SSD"]#, "GP RPM SSD", "GP ISI"] + "temporal_smoothing_type", ["RPP", "CPP", "FM SSD", "GP RPM SSD", "GP ISI"] ) def test_sinc_zeros(temporal_smoothing_type): """Test whether the transverse sinc envelope has the correct width @@ -181,17 +214,32 @@ def test_sinc_zeros(temporal_smoothing_type): phase_modulation_amplitude = (4.1, 4.1) number_color_cycles = [1.4, 1.0] transverse_bandwidth_distribution = [1.0, 1.0] + ssd_args = { + "relative_laser_bandwidth":relative_laser_bandwidth, + "phase_modulation_amplitude":phase_modulation_amplitude, + "number_color_cycles":number_color_cycles, + "transverse_bandwidth_distribution":transverse_bandwidth_distribution, + } if temporal_smoothing_type.upper() in ["RPP", "CPP"]: profile = PhasePlateProfile(*speckle_args,rpp_cpp=temporal_smoothing_type) - elif temporal_smoothing_type == "FM SSD": - profile = FMSSDProfile( + elif temporal_smoothing_type.upper() == "FM SSD": + profile = FM_SSD_Profile( + *speckle_args, + **ssd_args, + ) + elif temporal_smoothing_type.upper() == "GP RPM SSD": + profile = GP_RPM_SSD_Profile( + *speckle_args, + **ssd_args, + ) + elif temporal_smoothing_type.upper() == "GP ISI": + profile = GP_ISI_Profile( *speckle_args, relative_laser_bandwidth=relative_laser_bandwidth, - phase_modulation_amplitude=phase_modulation_amplitude, - number_color_cycles=number_color_cycles, - transverse_bandwidth_distribution=transverse_bandwidth_distribution, ) + else: + print('invalid smoothing type provided') dimensions = "xyt" dx = wavelength * focal_length / beam_aperture[0] dy = wavelength * focal_length / beam_aperture[1] @@ -229,7 +277,7 @@ def test_FM_periodicity(): number_color_cycles = [1.4, 1.0] transverse_bandwidth_distribution = [1.0, 1.0] - laser_profile = FMSSDProfile( + laser_profile = FM_SSD_Profile( *speckle_args, relative_laser_bandwidth=relative_laser_bandwidth, phase_modulation_amplitude=phase_modulation_amplitude, From a09be99f8767a128b54754b7bfbf68c797ad7d2c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 00:22:04 +0000 Subject: [PATCH 03/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/source/api/profiles/speckled/fm_ssd.rst | 2 +- docs/source/api/profiles/speckled/gp_isi.rst | 2 +- .../api/profiles/speckled/gp_rpm_ssd.rst | 2 +- docs/source/api/profiles/speckled/index.rst | 1 - lasy/profiles/speckled/fm_ssd.py | 52 +++++------ lasy/profiles/speckled/gp_isi.py | 18 ++-- lasy/profiles/speckled/gp_rpm_ssd.py | 40 ++++----- lasy/profiles/speckled/rpp_cpp.py | 8 +- lasy/profiles/speckled/speckle_profile.py | 20 ++++- .../speckled/stochastic_process_utilities.py | 1 + tests/test_laser_profiles.py | 2 +- tests/test_speckles.py | 86 +++++++++++++------ 12 files changed, 142 insertions(+), 92 deletions(-) diff --git a/docs/source/api/profiles/speckled/fm_ssd.rst b/docs/source/api/profiles/speckled/fm_ssd.rst index ab494e708..4ee132189 100644 --- a/docs/source/api/profiles/speckled/fm_ssd.rst +++ b/docs/source/api/profiles/speckled/fm_ssd.rst @@ -4,6 +4,6 @@ Frequency-modulated Smoothing by Spectral Dispersion (FM-SSD) Laser Profile .. autoclass:: lasy.profiles.SpeckleProfile :no-index: :members: - + .. autoclass:: lasy.profiles.speckled.FM_SSD_Profile :members: diff --git a/docs/source/api/profiles/speckled/gp_isi.rst b/docs/source/api/profiles/speckled/gp_isi.rst index 1906903ca..f84a65edb 100644 --- a/docs/source/api/profiles/speckled/gp_isi.rst +++ b/docs/source/api/profiles/speckled/gp_isi.rst @@ -4,6 +4,6 @@ Smoothing by Induced Spatial Incoherence (ISI) Laser Profile .. autoclass:: lasy.profiles.SpeckleProfile :no-index: :members: - + .. autoclass:: lasy.profiles.speckled.GP_ISI_Profile :members: diff --git a/docs/source/api/profiles/speckled/gp_rpm_ssd.rst b/docs/source/api/profiles/speckled/gp_rpm_ssd.rst index 62f1413f0..c26ae1cea 100644 --- a/docs/source/api/profiles/speckled/gp_rpm_ssd.rst +++ b/docs/source/api/profiles/speckled/gp_rpm_ssd.rst @@ -4,6 +4,6 @@ Random Phase Modulated (RPM) Smoothing by Spectral Dispersion (FM-SSD) Laser Pro .. autoclass:: lasy.profiles.SpeckleProfile :no-index: :members: - + .. autoclass:: lasy.profiles.speckled.GP_RPM_SSD_Profile :members: diff --git a/docs/source/api/profiles/speckled/index.rst b/docs/source/api/profiles/speckled/index.rst index 45b09da2e..971255429 100644 --- a/docs/source/api/profiles/speckled/index.rst +++ b/docs/source/api/profiles/speckled/index.rst @@ -10,4 +10,3 @@ Speckled Laser Profiles fm_ssd gp_rpm_ssd gp_isi - \ No newline at end of file diff --git a/lasy/profiles/speckled/fm_ssd.py b/lasy/profiles/speckled/fm_ssd.py index fd868f2b7..d1bcdf187 100644 --- a/lasy/profiles/speckled/fm_ssd.py +++ b/lasy/profiles/speckled/fm_ssd.py @@ -1,18 +1,19 @@ import numpy as np from .speckle_profile import SpeckleProfile + class FM_SSD_Profile(SpeckleProfile): r"""Generate a speckled laser profile with smoothing by frequency modulated (FM) spectral dispersion (SSD). In frequency-modulated smoothing by spectral dispersion, or FM-SSD, the amplitude of the beamlets is always :math:`A_{ml}(t)=1`. There are two contributions to the phase :math:`\phi_{ml}` of each beamlet: - + .. math:: - + \phi_{ml}(t)=\phi_{PP,ml}+\phi_{SSD,ml}. The phase plate part :math:`\phi_{PP,ml}` is the initial phase delay from the randomly sized phase plate sections, - drawn from uniform distribution on the interval :math:`[0,2\pi]`. + drawn from uniform distribution on the interval :math:`[0,2\pi]`. The temporal smoothing is from the SSD term: .. math:: @@ -21,7 +22,7 @@ class FM_SSD_Profile(SpeckleProfile): \phi_{SSD,ml}(t)&=\delta_{x} \sin\left(\omega_{x} t + 2\pi\frac{mN_{cc,x}}{N_{bx}}\right)\\ &+\delta_{y} \sin\left(\omega_{y} t + 2\pi\frac{lN_{cc,y}}{N_{by}}\right). \end{aligned} - + The modulation frequencies :math:`\omega_x,\omega_y` are determined by the laser bandwidth and modulation amplitudes according to the relation @@ -30,9 +31,9 @@ class FM_SSD_Profile(SpeckleProfile): \omega_x = \frac{\Delta_\nu r_x }{2\delta_x}, \omega_y = \frac{\Delta_\nu r_y }{2\delta_y}, - where :math:`\Delta_\nu` is the relative bandwidth of the laser pulse - and :math:`r_x, r_y` are additional rotation factors supplied by the user - in the `transverse_bandwidth_distribution` parameter that determine + where :math:`\Delta_\nu` is the relative bandwidth of the laser pulse + and :math:`r_x, r_y` are additional rotation factors supplied by the user + in the `transverse_bandwidth_distribution` parameter that determine how much of the modulation is in x and how much is in y. [Michel, Eqn. 9.69] Parameters @@ -51,7 +52,10 @@ class FM_SSD_Profile(SpeckleProfile): Determines how much SSD is distributed in the :math:`x` and :math:`y` directions. if `transverse_bandwidth_distribution=[a,b]`, then the SSD frequency modulation is :math:`r_x=a/\sqrt{a^2+b^2}` in :math:`x` and :math:`r_y=b/\sqrt{a^2+b^2}` in :math:`y`. """ - def __init__(self, *speckle_args, + + def __init__( + self, + *speckle_args, relative_laser_bandwidth, phase_modulation_amplitude, number_color_cycles, @@ -64,9 +68,7 @@ def __init__(self, *speckle_args, # number of color cycles self.number_color_cycles = number_color_cycles # bandwidth distributed with respect to the two transverse direction - self.transverse_bandwidth_distribution = ( - transverse_bandwidth_distribution - ) + self.transverse_bandwidth_distribution = transverse_bandwidth_distribution normalization = np.sqrt( self.transverse_bandwidth_distribution[0] ** 2 + self.transverse_bandwidth_distribution[1] ** 2 @@ -81,14 +83,12 @@ def __init__(self, *speckle_args, ] self.time_delay = ( ( - self.number_color_cycles[0] - / self.phase_modulation_frequency[0] + self.number_color_cycles[0] / self.phase_modulation_frequency[0] if self.phase_modulation_frequency[0] > 0 else 0 ), ( - self.number_color_cycles[1] - / self.phase_modulation_frequency[1] + self.number_color_cycles[1] / self.phase_modulation_frequency[1] if self.phase_modulation_frequency[1] > 0 else 0 ), @@ -97,9 +97,10 @@ def __init__(self, *speckle_args, self.phase_plate = np.random.uniform( -np.pi, np.pi, size=self.n_beamlets[0] * self.n_beamlets[1] ).reshape(self.n_beamlets) - + def beamlets_complex_amplitude( - self, t_now, + self, + t_now, ): """Calculate complex amplitude of the beamlets in the near-field, before propagating to the focal plane. @@ -113,16 +114,15 @@ def beamlets_complex_amplitude( phase_t = self.phase_modulation_amplitude[0] * np.sin( self.x_y_dephasing[0] - + 2 * np.pi * self.phase_modulation_frequency[0] - * ( - t_now - self.X_lens_matrix * self.time_delay[0] / self.n_beamlets[0] - ) + + 2 + * np.pi + * self.phase_modulation_frequency[0] + * (t_now - self.X_lens_matrix * self.time_delay[0] / self.n_beamlets[0]) ) + self.phase_modulation_amplitude[1] * np.sin( self.x_y_dephasing[1] - + 2 * np.pi * self.phase_modulation_frequency[1] - * ( - t_now - self.Y_lens_matrix * self.time_delay[1] / self.n_beamlets[1] - ) + + 2 + * np.pi + * self.phase_modulation_frequency[1] + * (t_now - self.Y_lens_matrix * self.time_delay[1] / self.n_beamlets[1]) ) return np.exp(1j * (self.phase_plate + phase_t)) - \ No newline at end of file diff --git a/lasy/profiles/speckled/gp_isi.py b/lasy/profiles/speckled/gp_isi.py index 08296aa87..2ccc60e63 100644 --- a/lasy/profiles/speckled/gp_isi.py +++ b/lasy/profiles/speckled/gp_isi.py @@ -2,13 +2,14 @@ from .speckle_profile import SpeckleProfile from .stochastic_process_utilities import gen_gaussian_time_series + class GP_ISI_Profile(SpeckleProfile): r"""Generate a speckled laser profile with smoothing inspired by Induced Spatial Incoherence (ISI). This is a smoothing technique with temporal stochastic variation in the beamlet phases and amplitudes - to simulate the random phase differences and amplitudes the beamlets pick up when passing through ISI echelons. + to simulate the random phase differences and amplitudes the beamlets pick up when passing through ISI echelons. In this case, :math:`\phi_{ml}(t)` and :math:`A_{ml}(t)` are chosen randomly. - Practically, this is done by drawing the complex amplitudes :math:\tilde A_{ml}(t)` + Practically, this is done by drawing the complex amplitudes :math:\tilde A_{ml}(t)` from a stochastic process with Guassian power spectral density having mean of 1 and FWHM of twice the laser bandwidth. Parameters @@ -18,14 +19,17 @@ class GP_ISI_Profile(SpeckleProfile): Bandwidth :math:`\Delta_\nu` of the incoming laser pulse, relative to the central frequency. """ - def __init__(self, *speckle_args, + + def __init__( + self, + *speckle_args, relative_laser_bandwidth, ): super().__init__(*speckle_args) self.laser_bandwidth = relative_laser_bandwidth self.dt_update = 1 / self.laser_bandwidth / 50 return - + def init_gaussian_time_series( self, series_time, @@ -76,9 +80,10 @@ def setup_for_evaluation(self, t_norm): self.time_series = self.init_gaussian_time_series(series_time) return - + def beamlets_complex_amplitude( - self, t_now, + self, + t_now, ): """Calculate complex amplitude of the beamlets in the near-field, before propagating to the focal plane. @@ -91,4 +96,3 @@ def beamlets_complex_amplitude( """ return self.time_series[:, :, int(round(t_now / self.dt_update))] - \ No newline at end of file diff --git a/lasy/profiles/speckled/gp_rpm_ssd.py b/lasy/profiles/speckled/gp_rpm_ssd.py index 7f2796570..6d52dda05 100644 --- a/lasy/profiles/speckled/gp_rpm_ssd.py +++ b/lasy/profiles/speckled/gp_rpm_ssd.py @@ -2,20 +2,21 @@ from .speckle_profile import SpeckleProfile from .stochastic_process_utilities import gen_gaussian_time_series + class GP_RPM_SSD_Profile(SpeckleProfile): r"""Generate a speckled laser profile with smoothing by a random phase modulated (RPM) spectral dispersion (SSD). - This provides a version of smoothing by spectral dispersion (SSD) where the phases are randomly modulated. + This provides a version of smoothing by spectral dispersion (SSD) where the phases are randomly modulated. Here the amplitude of the beamlets is always :math:`A_{ml}(t)=1`. There are two contributions to the phase :math:`\phi_{ml}` of each beamlet: - + ..math:: - + \phi_{ml}(t) = \phi_{PP,ml} + \phi_{SSD,ml}. The phase plate part :math:`\phi_{PP,ml}` is the initial phase delay from the randomly sized phase plate sections, - drawn from uniform distribution on the interval :math:`[0,2\pi]`. - The phases :math:`\phi_{SSD,ml}(t)` are drawn from a stochastic process + drawn from uniform distribution on the interval :math:`[0,2\pi]`. + The phases :math:`\phi_{SSD,ml}(t)` are drawn from a stochastic process with Gaussian power spectrum with means :math:`\delta_x,\delta_y` given by the `phase_modulation_amplitude` argument and FWHM given by the modulation frequencies :math:`\omega_x,\omega_y`. The modulation frequencies :math:`\omega_x,\omega_y` are determined by the @@ -26,9 +27,9 @@ class GP_RPM_SSD_Profile(SpeckleProfile): \omega_x = \frac{\Delta_\nu r_x }{2\delta_x}, \omega_y = \frac{\Delta_\nu r_y }{2\delta_y}, - where :math:`\Delta_\nu` is the resulting relative bandwidth of the laser pulse - and :math:`r_x, r_y` are additional rotation factors supplied by the user - in the `transverse_bandwidth_distribution` parameter that determine + where :math:`\Delta_\nu` is the resulting relative bandwidth of the laser pulse + and :math:`r_x, r_y` are additional rotation factors supplied by the user + in the `transverse_bandwidth_distribution` parameter that determine how much of the modulation is in x and how much is in y. [Michel, Eqn. 9.69] Parameters @@ -51,7 +52,10 @@ class GP_RPM_SSD_Profile(SpeckleProfile): if `transverse_bandwidth_distribution=[a,b]`, then the SSD frequency modulation is :math:`a/\sqrt{a^2+b^2}` in :math:`x` and :math:`b/\sqrt{a^2+b^2}` in :math:`y`. Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. """ - def __init__(self, *speckle_args, + + def __init__( + self, + *speckle_args, relative_laser_bandwidth, phase_modulation_amplitude, number_color_cycles, @@ -64,9 +68,7 @@ def __init__(self, *speckle_args, # number of color cycles self.number_color_cycles = number_color_cycles # bandwidth distributed with respect to the two transverse direction - self.transverse_bandwidth_distribution = ( - transverse_bandwidth_distribution - ) + self.transverse_bandwidth_distribution = transverse_bandwidth_distribution normalization = np.sqrt( self.transverse_bandwidth_distribution[0] ** 2 + self.transverse_bandwidth_distribution[1] ** 2 @@ -81,21 +83,19 @@ def __init__(self, *speckle_args, ] self.time_delay = ( ( - self.number_color_cycles[0] - / self.phase_modulation_frequency[0] + self.number_color_cycles[0] / self.phase_modulation_frequency[0] if self.phase_modulation_frequency[0] > 0 else 0 ), ( - self.number_color_cycles[1] - / self.phase_modulation_frequency[1] + self.number_color_cycles[1] / self.phase_modulation_frequency[1] if self.phase_modulation_frequency[1] > 0 else 0 ), ) self.dt_update = 1 / self.laser_bandwidth / 50 return - + def init_gaussian_time_series( self, series_time, @@ -156,9 +156,10 @@ def setup_for_evaluation(self, t_norm): self.series_time, self.time_series = self.init_gaussian_time_series(series_time) return - + def beamlets_complex_amplitude( - self, t_now, + self, + t_now, ): """Calculate complex amplitude of the beamlets in the near-field, before propagating to the focal plane. @@ -179,4 +180,3 @@ def beamlets_complex_amplitude( self.time_series[1], ) return np.exp(1j * (self.phase_plate + phase_t)) - \ No newline at end of file diff --git a/lasy/profiles/speckled/rpp_cpp.py b/lasy/profiles/speckled/rpp_cpp.py index 1ea814be9..058c1a957 100644 --- a/lasy/profiles/speckled/rpp_cpp.py +++ b/lasy/profiles/speckled/rpp_cpp.py @@ -1,11 +1,12 @@ import numpy as np from .speckle_profile import SpeckleProfile + class PhasePlateProfile(SpeckleProfile): """Generate a speckled laser profile with a random phase plate. This has no temporal smoothing. - The amplitude of the beamlets is always :math:`A_{ml}(t)=1` and + The amplitude of the beamlets is always :math:`A_{ml}(t)=1` and the relative phases of the beamlets, resulting from the randomly sized phase plate sections, are assigned randomly. If the user specifies Random Phase Plate (RPP: `rpp`), the beamlet phases are drawn with equal probabilities from the set :math:`{0,2\pi}`. @@ -15,12 +16,14 @@ class PhasePlateProfile(SpeckleProfile): ---------- rpp_cpp: string, keyword only, can be 'rpp' or 'cpp', whether to assign beamlet phases according to RPP or CPP scheme """ + def __init__(self, *speckle_args, rpp_cpp): super().__init__(*speckle_args) self.rpp_cpp = rpp_cpp def beamlets_complex_amplitude( - self, t_now, + self, + t_now, ): """Calculate complex amplitude of the beamlets in the near-field, before propagating to the focal plane. @@ -39,4 +42,3 @@ def beamlets_complex_amplitude( ).reshape(self.n_beamlets) exp_phase_plate = np.exp(1j * phase_plate) return exp_phase_plate - \ No newline at end of file diff --git a/lasy/profiles/speckled/speckle_profile.py b/lasy/profiles/speckled/speckle_profile.py index 255800929..991540921 100644 --- a/lasy/profiles/speckled/speckle_profile.py +++ b/lasy/profiles/speckled/speckle_profile.py @@ -2,6 +2,7 @@ from scipy.constants import c from ..profile import Profile + class SpeckleProfile(Profile): r""" Derived class for the profile of a speckled laser pulse. @@ -114,7 +115,8 @@ def __init__( ) def beamlets_complex_amplitude( - self, t_now, + self, + t_now, ): """Calculate complex amplitude of the beamlets in the near-field, before propagating to the focal plane. This function can be overwritten to define custom speckled laser objects. @@ -128,7 +130,7 @@ def beamlets_complex_amplitude( array of complex numbers giving beamlet amplitude and phases in the near-field """ return np.ones_like(self.X_lens_matrix) - + def setup_for_evaluation(self, t_norm): val = 20 print("Im setting up for evaluation!!") @@ -160,10 +162,20 @@ def generate_speckle_pattern(self, t_now, x, y): x_focus_list = X_focus_matrix[:, 0] y_focus_list = Y_focus_matrix[0, :] x_phase_focus_matrix = np.exp( - -2 * np.pi * 1j / self.n_beamlets[0] * self.x_lens_list[:, np.newaxis] * x_focus_list[np.newaxis, :] + -2 + * np.pi + * 1j + / self.n_beamlets[0] + * self.x_lens_list[:, np.newaxis] + * x_focus_list[np.newaxis, :] ) y_phase_focus_matrix = np.exp( - -2 * np.pi * 1j / self.n_beamlets[1] * self.y_lens_list[:, np.newaxis] * y_focus_list[np.newaxis, :] + -2 + * np.pi + * 1j + / self.n_beamlets[1] + * self.y_lens_list[:, np.newaxis] + * y_focus_list[np.newaxis, :] ) bca = self.beamlets_complex_amplitude(t_now) diff --git a/lasy/profiles/speckled/stochastic_process_utilities.py b/lasy/profiles/speckled/stochastic_process_utilities.py index b99f32377..938909872 100644 --- a/lasy/profiles/speckled/stochastic_process_utilities.py +++ b/lasy/profiles/speckled/stochastic_process_utilities.py @@ -1,5 +1,6 @@ import numpy as np + def gen_gaussian_time_series(t_num, dt, fwhm, rms_mean): """Generate a discrete time series that has gaussian power spectrum. diff --git a/tests/test_laser_profiles.py b/tests/test_laser_profiles.py index 678ec3ecc..37d2c3c63 100644 --- a/tests/test_laser_profiles.py +++ b/tests/test_laser_profiles.py @@ -315,7 +315,7 @@ def test_speckle_profile(): focal_length = 3.5 # m beam_aperture = [0.35, 0.5] # m n_beamlets = [24, 32] - do_sinc_profile=False + do_sinc_profile = False relative_laser_bandwidth = 0.005 profile = GP_ISI_Profile( diff --git a/tests/test_speckles.py b/tests/test_speckles.py index 88f395cf1..aa882c3a3 100644 --- a/tests/test_speckles.py +++ b/tests/test_speckles.py @@ -26,22 +26,30 @@ def test_intensity_distribution(temporal_smoothing_type): focal_length = 3.5 # m beam_aperture = [0.35, 0.5] # m n_beamlets = [24, 32] - do_sinc_profile=False - speckle_args = (wavelength, polarization, laser_energy, focal_length, beam_aperture, n_beamlets,do_sinc_profile) + do_sinc_profile = False + speckle_args = ( + wavelength, + polarization, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, + do_sinc_profile, + ) relative_laser_bandwidth = 0.005 phase_modulation_amplitude = (4.1, 4.5) number_color_cycles = [1.4, 1.0] transverse_bandwidth_distribution = [1.8, 1.0] ssd_args = { - "relative_laser_bandwidth":relative_laser_bandwidth, - "phase_modulation_amplitude":phase_modulation_amplitude, - "number_color_cycles":number_color_cycles, - "transverse_bandwidth_distribution":transverse_bandwidth_distribution, + "relative_laser_bandwidth": relative_laser_bandwidth, + "phase_modulation_amplitude": phase_modulation_amplitude, + "number_color_cycles": number_color_cycles, + "transverse_bandwidth_distribution": transverse_bandwidth_distribution, } if temporal_smoothing_type.upper() in ["RPP", "CPP"]: - profile = PhasePlateProfile(*speckle_args,rpp_cpp=temporal_smoothing_type) + profile = PhasePlateProfile(*speckle_args, rpp_cpp=temporal_smoothing_type) elif temporal_smoothing_type.upper() == "FM SSD": profile = FM_SSD_Profile( *speckle_args, @@ -58,7 +66,7 @@ def test_intensity_distribution(temporal_smoothing_type): relative_laser_bandwidth=relative_laser_bandwidth, ) else: - print('invalid smoothing type provided') + print("invalid smoothing type provided") dimensions = "xyt" dx = wavelength * focal_length / beam_aperture[0] dy = wavelength * focal_length / beam_aperture[1] @@ -110,22 +118,30 @@ def test_spatial_correlation(temporal_smoothing_type): focal_length = 3.5 # m beam_aperture = [0.35, 0.35] # m n_beamlets = [24, 32] - do_sinc_profile=False - speckle_args = (wavelength, polarization, laser_energy, focal_length, beam_aperture, n_beamlets,do_sinc_profile) + do_sinc_profile = False + speckle_args = ( + wavelength, + polarization, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, + do_sinc_profile, + ) relative_laser_bandwidth = 0.005 phase_modulation_amplitude = (4.1, 4.1) number_color_cycles = [1.4, 1.0] transverse_bandwidth_distribution = [1.0, 1.0] ssd_args = { - "relative_laser_bandwidth":relative_laser_bandwidth, - "phase_modulation_amplitude":phase_modulation_amplitude, - "number_color_cycles":number_color_cycles, - "transverse_bandwidth_distribution":transverse_bandwidth_distribution, + "relative_laser_bandwidth": relative_laser_bandwidth, + "phase_modulation_amplitude": phase_modulation_amplitude, + "number_color_cycles": number_color_cycles, + "transverse_bandwidth_distribution": transverse_bandwidth_distribution, } if temporal_smoothing_type.upper() in ["RPP", "CPP"]: - profile = PhasePlateProfile(*speckle_args,rpp_cpp=temporal_smoothing_type) + profile = PhasePlateProfile(*speckle_args, rpp_cpp=temporal_smoothing_type) elif temporal_smoothing_type.upper() == "FM SSD": profile = FM_SSD_Profile( *speckle_args, @@ -142,7 +158,7 @@ def test_spatial_correlation(temporal_smoothing_type): relative_laser_bandwidth=relative_laser_bandwidth, ) else: - print('invalid smoothing type provided') + print("invalid smoothing type provided") dimensions = "xyt" dx = wavelength * focal_length / beam_aperture[0] dy = wavelength * focal_length / beam_aperture[1] @@ -207,22 +223,30 @@ def test_sinc_zeros(temporal_smoothing_type): focal_length = 3.5 # m beam_aperture = [0.35, 0.35] # m n_beamlets = [24, 48] - do_sinc_profile=True - speckle_args = (wavelength, polarization, laser_energy, focal_length, beam_aperture, n_beamlets,do_sinc_profile) + do_sinc_profile = True + speckle_args = ( + wavelength, + polarization, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, + do_sinc_profile, + ) relative_laser_bandwidth = 0.005 phase_modulation_amplitude = (4.1, 4.1) number_color_cycles = [1.4, 1.0] transverse_bandwidth_distribution = [1.0, 1.0] ssd_args = { - "relative_laser_bandwidth":relative_laser_bandwidth, - "phase_modulation_amplitude":phase_modulation_amplitude, - "number_color_cycles":number_color_cycles, - "transverse_bandwidth_distribution":transverse_bandwidth_distribution, + "relative_laser_bandwidth": relative_laser_bandwidth, + "phase_modulation_amplitude": phase_modulation_amplitude, + "number_color_cycles": number_color_cycles, + "transverse_bandwidth_distribution": transverse_bandwidth_distribution, } if temporal_smoothing_type.upper() in ["RPP", "CPP"]: - profile = PhasePlateProfile(*speckle_args,rpp_cpp=temporal_smoothing_type) + profile = PhasePlateProfile(*speckle_args, rpp_cpp=temporal_smoothing_type) elif temporal_smoothing_type.upper() == "FM SSD": profile = FM_SSD_Profile( *speckle_args, @@ -239,7 +263,7 @@ def test_sinc_zeros(temporal_smoothing_type): relative_laser_bandwidth=relative_laser_bandwidth, ) else: - print('invalid smoothing type provided') + print("invalid smoothing type provided") dimensions = "xyt" dx = wavelength * focal_length / beam_aperture[0] dy = wavelength * focal_length / beam_aperture[1] @@ -269,9 +293,17 @@ def test_FM_periodicity(): focal_length = 3.5 # m beam_aperture = [0.35, 0.35] # m n_beamlets = [24, 32] - do_sinc_profile=False - speckle_args = (wavelength, polarization, laser_energy, focal_length, beam_aperture, n_beamlets,do_sinc_profile) - + do_sinc_profile = False + speckle_args = ( + wavelength, + polarization, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, + do_sinc_profile, + ) + relative_laser_bandwidth = 0.005 phase_modulation_amplitude = [4.1, 4.1] number_color_cycles = [1.4, 1.0] From 07eb4ead5d3d5f9d9010599542e03d6ab9768c44 Mon Sep 17 00:00:00 2001 From: RTSandberg Date: Thu, 18 Apr 2024 17:39:54 -0700 Subject: [PATCH 04/18] Fix review-suggested changes --- lasy/profiles/speckled/fm_ssd.py | 3 +- lasy/profiles/speckled/gp_isi.py | 5 +- lasy/profiles/speckled/gp_rpm_ssd.py | 4 +- lasy/profiles/speckled/rpp_cpp.py | 3 +- lasy/profiles/speckled/speckle_profile.py | 7 +- tests/old_test_speckles.py | 273 ---------------------- 6 files changed, 13 insertions(+), 282 deletions(-) delete mode 100644 tests/old_test_speckles.py diff --git a/lasy/profiles/speckled/fm_ssd.py b/lasy/profiles/speckled/fm_ssd.py index d1bcdf187..185b5033f 100644 --- a/lasy/profiles/speckled/fm_ssd.py +++ b/lasy/profiles/speckled/fm_ssd.py @@ -38,7 +38,6 @@ class FM_SSD_Profile(SpeckleProfile): Parameters ---------- - relative_laser_bandwidth : float Resulting bandwidth :math:`\Delta_\nu` of the laser pulse, relative to central frequency, due to the frequency modulation. @@ -106,12 +105,12 @@ def beamlets_complex_amplitude( Parameters ---------- + t_now: float, time at which to evaluate complex amplitude Returns ------- array of complex numbers giving beamlet amplitude and phases in the near-field """ - phase_t = self.phase_modulation_amplitude[0] * np.sin( self.x_y_dephasing[0] + 2 diff --git a/lasy/profiles/speckled/gp_isi.py b/lasy/profiles/speckled/gp_isi.py index 2ccc60e63..22a26cb88 100644 --- a/lasy/profiles/speckled/gp_isi.py +++ b/lasy/profiles/speckled/gp_isi.py @@ -14,7 +14,6 @@ class GP_ISI_Profile(SpeckleProfile): Parameters ---------- - relative_laser_bandwidth : float Bandwidth :math:`\Delta_\nu` of the incoming laser pulse, relative to the central frequency. @@ -70,6 +69,8 @@ def init_gaussian_time_series( return complex_amp def setup_for_evaluation(self, t_norm): + """Create or update data used in evaluation. + """ self.x_y_dephasing = np.random.standard_normal(2) * np.pi self.phase_plate = np.random.uniform( -np.pi, np.pi, size=self.n_beamlets[0] * self.n_beamlets[1] @@ -89,10 +90,10 @@ def beamlets_complex_amplitude( Parameters ---------- + t_now: float, time at which to evaluate complex amplitude Returns ------- array of complex numbers giving beamlet amplitude and phases in the near-field """ - return self.time_series[:, :, int(round(t_now / self.dt_update))] diff --git a/lasy/profiles/speckled/gp_rpm_ssd.py b/lasy/profiles/speckled/gp_rpm_ssd.py index 6d52dda05..16e2dd5c0 100644 --- a/lasy/profiles/speckled/gp_rpm_ssd.py +++ b/lasy/profiles/speckled/gp_rpm_ssd.py @@ -34,7 +34,6 @@ class GP_RPM_SSD_Profile(SpeckleProfile): Parameters ---------- - relative_laser_bandwidth : float Bandwidth :math:`\Delta_\nu` of the laser pulse, relative to central frequency. Only used if ``temporal_smoothing_type`` is ``'FM SSD'``, ``'GP RPM SSD'`` or ``'GP ISI'``. @@ -146,6 +145,8 @@ def init_gaussian_time_series( ) def setup_for_evaluation(self, t_norm): + """Create or update data used in evaluation. + """ self.x_y_dephasing = np.random.standard_normal(2) * np.pi self.phase_plate = np.random.uniform( -np.pi, np.pi, size=self.n_beamlets[0] * self.n_beamlets[1] @@ -165,6 +166,7 @@ def beamlets_complex_amplitude( Parameters ---------- + t_now: float, time at which to evaluate complex amplitude Returns ------- diff --git a/lasy/profiles/speckled/rpp_cpp.py b/lasy/profiles/speckled/rpp_cpp.py index 058c1a957..921274082 100644 --- a/lasy/profiles/speckled/rpp_cpp.py +++ b/lasy/profiles/speckled/rpp_cpp.py @@ -3,7 +3,7 @@ class PhasePlateProfile(SpeckleProfile): - """Generate a speckled laser profile with a random phase plate. + r"""Generate a speckled laser profile with a random phase plate. This has no temporal smoothing. The amplitude of the beamlets is always :math:`A_{ml}(t)=1` and @@ -29,6 +29,7 @@ def beamlets_complex_amplitude( Parameters ---------- + t_now: float, time at which to evaluate complex amplitude Returns ------- diff --git a/lasy/profiles/speckled/speckle_profile.py b/lasy/profiles/speckled/speckle_profile.py index 991540921..9d890b9a7 100644 --- a/lasy/profiles/speckled/speckle_profile.py +++ b/lasy/profiles/speckled/speckle_profile.py @@ -119,6 +119,7 @@ def beamlets_complex_amplitude( t_now, ): """Calculate complex amplitude of the beamlets in the near-field, before propagating to the focal plane. + This function can be overwritten to define custom speckled laser objects. Parameters @@ -132,9 +133,9 @@ def beamlets_complex_amplitude( return np.ones_like(self.X_lens_matrix) def setup_for_evaluation(self, t_norm): - val = 20 - print("Im setting up for evaluation!!") - return val + 5 + """Create or update data used in evaluation. + """ + pass def generate_speckle_pattern(self, t_now, x, y): """Calculate the speckle pattern in the focal plane. diff --git a/tests/old_test_speckles.py b/tests/old_test_speckles.py deleted file mode 100644 index 80df1b389..000000000 --- a/tests/old_test_speckles.py +++ /dev/null @@ -1,273 +0,0 @@ -import numpy as np - -from lasy.laser import Laser -from lasy.profiles.speckled.speckle_profile import SpeckleProfile -import pytest -from scipy.constants import c - - -@pytest.mark.parametrize( - "temporal_smoothing_type", ["RPP", "CPP", "FM SSD", "GP RPM SSD", "GP ISI"] -) -def test_intensity_distribution(temporal_smoothing_type): - """Test whether the spatial intensity distribution and statisticis are correct. - - The distribution should be exponential, 1/ exp(-I/) [Michel, 9.35]. - The real and imaginary parts of the envelope [Michel, Eqn. 9.26] and their product [9.30] should all be 0 on average. - """ - - wavelength = 0.351e-6 # Laser wavelength in meters - polarization = (1, 0) # Linearly polarized in the x direction - focal_length = 3.5 # m - beam_aperture = [0.35, 0.5] # m - n_beamlets = [24, 32] - relative_laser_bandwidth = 0.005 - laser_energy = 1.0 # J (this is the laser energy stored in the box defined by `lo` and `hi` below) - - ssd_phase_modulation_amplitude = (4.1, 4.5) - ssd_number_color_cycles = [1.4, 1.0] - ssd_transverse_bandwidth_distribution = [1.8, 1.0] - - profile = SpeckleProfile( - wavelength, - polarization, - laser_energy, - focal_length, - beam_aperture, - n_beamlets, - temporal_smoothing_type=temporal_smoothing_type, - relative_laser_bandwidth=relative_laser_bandwidth, - ssd_phase_modulation_amplitude=ssd_phase_modulation_amplitude, - ssd_number_color_cycles=ssd_number_color_cycles, - ssd_transverse_bandwidth_distribution=ssd_transverse_bandwidth_distribution, - ) - dimensions = "xyt" - dx = wavelength * focal_length / beam_aperture[0] - dy = wavelength * focal_length / beam_aperture[1] - Lx = 1.8 * dx * n_beamlets[0] - Ly = 3.1 * dy * n_beamlets[1] - nu_laser = c / wavelength - t_max = 50 / nu_laser - lo = (0, 0, 0) - hi = (Lx, Ly, t_max) - num_points = (200, 250, 2) - - laser = Laser(dimensions, lo, hi, num_points, profile) - - F = laser.grid.field - - # get spatial statistics - # = 0 = = - e_r = np.real(F) - e_i = np.imag(F) - er_ei = e_r * e_i - assert np.max(abs(e_r.mean(axis=(0, 1)) / e_r.std(axis=(0, 1)))) < 1.0e-1 - assert np.max(abs(e_i.mean(axis=(0, 1)) / e_i.std(axis=(0, 1)))) < 1.0e-1 - assert np.max(abs(er_ei.mean(axis=(0, 1)) / er_ei.std(axis=(0, 1)))) < 1.0e-1 - - # # compare intensity distribution with expected 1/ exp(-I/) - env_I = abs(F) ** 2 - I_vec = env_I.flatten() - mean_I = I_vec.mean() - N_hist = 200 - counts_np, bins_np = np.histogram(I_vec, bins=N_hist, density=True) - I_dist = 1.0 / mean_I * np.exp(-bins_np / mean_I) - error_I_dist = np.max(abs(counts_np - I_dist[:-1])) - assert error_I_dist < 2.0e-4 - - -@pytest.mark.parametrize( - "temporal_smoothing_type", ["RPP", "CPP", "FM SSD", "GP RPM SSD", "GP ISI"] -) -def test_spatial_correlation(temporal_smoothing_type): - """Tests whether the speckles have the correct shape. - - The speckle shape is measured over one period, since the spatial profile is periodic. - The correct speckle shape for a rectangular laser, - determined by the autocorrelation, is the product of sinc functions [Michel, Eqn. 9.16]. - """ - wavelength = 0.351e-6 # Laser wavelength in meters - polarization = (1, 0) # Linearly polarized in the x direction - laser_energy = 1.0 # J (this is the laser energy stored in the box defined by `lo` and `hi` below) - focal_length = 3.5 # m - beam_aperture = [0.35, 0.35] # m - n_beamlets = [24, 32] - relative_laser_bandwidth = 0.005 - - ssd_phase_modulation_amplitude = (4.1, 4.1) - ssd_number_color_cycles = [1.4, 1.0] - ssd_transverse_bandwidth_distribution = [1.0, 1.0] - - profile = SpeckleProfile( - wavelength, - polarization, - laser_energy, - focal_length, - beam_aperture, - n_beamlets, - temporal_smoothing_type=temporal_smoothing_type, - relative_laser_bandwidth=relative_laser_bandwidth, # 0.005 - ssd_phase_modulation_amplitude=ssd_phase_modulation_amplitude, - ssd_number_color_cycles=ssd_number_color_cycles, - ssd_transverse_bandwidth_distribution=ssd_transverse_bandwidth_distribution, - ) - dimensions = "xyt" - dx = wavelength * focal_length / beam_aperture[0] - dy = wavelength * focal_length / beam_aperture[1] - Lx = dx * n_beamlets[0] - Ly = dy * n_beamlets[1] - nu_laser = c / wavelength - tu = 1 / relative_laser_bandwidth / 50 / nu_laser - t_max = 200 * tu - lo = (0, 0, 0) - hi = (Lx, Ly, t_max) - num_points = (200, 200, 300) - - laser = Laser(dimensions, lo, hi, num_points, profile) - F = laser.grid.field - - # compare speckle profile / autocorrelation - # compute autocorrelation using Wiener-Khinchin Theorem - - fft_abs_all = abs(np.fft.fft2(F, axes=(0, 1))) ** 2 - ifft_abs = abs(np.fft.ifft2(fft_abs_all, axes=(0, 1))) ** 2 - acorr2_3d = np.fft.fftshift(ifft_abs, axes=(0, 1)) - acorr2_3d_norm = acorr2_3d / np.max(acorr2_3d, axis=(0, 1)) - - # compare with theoretical speckle profile - x_list = np.linspace( - -n_beamlets[0] / 2 + 0.5, n_beamlets[0] / 2 - 0.5, num_points[0], endpoint=False - ) - y_list = np.linspace( - -n_beamlets[1] / 2 + 0.5, n_beamlets[1] / 2 - 0.5, num_points[1], endpoint=False - ) - X, Y = np.meshgrid(x_list, y_list) - acorr_theor = np.sinc(X) ** 2 * np.sinc(Y) ** 2 - error_auto_correlation = np.max(abs(acorr_theor[:, :, np.newaxis] - acorr2_3d_norm)) - - assert error_auto_correlation < 5.0e-1 - - -@pytest.mark.parametrize( - "temporal_smoothing_type", ["RPP", "CPP", "FM SSD", "GP RPM SSD", "GP ISI"] -) -def test_sinc_zeros(temporal_smoothing_type): - """Test whether the transverse sinc envelope has the correct width - - The transverse envelope for the rectangular laser has the form - - ..math:: - - {\rm sinc}\left(\frac{\pi x}{\Delta x}\right) - {\rm sinc}\left(\frac{\pi y}{\Delta y}\right) - - [Michel, Eqns. 9.11, 87, 94]. - This has widths - - ..math:: - - \Delta x=\lambda_0fN_{bx}/D_x, - \Delta y=\lambda_0fN_{by}/D_y - """ - wavelength = 0.351e-6 # Laser wavelength in meters - polarization = (1, 0) # Linearly polarized in the x direction - laser_energy = 1.0 # J (this is the laser energy stored in the box defined by `lo` and `hi` below) - focal_length = 3.5 # m - beam_aperture = [0.35, 0.35] # m - n_beamlets = [24, 48] - relative_laser_bandwidth = 0.005 - ssd_phase_modulation_amplitude = (4.1, 4.1) - ssd_number_color_cycles = [1.4, 1.0] - ssd_transverse_bandwidth_distribution = [1.0, 1.0] - - profile = SpeckleProfile( - wavelength, - polarization, - laser_energy, - focal_length, - beam_aperture, - n_beamlets, - temporal_smoothing_type=temporal_smoothing_type, - relative_laser_bandwidth=relative_laser_bandwidth, - ssd_phase_modulation_amplitude=ssd_phase_modulation_amplitude, - ssd_number_color_cycles=ssd_number_color_cycles, - ssd_transverse_bandwidth_distribution=ssd_transverse_bandwidth_distribution, - do_include_transverse_envelope=True, - ) - dimensions = "xyt" - dx = wavelength * focal_length / beam_aperture[0] - dy = wavelength * focal_length / beam_aperture[1] - Lx = dx * n_beamlets[0] - Ly = dy * n_beamlets[1] - nu_laser = c / wavelength - tu = 1 / relative_laser_bandwidth / 50 / nu_laser - t_max = 200 * tu - lo = (-Lx, -Ly, 0) - hi = (Lx, Ly, t_max) - num_points = (300, 300, 10) - - laser = Laser(dimensions, lo, hi, num_points, profile) - F = laser.grid.field - - assert abs(F[0, :, :]).max() / abs(F).max() < 1.0e-8 - assert abs(F[-1, :, :]).max() / abs(F).max() < 1.0e-8 - assert abs(F[:, 0, :]).max() / abs(F).max() < 1.0e-8 - assert abs(F[:, -1, :]).max() / abs(F).max() < 1.0e-8 - - -def test_FM_SSD_periodicity(): - """Test that the frequency modulated Smoothing by spectral dispersion (SSD) has the correct temporal frequency.""" - wavelength = 0.351e-6 # Laser wavelength in meters - polarization = (1, 0) # Linearly polarized in the x direction - laser_energy = 1.0 # J (this is the laser energy stored in the box defined by `lo` and `hi` below) - focal_length = 3.5 # m - beam_aperture = [0.35, 0.35] # m - n_beamlets = [24, 32] - temporal_smoothing_type = "FM SSD" - relative_laser_bandwidth = 0.005 - - ssd_phase_modulation_amplitude = [4.1, 4.1] - ssd_number_color_cycles = [1.4, 1.0] - ssd_transverse_bandwidth_distribution = [1.0, 1.0] - - laser_profile = SpeckleProfile( - wavelength, - polarization, - laser_energy, - focal_length, - beam_aperture, - n_beamlets, - temporal_smoothing_type=temporal_smoothing_type, - relative_laser_bandwidth=relative_laser_bandwidth, - ssd_phase_modulation_amplitude=ssd_phase_modulation_amplitude, - ssd_number_color_cycles=ssd_number_color_cycles, - ssd_transverse_bandwidth_distribution=ssd_transverse_bandwidth_distribution, - ) - nu_laser = c / wavelength - ssd_frac = np.sqrt( - ssd_transverse_bandwidth_distribution[0] ** 2 - + ssd_transverse_bandwidth_distribution[1] ** 2 - ) - ssd_frac = ( - ssd_transverse_bandwidth_distribution[0] / ssd_frac, - ssd_transverse_bandwidth_distribution[1] / ssd_frac, - ) - phase_mod_freq = [ - relative_laser_bandwidth * sf * 0.5 / pma - for sf, pma in zip(ssd_frac, ssd_phase_modulation_amplitude) - ] - t_max = 1.0 / phase_mod_freq[0] / nu_laser - - dimensions = "xyt" - dx = wavelength * focal_length / beam_aperture[0] - dy = wavelength * focal_length / beam_aperture[1] - Lx = dx * n_beamlets[0] - Ly = dy * n_beamlets[1] - lo = (0, 0, 0) # Lower bounds of the simulation box - hi = (Lx, Ly, t_max) # Upper bounds of the simulation box - num_points = (160, 200, 400) # Number of points in each dimension - - laser = Laser(dimensions, lo, hi, num_points, laser_profile) - F = laser.grid.field - period_error = abs(F[:, :, 0] - F[:, :, -1]).max() / abs(F).max() - assert period_error < 1.0e-8 From b579744dcc7f1b41e82348412b723463f18117c4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 00:40:05 +0000 Subject: [PATCH 05/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- lasy/profiles/speckled/gp_isi.py | 3 +-- lasy/profiles/speckled/gp_rpm_ssd.py | 3 +-- lasy/profiles/speckled/speckle_profile.py | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lasy/profiles/speckled/gp_isi.py b/lasy/profiles/speckled/gp_isi.py index 22a26cb88..ce41b4c74 100644 --- a/lasy/profiles/speckled/gp_isi.py +++ b/lasy/profiles/speckled/gp_isi.py @@ -69,8 +69,7 @@ def init_gaussian_time_series( return complex_amp def setup_for_evaluation(self, t_norm): - """Create or update data used in evaluation. - """ + """Create or update data used in evaluation.""" self.x_y_dephasing = np.random.standard_normal(2) * np.pi self.phase_plate = np.random.uniform( -np.pi, np.pi, size=self.n_beamlets[0] * self.n_beamlets[1] diff --git a/lasy/profiles/speckled/gp_rpm_ssd.py b/lasy/profiles/speckled/gp_rpm_ssd.py index 16e2dd5c0..03446f856 100644 --- a/lasy/profiles/speckled/gp_rpm_ssd.py +++ b/lasy/profiles/speckled/gp_rpm_ssd.py @@ -145,8 +145,7 @@ def init_gaussian_time_series( ) def setup_for_evaluation(self, t_norm): - """Create or update data used in evaluation. - """ + """Create or update data used in evaluation.""" self.x_y_dephasing = np.random.standard_normal(2) * np.pi self.phase_plate = np.random.uniform( -np.pi, np.pi, size=self.n_beamlets[0] * self.n_beamlets[1] diff --git a/lasy/profiles/speckled/speckle_profile.py b/lasy/profiles/speckled/speckle_profile.py index 9d890b9a7..fbdb507df 100644 --- a/lasy/profiles/speckled/speckle_profile.py +++ b/lasy/profiles/speckled/speckle_profile.py @@ -133,8 +133,7 @@ def beamlets_complex_amplitude( return np.ones_like(self.X_lens_matrix) def setup_for_evaluation(self, t_norm): - """Create or update data used in evaluation. - """ + """Create or update data used in evaluation.""" pass def generate_speckle_pattern(self, t_now, x, y): From 96254ba96982a6e283ee46cd749e126f5c32a465 Mon Sep 17 00:00:00 2001 From: RTSandberg Date: Thu, 18 Apr 2024 17:48:16 -0700 Subject: [PATCH 06/18] more automated refactoring --- docs/source/api/profiles/speckled/speckled.rst | 2 +- lasy/profiles/__init__.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/source/api/profiles/speckled/speckled.rst b/docs/source/api/profiles/speckled/speckled.rst index 98b4e9a62..ede1ebeb1 100644 --- a/docs/source/api/profiles/speckled/speckled.rst +++ b/docs/source/api/profiles/speckled/speckled.rst @@ -1,7 +1,7 @@ Base Speckled Laser Profile =========================== -The `beamlets_complex_amplitude` function can be modified to create custom speckle profiles. +The ``beamlets_complex_amplitude`` function can be modified to create custom speckle profiles. See the other profiles for examples. .. autoclass:: lasy.profiles.SpeckleProfile diff --git a/lasy/profiles/__init__.py b/lasy/profiles/__init__.py index 9acedbb83..babafc104 100644 --- a/lasy/profiles/__init__.py +++ b/lasy/profiles/__init__.py @@ -2,12 +2,10 @@ from .gaussian_profile import GaussianProfile from .from_array_profile import FromArrayProfile from .from_openpmd_profile import FromOpenPMDProfile -from .speckled.speckle_profile import SpeckleProfile __all__ = [ "CombinedLongitudinalTransverseProfile", "GaussianProfile", "FromArrayProfile", "FromOpenPMDProfile", - # "SpeckleProfile", ] From fb00bc1c180c6fba7ae4c2377284a2925a47491a Mon Sep 17 00:00:00 2001 From: RTSandberg Date: Thu, 18 Apr 2024 17:55:15 -0700 Subject: [PATCH 07/18] make sure docs build --- docs/source/api/profiles/speckled/fm_ssd.rst | 2 +- docs/source/api/profiles/speckled/gp_isi.rst | 2 +- docs/source/api/profiles/speckled/gp_rpm_ssd.rst | 2 +- docs/source/api/profiles/speckled/rpp_cpp.rst | 2 +- docs/source/api/profiles/speckled/speckled.rst | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/api/profiles/speckled/fm_ssd.rst b/docs/source/api/profiles/speckled/fm_ssd.rst index 4ee132189..a690d1fd9 100644 --- a/docs/source/api/profiles/speckled/fm_ssd.rst +++ b/docs/source/api/profiles/speckled/fm_ssd.rst @@ -1,7 +1,7 @@ Frequency-modulated Smoothing by Spectral Dispersion (FM-SSD) Laser Profile =========================================================================== -.. autoclass:: lasy.profiles.SpeckleProfile +.. autoclass:: lasy.profiles.speckled.SpeckleProfile :no-index: :members: diff --git a/docs/source/api/profiles/speckled/gp_isi.rst b/docs/source/api/profiles/speckled/gp_isi.rst index f84a65edb..c77696e89 100644 --- a/docs/source/api/profiles/speckled/gp_isi.rst +++ b/docs/source/api/profiles/speckled/gp_isi.rst @@ -1,7 +1,7 @@ Smoothing by Induced Spatial Incoherence (ISI) Laser Profile ============================================================ -.. autoclass:: lasy.profiles.SpeckleProfile +.. autoclass:: lasy.profiles.speckled.SpeckleProfile :no-index: :members: diff --git a/docs/source/api/profiles/speckled/gp_rpm_ssd.rst b/docs/source/api/profiles/speckled/gp_rpm_ssd.rst index c26ae1cea..79e3438a5 100644 --- a/docs/source/api/profiles/speckled/gp_rpm_ssd.rst +++ b/docs/source/api/profiles/speckled/gp_rpm_ssd.rst @@ -1,7 +1,7 @@ Random Phase Modulated (RPM) Smoothing by Spectral Dispersion (FM-SSD) Laser Profile ==================================================================================== -.. autoclass:: lasy.profiles.SpeckleProfile +.. autoclass:: lasy.profiles.speckled.SpeckleProfile :no-index: :members: diff --git a/docs/source/api/profiles/speckled/rpp_cpp.rst b/docs/source/api/profiles/speckled/rpp_cpp.rst index c85f06e7c..f3668600c 100644 --- a/docs/source/api/profiles/speckled/rpp_cpp.rst +++ b/docs/source/api/profiles/speckled/rpp_cpp.rst @@ -1,7 +1,7 @@ RPP/CPP only Laser Profile ========================== -.. autoclass:: lasy.profiles.SpeckleProfile +.. autoclass:: lasy.profiles.speckled.SpeckleProfile :no-index: :members: diff --git a/docs/source/api/profiles/speckled/speckled.rst b/docs/source/api/profiles/speckled/speckled.rst index ede1ebeb1..028913eaa 100644 --- a/docs/source/api/profiles/speckled/speckled.rst +++ b/docs/source/api/profiles/speckled/speckled.rst @@ -4,5 +4,5 @@ Base Speckled Laser Profile The ``beamlets_complex_amplitude`` function can be modified to create custom speckle profiles. See the other profiles for examples. -.. autoclass:: lasy.profiles.SpeckleProfile +.. autoclass:: lasy.profiles.speckled.SpeckleProfile :members: From 7f399b3c92e4830cea87e9e8b810f50992cd895f Mon Sep 17 00:00:00 2001 From: RTSandberg Date: Fri, 19 Apr 2024 14:47:46 -0700 Subject: [PATCH 08/18] include longitudinal profile --- lasy/profiles/speckled/speckle_profile.py | 30 +++++++++++------------ tests/test_laser_profiles.py | 2 ++ tests/test_speckles.py | 8 ++++++ 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/lasy/profiles/speckled/speckle_profile.py b/lasy/profiles/speckled/speckle_profile.py index fbdb507df..9b1b77341 100644 --- a/lasy/profiles/speckled/speckle_profile.py +++ b/lasy/profiles/speckled/speckle_profile.py @@ -45,6 +45,8 @@ class SpeckleProfile(Profile): Notes ----- This assumes a flat-top rectangular laser and so a rectangular arrangement of beamlets in the near-field. + The longitudinal profile is currently applied to the beamlets + individually in the near-field before they are propagated to the focal plane. Parameters ---------- @@ -73,9 +75,12 @@ class SpeckleProfile(Profile): n_beamlets : list of 2 integers Number of RPP/CPP elements :math:`N_{bx},N_{by}` in each direction, in the near field. - do_include_transverse_envelope : boolean, (optional, default False) + do_include_transverse_envelope : boolean Whether to include the transverse sinc envelope or not. I.e. whether it is assumed to be close enough to the laser axis to neglect the transverse field decay. + + long_profile : Lasy Longitudinal laser object (or None). + If this is not None, the longitudinal profile is applied individually to the beamlets in the near-field. """ def __init__( @@ -87,14 +92,16 @@ def __init__( beam_aperture, n_beamlets, do_include_transverse_envelope, + long_profile, ): super().__init__(wavelength, pol) self.laser_energy = laser_energy self.focal_length = focal_length self.beam_aperture = np.array(beam_aperture, dtype="float") self.n_beamlets = np.array(n_beamlets, dtype="int") - self.do_include_transverse_envelope = do_include_transverse_envelope + self.long_profile = long_profile + self.x_lens_list = np.linspace( -0.5 * (self.n_beamlets[0] - 1), @@ -113,6 +120,7 @@ def __init__( np.arange(self.n_beamlets[1], dtype=float), np.arange(self.n_beamlets[0], dtype=float), ) + return def beamlets_complex_amplitude( self, @@ -162,23 +170,15 @@ def generate_speckle_pattern(self, t_now, x, y): x_focus_list = X_focus_matrix[:, 0] y_focus_list = Y_focus_matrix[0, :] x_phase_focus_matrix = np.exp( - -2 - * np.pi - * 1j - / self.n_beamlets[0] - * self.x_lens_list[:, np.newaxis] - * x_focus_list[np.newaxis, :] + -2 * np.pi * 1j / self.n_beamlets[0] * self.x_lens_list[:, np.newaxis] * x_focus_list[np.newaxis, :] ) y_phase_focus_matrix = np.exp( - -2 - * np.pi - * 1j - / self.n_beamlets[1] - * self.y_lens_list[:, np.newaxis] - * y_focus_list[np.newaxis, :] + -2 * np.pi * 1j / self.n_beamlets[1] * self.y_lens_list[:, np.newaxis] * y_focus_list[np.newaxis, :] ) - bca = self.beamlets_complex_amplitude(t_now) + if self.long_profile is not None: + # have to unnormalize t_now to evaluate in longitudinal profile + bca = bca * self.long_profile.evaluate(t_now / c * self.lambda0) speckle_amp = np.einsum( "jk,jl->kl", np.einsum("ij,ik->jk", bca, x_phase_focus_matrix), diff --git a/tests/test_laser_profiles.py b/tests/test_laser_profiles.py index 37d2c3c63..20f66cfd7 100644 --- a/tests/test_laser_profiles.py +++ b/tests/test_laser_profiles.py @@ -317,6 +317,7 @@ def test_speckle_profile(): n_beamlets = [24, 32] do_sinc_profile = False relative_laser_bandwidth = 0.005 + long_profile = None profile = GP_ISI_Profile( wavelength, @@ -326,6 +327,7 @@ def test_speckle_profile(): beam_aperture, n_beamlets, do_sinc_profile, + long_profile, relative_laser_bandwidth=relative_laser_bandwidth, ) dimensions = "xyt" diff --git a/tests/test_speckles.py b/tests/test_speckles.py index aa882c3a3..ecb9f5477 100644 --- a/tests/test_speckles.py +++ b/tests/test_speckles.py @@ -27,6 +27,7 @@ def test_intensity_distribution(temporal_smoothing_type): beam_aperture = [0.35, 0.5] # m n_beamlets = [24, 32] do_sinc_profile = False + long_profile = None speckle_args = ( wavelength, polarization, @@ -35,6 +36,7 @@ def test_intensity_distribution(temporal_smoothing_type): beam_aperture, n_beamlets, do_sinc_profile, + long_profile, ) relative_laser_bandwidth = 0.005 @@ -119,6 +121,7 @@ def test_spatial_correlation(temporal_smoothing_type): beam_aperture = [0.35, 0.35] # m n_beamlets = [24, 32] do_sinc_profile = False + long_profile = None speckle_args = ( wavelength, polarization, @@ -127,6 +130,7 @@ def test_spatial_correlation(temporal_smoothing_type): beam_aperture, n_beamlets, do_sinc_profile, + long_profile, ) relative_laser_bandwidth = 0.005 @@ -224,6 +228,7 @@ def test_sinc_zeros(temporal_smoothing_type): beam_aperture = [0.35, 0.35] # m n_beamlets = [24, 48] do_sinc_profile = True + long_profile = None speckle_args = ( wavelength, polarization, @@ -232,6 +237,7 @@ def test_sinc_zeros(temporal_smoothing_type): beam_aperture, n_beamlets, do_sinc_profile, + long_profile, ) relative_laser_bandwidth = 0.005 @@ -294,6 +300,7 @@ def test_FM_periodicity(): beam_aperture = [0.35, 0.35] # m n_beamlets = [24, 32] do_sinc_profile = False + long_profile = None speckle_args = ( wavelength, polarization, @@ -302,6 +309,7 @@ def test_FM_periodicity(): beam_aperture, n_beamlets, do_sinc_profile, + long_profile, ) relative_laser_bandwidth = 0.005 From 70898e63e76e561a9049389e5ec7b62ac4d616b0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 21:47:59 +0000 Subject: [PATCH 09/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- lasy/profiles/speckled/speckle_profile.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lasy/profiles/speckled/speckle_profile.py b/lasy/profiles/speckled/speckle_profile.py index 9b1b77341..1469e73ec 100644 --- a/lasy/profiles/speckled/speckle_profile.py +++ b/lasy/profiles/speckled/speckle_profile.py @@ -45,7 +45,7 @@ class SpeckleProfile(Profile): Notes ----- This assumes a flat-top rectangular laser and so a rectangular arrangement of beamlets in the near-field. - The longitudinal profile is currently applied to the beamlets + The longitudinal profile is currently applied to the beamlets individually in the near-field before they are propagated to the focal plane. Parameters @@ -102,7 +102,6 @@ def __init__( self.do_include_transverse_envelope = do_include_transverse_envelope self.long_profile = long_profile - self.x_lens_list = np.linspace( -0.5 * (self.n_beamlets[0] - 1), 0.5 * (self.n_beamlets[0] - 1), @@ -170,14 +169,24 @@ def generate_speckle_pattern(self, t_now, x, y): x_focus_list = X_focus_matrix[:, 0] y_focus_list = Y_focus_matrix[0, :] x_phase_focus_matrix = np.exp( - -2 * np.pi * 1j / self.n_beamlets[0] * self.x_lens_list[:, np.newaxis] * x_focus_list[np.newaxis, :] + -2 + * np.pi + * 1j + / self.n_beamlets[0] + * self.x_lens_list[:, np.newaxis] + * x_focus_list[np.newaxis, :] ) y_phase_focus_matrix = np.exp( - -2 * np.pi * 1j / self.n_beamlets[1] * self.y_lens_list[:, np.newaxis] * y_focus_list[np.newaxis, :] + -2 + * np.pi + * 1j + / self.n_beamlets[1] + * self.y_lens_list[:, np.newaxis] + * y_focus_list[np.newaxis, :] ) bca = self.beamlets_complex_amplitude(t_now) if self.long_profile is not None: - # have to unnormalize t_now to evaluate in longitudinal profile + # have to unnormalize t_now to evaluate in longitudinal profile bca = bca * self.long_profile.evaluate(t_now / c * self.lambda0) speckle_amp = np.einsum( "jk,jl->kl", From 324a94c9b1b3f0b18515fa8db4b0fe008e83cd85 Mon Sep 17 00:00:00 2001 From: RTSandberg Date: Thu, 9 May 2024 11:58:31 -0700 Subject: [PATCH 10/18] include base parameters explicitly in derived classes --- lasy/profiles/speckled/fm_ssd.py | 20 ++- lasy/profiles/speckled/gp_isi.py | 20 ++- lasy/profiles/speckled/gp_rpm_ssd.py | 20 ++- lasy/profiles/speckled/rpp_cpp.py | 26 +++- tests/test_laser_profiles.py | 2 +- tests/test_speckles.py | 184 ++++++++++++++------------- 6 files changed, 171 insertions(+), 101 deletions(-) diff --git a/lasy/profiles/speckled/fm_ssd.py b/lasy/profiles/speckled/fm_ssd.py index 185b5033f..e2a5fc72f 100644 --- a/lasy/profiles/speckled/fm_ssd.py +++ b/lasy/profiles/speckled/fm_ssd.py @@ -54,13 +54,29 @@ class FM_SSD_Profile(SpeckleProfile): def __init__( self, - *speckle_args, + wavelength, + pol, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, relative_laser_bandwidth, phase_modulation_amplitude, number_color_cycles, transverse_bandwidth_distribution, + do_include_transverse_envelope=True, + long_profile=None, ): - super().__init__(*speckle_args) + super().__init__( + wavelength, + pol, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, + do_include_transverse_envelope, + long_profile, + ) self.laser_bandwidth = relative_laser_bandwidth # the amplitude of phase along each direction self.phase_modulation_amplitude = phase_modulation_amplitude diff --git a/lasy/profiles/speckled/gp_isi.py b/lasy/profiles/speckled/gp_isi.py index ce41b4c74..df5889619 100644 --- a/lasy/profiles/speckled/gp_isi.py +++ b/lasy/profiles/speckled/gp_isi.py @@ -21,10 +21,26 @@ class GP_ISI_Profile(SpeckleProfile): def __init__( self, - *speckle_args, + wavelength, + pol, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, relative_laser_bandwidth, + do_include_transverse_envelope=True, + long_profile=None, ): - super().__init__(*speckle_args) + super().__init__( + wavelength, + pol, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, + do_include_transverse_envelope, + long_profile, + ) self.laser_bandwidth = relative_laser_bandwidth self.dt_update = 1 / self.laser_bandwidth / 50 return diff --git a/lasy/profiles/speckled/gp_rpm_ssd.py b/lasy/profiles/speckled/gp_rpm_ssd.py index 03446f856..0f18695fe 100644 --- a/lasy/profiles/speckled/gp_rpm_ssd.py +++ b/lasy/profiles/speckled/gp_rpm_ssd.py @@ -54,13 +54,29 @@ class GP_RPM_SSD_Profile(SpeckleProfile): def __init__( self, - *speckle_args, + wavelength, + pol, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, relative_laser_bandwidth, phase_modulation_amplitude, number_color_cycles, transverse_bandwidth_distribution, + do_include_transverse_envelope=True, + long_profile=None, ): - super().__init__(*speckle_args) + super().__init__( + wavelength, + pol, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, + do_include_transverse_envelope, + long_profile, + ) self.laser_bandwidth = relative_laser_bandwidth # the amplitude of phase along each direction self.phase_modulation_amplitude = phase_modulation_amplitude diff --git a/lasy/profiles/speckled/rpp_cpp.py b/lasy/profiles/speckled/rpp_cpp.py index 921274082..a47b9ad6a 100644 --- a/lasy/profiles/speckled/rpp_cpp.py +++ b/lasy/profiles/speckled/rpp_cpp.py @@ -16,9 +16,29 @@ class PhasePlateProfile(SpeckleProfile): ---------- rpp_cpp: string, keyword only, can be 'rpp' or 'cpp', whether to assign beamlet phases according to RPP or CPP scheme """ - - def __init__(self, *speckle_args, rpp_cpp): - super().__init__(*speckle_args) + + def __init__( + self, + wavelength, + pol, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, + rpp_cpp, + do_include_transverse_envelope=True, + long_profile=None + ): + super().__init__( + wavelength, + pol, + laser_energy, + focal_length, + beam_aperture, + n_beamlets, + do_include_transverse_envelope, + long_profile, + ) self.rpp_cpp = rpp_cpp def beamlets_complex_amplitude( diff --git a/tests/test_laser_profiles.py b/tests/test_laser_profiles.py index 20f66cfd7..b3e338010 100644 --- a/tests/test_laser_profiles.py +++ b/tests/test_laser_profiles.py @@ -326,9 +326,9 @@ def test_speckle_profile(): focal_length, beam_aperture, n_beamlets, + relative_laser_bandwidth, do_sinc_profile, long_profile, - relative_laser_bandwidth=relative_laser_bandwidth, ) dimensions = "xyt" dx = wavelength * focal_length / beam_aperture[0] diff --git a/tests/test_speckles.py b/tests/test_speckles.py index ecb9f5477..1f1ca8220 100644 --- a/tests/test_speckles.py +++ b/tests/test_speckles.py @@ -9,6 +9,44 @@ import pytest from scipy.constants import c +def _get_arg_string( + temporal_smoothing_type, + speckle_args, + ssd_args=None, + isi_args=None, +): + if temporal_smoothing_type.upper() in ["RPP", "CPP"]: + args = [*speckle_args, temporal_smoothing_type] + elif temporal_smoothing_type.upper() in ["FM SSD", "GP RPM SSD"]: + if ssd_args is None: + raise ValueError(f"require ssd_args for SSD smoothing") + else: + args = [*speckle_args, *ssd_args] + elif temporal_smoothing_type.upper() == "GP ISI": + if isi_args is None: + raise ValueError(f"require isi_args for ISI smoothing") + else: + args = [*speckle_args, *isi_args] + else: + raise ValueError(f"Invalid smoothing type provided: {temporal_smoothing_type}") + return args + +def _get_laser_profile( + temporal_smoothing_type, + *args, + **kw_args, +): + if temporal_smoothing_type.upper() in ["RPP", "CPP"]: + profile = PhasePlateProfile(*args, **kw_args) + elif temporal_smoothing_type.upper() in "FM SSD": + profile = FM_SSD_Profile(*args, **kw_args) + elif temporal_smoothing_type.upper() == "GP RPM SSD": + profile = GP_RPM_SSD_Profile(*args, **kw_args) + elif temporal_smoothing_type.upper() == "GP ISI": + profile = GP_ISI_Profile(*args, **kw_args) + else: + raise ValueError(f"Invalid smoothing type provided: {temporal_smoothing_type}") + return profile @pytest.mark.parametrize( "temporal_smoothing_type", ["RPP", "CPP", "FM SSD", "GP RPM SSD", "GP ISI"] @@ -35,40 +73,27 @@ def test_intensity_distribution(temporal_smoothing_type): focal_length, beam_aperture, n_beamlets, - do_sinc_profile, - long_profile, ) + opt_args = { + 'do_include_transverse_envelope': do_sinc_profile, + 'long_profile': long_profile, + } relative_laser_bandwidth = 0.005 phase_modulation_amplitude = (4.1, 4.5) number_color_cycles = [1.4, 1.0] transverse_bandwidth_distribution = [1.8, 1.0] - ssd_args = { - "relative_laser_bandwidth": relative_laser_bandwidth, - "phase_modulation_amplitude": phase_modulation_amplitude, - "number_color_cycles": number_color_cycles, - "transverse_bandwidth_distribution": transverse_bandwidth_distribution, - } + ssd_args = ( + relative_laser_bandwidth, + phase_modulation_amplitude, + number_color_cycles, + transverse_bandwidth_distribution, + ) + isi_args = (relative_laser_bandwidth,) + + args = _get_arg_string(temporal_smoothing_type,speckle_args,ssd_args,isi_args) + profile = _get_laser_profile(temporal_smoothing_type, *args, **opt_args) - if temporal_smoothing_type.upper() in ["RPP", "CPP"]: - profile = PhasePlateProfile(*speckle_args, rpp_cpp=temporal_smoothing_type) - elif temporal_smoothing_type.upper() == "FM SSD": - profile = FM_SSD_Profile( - *speckle_args, - **ssd_args, - ) - elif temporal_smoothing_type.upper() == "GP RPM SSD": - profile = GP_RPM_SSD_Profile( - *speckle_args, - **ssd_args, - ) - elif temporal_smoothing_type.upper() == "GP ISI": - profile = GP_ISI_Profile( - *speckle_args, - relative_laser_bandwidth=relative_laser_bandwidth, - ) - else: - print("invalid smoothing type provided") dimensions = "xyt" dx = wavelength * focal_length / beam_aperture[0] dy = wavelength * focal_length / beam_aperture[1] @@ -129,40 +154,27 @@ def test_spatial_correlation(temporal_smoothing_type): focal_length, beam_aperture, n_beamlets, - do_sinc_profile, - long_profile, ) + opt_args = { + 'do_include_transverse_envelope': do_sinc_profile, + 'long_profile': long_profile, + } relative_laser_bandwidth = 0.005 phase_modulation_amplitude = (4.1, 4.1) number_color_cycles = [1.4, 1.0] transverse_bandwidth_distribution = [1.0, 1.0] - ssd_args = { - "relative_laser_bandwidth": relative_laser_bandwidth, - "phase_modulation_amplitude": phase_modulation_amplitude, - "number_color_cycles": number_color_cycles, - "transverse_bandwidth_distribution": transverse_bandwidth_distribution, - } + ssd_args = ( + relative_laser_bandwidth, + phase_modulation_amplitude, + number_color_cycles, + transverse_bandwidth_distribution, + ) + isi_args = (relative_laser_bandwidth,) + + args = _get_arg_string(temporal_smoothing_type,speckle_args,ssd_args,isi_args) + profile = _get_laser_profile(temporal_smoothing_type, *args, **opt_args) - if temporal_smoothing_type.upper() in ["RPP", "CPP"]: - profile = PhasePlateProfile(*speckle_args, rpp_cpp=temporal_smoothing_type) - elif temporal_smoothing_type.upper() == "FM SSD": - profile = FM_SSD_Profile( - *speckle_args, - **ssd_args, - ) - elif temporal_smoothing_type.upper() == "GP RPM SSD": - profile = GP_RPM_SSD_Profile( - *speckle_args, - **ssd_args, - ) - elif temporal_smoothing_type.upper() == "GP ISI": - profile = GP_ISI_Profile( - *speckle_args, - relative_laser_bandwidth=relative_laser_bandwidth, - ) - else: - print("invalid smoothing type provided") dimensions = "xyt" dx = wavelength * focal_length / beam_aperture[0] dy = wavelength * focal_length / beam_aperture[1] @@ -236,40 +248,26 @@ def test_sinc_zeros(temporal_smoothing_type): focal_length, beam_aperture, n_beamlets, - do_sinc_profile, - long_profile, ) + opt_args = { + 'do_include_transverse_envelope': do_sinc_profile, + 'long_profile': long_profile, + } relative_laser_bandwidth = 0.005 phase_modulation_amplitude = (4.1, 4.1) number_color_cycles = [1.4, 1.0] transverse_bandwidth_distribution = [1.0, 1.0] - ssd_args = { - "relative_laser_bandwidth": relative_laser_bandwidth, - "phase_modulation_amplitude": phase_modulation_amplitude, - "number_color_cycles": number_color_cycles, - "transverse_bandwidth_distribution": transverse_bandwidth_distribution, - } - - if temporal_smoothing_type.upper() in ["RPP", "CPP"]: - profile = PhasePlateProfile(*speckle_args, rpp_cpp=temporal_smoothing_type) - elif temporal_smoothing_type.upper() == "FM SSD": - profile = FM_SSD_Profile( - *speckle_args, - **ssd_args, - ) - elif temporal_smoothing_type.upper() == "GP RPM SSD": - profile = GP_RPM_SSD_Profile( - *speckle_args, - **ssd_args, - ) - elif temporal_smoothing_type.upper() == "GP ISI": - profile = GP_ISI_Profile( - *speckle_args, - relative_laser_bandwidth=relative_laser_bandwidth, - ) - else: - print("invalid smoothing type provided") + ssd_args = ( + relative_laser_bandwidth, + phase_modulation_amplitude, + number_color_cycles, + transverse_bandwidth_distribution, + ) + isi_args = (relative_laser_bandwidth,) + + args = _get_arg_string(temporal_smoothing_type,speckle_args,ssd_args,isi_args) + profile = _get_laser_profile(temporal_smoothing_type, *args, **opt_args) dimensions = "xyt" dx = wavelength * focal_length / beam_aperture[0] dy = wavelength * focal_length / beam_aperture[1] @@ -293,6 +291,7 @@ def test_sinc_zeros(temporal_smoothing_type): def test_FM_periodicity(): """Test that the frequency modulated Smoothing by spectral dispersion (SSD) has the correct temporal frequency.""" + temporal_smoothing_type = 'FM SSD' wavelength = 0.351e-6 # Laser wavelength in meters polarization = (1, 0) # Linearly polarized in the x direction laser_energy = 1.0 # J (this is the laser energy stored in the box defined by `lo` and `hi` below) @@ -308,22 +307,25 @@ def test_FM_periodicity(): focal_length, beam_aperture, n_beamlets, - do_sinc_profile, - long_profile, ) + opt_args = { + 'do_include_transverse_envelope': do_sinc_profile, + 'long_profile': long_profile, + } relative_laser_bandwidth = 0.005 phase_modulation_amplitude = [4.1, 4.1] number_color_cycles = [1.4, 1.0] transverse_bandwidth_distribution = [1.0, 1.0] - - laser_profile = FM_SSD_Profile( - *speckle_args, - relative_laser_bandwidth=relative_laser_bandwidth, - phase_modulation_amplitude=phase_modulation_amplitude, - number_color_cycles=number_color_cycles, - transverse_bandwidth_distribution=transverse_bandwidth_distribution, + ssd_args = ( + relative_laser_bandwidth, + phase_modulation_amplitude, + number_color_cycles, + transverse_bandwidth_distribution, ) + args = _get_arg_string(temporal_smoothing_type, speckle_args, ssd_args=ssd_args) + laser_profile = _get_laser_profile(temporal_smoothing_type, *args, **opt_args) + nu_laser = c / wavelength frac = np.sqrt( transverse_bandwidth_distribution[0] ** 2 From 62659373afddb6b1aa128d005bd476214f6ff0e8 Mon Sep 17 00:00:00 2001 From: RTSandberg Date: Thu, 9 May 2024 12:03:08 -0700 Subject: [PATCH 11/18] take out base documentation --- docs/source/api/profiles/speckled/fm_ssd.rst | 6 +----- docs/source/api/profiles/speckled/gp_isi.rst | 6 +----- docs/source/api/profiles/speckled/gp_rpm_ssd.rst | 6 +----- docs/source/api/profiles/speckled/index.rst | 1 - docs/source/api/profiles/speckled/rpp_cpp.rst | 6 +----- docs/source/api/profiles/speckled/speckled.rst | 8 -------- 6 files changed, 4 insertions(+), 29 deletions(-) delete mode 100644 docs/source/api/profiles/speckled/speckled.rst diff --git a/docs/source/api/profiles/speckled/fm_ssd.rst b/docs/source/api/profiles/speckled/fm_ssd.rst index a690d1fd9..40d2a5c6f 100644 --- a/docs/source/api/profiles/speckled/fm_ssd.rst +++ b/docs/source/api/profiles/speckled/fm_ssd.rst @@ -1,9 +1,5 @@ Frequency-modulated Smoothing by Spectral Dispersion (FM-SSD) Laser Profile =========================================================================== -.. autoclass:: lasy.profiles.speckled.SpeckleProfile - :no-index: - :members: - .. autoclass:: lasy.profiles.speckled.FM_SSD_Profile - :members: + :members: evaluate diff --git a/docs/source/api/profiles/speckled/gp_isi.rst b/docs/source/api/profiles/speckled/gp_isi.rst index c77696e89..972da5d6a 100644 --- a/docs/source/api/profiles/speckled/gp_isi.rst +++ b/docs/source/api/profiles/speckled/gp_isi.rst @@ -1,9 +1,5 @@ Smoothing by Induced Spatial Incoherence (ISI) Laser Profile ============================================================ -.. autoclass:: lasy.profiles.speckled.SpeckleProfile - :no-index: - :members: - .. autoclass:: lasy.profiles.speckled.GP_ISI_Profile - :members: + :members: evaluate diff --git a/docs/source/api/profiles/speckled/gp_rpm_ssd.rst b/docs/source/api/profiles/speckled/gp_rpm_ssd.rst index 79e3438a5..80fd3cdb1 100644 --- a/docs/source/api/profiles/speckled/gp_rpm_ssd.rst +++ b/docs/source/api/profiles/speckled/gp_rpm_ssd.rst @@ -1,9 +1,5 @@ Random Phase Modulated (RPM) Smoothing by Spectral Dispersion (FM-SSD) Laser Profile ==================================================================================== -.. autoclass:: lasy.profiles.speckled.SpeckleProfile - :no-index: - :members: - .. autoclass:: lasy.profiles.speckled.GP_RPM_SSD_Profile - :members: + :members: evaluate diff --git a/docs/source/api/profiles/speckled/index.rst b/docs/source/api/profiles/speckled/index.rst index 971255429..7571ed826 100644 --- a/docs/source/api/profiles/speckled/index.rst +++ b/docs/source/api/profiles/speckled/index.rst @@ -5,7 +5,6 @@ Speckled Laser Profiles :maxdepth: 4 :hidden: - speckled rpp_cpp fm_ssd gp_rpm_ssd diff --git a/docs/source/api/profiles/speckled/rpp_cpp.rst b/docs/source/api/profiles/speckled/rpp_cpp.rst index f3668600c..3f0cb2920 100644 --- a/docs/source/api/profiles/speckled/rpp_cpp.rst +++ b/docs/source/api/profiles/speckled/rpp_cpp.rst @@ -1,9 +1,5 @@ RPP/CPP only Laser Profile ========================== -.. autoclass:: lasy.profiles.speckled.SpeckleProfile - :no-index: - :members: - .. autoclass:: lasy.profiles.speckled.PhasePlateProfile - :members: + :members: evaluate diff --git a/docs/source/api/profiles/speckled/speckled.rst b/docs/source/api/profiles/speckled/speckled.rst deleted file mode 100644 index 028913eaa..000000000 --- a/docs/source/api/profiles/speckled/speckled.rst +++ /dev/null @@ -1,8 +0,0 @@ -Base Speckled Laser Profile -=========================== - -The ``beamlets_complex_amplitude`` function can be modified to create custom speckle profiles. -See the other profiles for examples. - -.. autoclass:: lasy.profiles.speckled.SpeckleProfile - :members: From f31b155cfb7e9e6da29cb75920000ffafc757fa6 Mon Sep 17 00:00:00 2001 From: RTSandberg Date: Thu, 9 May 2024 13:02:10 -0700 Subject: [PATCH 12/18] Getting close --- lasy/profiles/speckled/rpp_cpp.py | 42 +++++++++++++++++++++-- lasy/profiles/speckled/speckle_profile.py | 3 -- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/lasy/profiles/speckled/rpp_cpp.py b/lasy/profiles/speckled/rpp_cpp.py index a47b9ad6a..79709495c 100644 --- a/lasy/profiles/speckled/rpp_cpp.py +++ b/lasy/profiles/speckled/rpp_cpp.py @@ -1,8 +1,45 @@ import numpy as np from .speckle_profile import SpeckleProfile +class _DocumentedMetaClass(type): + """This is used as a metaclass that combines the __doc__ of the picmistandard base and of the implementation""" + def __new__(cls, name, bases, attrs): + # "if bases" skips this for the _ClassWithInit (which has no bases) + # "if bases[0].__doc__ is not None" skips this for the picmistandard classes since their bases[0] (i.e. _ClassWithInit) + # has no __doc__. + if bases and bases[0].__doc__ is not None: + implementation_doc = attrs.get('__doc__', '') + # print('implementation doc',implementation_doc) + base_doc = bases[0].__doc__ + param_delimiter = 'Parameters\n ----------\n' + opt_param_delimiter = 'do_include_transverse_envelope' -class PhasePlateProfile(SpeckleProfile): + if implementation_doc: + # The format of the added string is intentional. + # The double return "\n\n" is needed to start a new section in the documentation. + # Then the four spaces matches the standard level of indentation for doc strings + # (assuming PEP8 formatting). + # The final return "\n" assumes that the implementation doc string begins with a return, + # i.e. a line with only three quotes, """. + implementation_notes, implementation_params = implementation_doc.split(param_delimiter) + # print('implementation intro',implementation_notes) + # print('implementation params', implementation_params) + base_doc_notes, base_doc_params = base_doc.split(param_delimiter) + base_doc_needed_params, base_doc_opt_params = base_doc_params.split(opt_param_delimiter) + # print('base doc notes', base_doc_notes) + # print('base doc needed params', base_doc_needed_params) + # print('base doc opt params',base_doc_opt_params) + print('implementation params',repr(implementation_params)) + attrs['__doc__'] = base_doc_notes + implementation_notes + param_delimiter + base_doc_needed_params# + implementation_params + # attrs['__doc__'] = base_doc_notes + implementation_notes + param_delimiter + base_doc_needed_params + '\n' + implementation_params + '\n ' + opt_param_delimiter + base_doc_opt_params + # attrs['__doc__'] = bases[0].__doc__ + """\n\n Implementation specific documentation\n""" + implementation_doc + print('New doc:\n--------') + print(repr(attrs['__doc__'])) + else: + attrs['__doc__'] = base_doc + return super(_DocumentedMetaClass, cls).__new__(cls, name, bases, attrs) + +class PhasePlateProfile(SpeckleProfile, metaclass=_DocumentedMetaClass): r"""Generate a speckled laser profile with a random phase plate. This has no temporal smoothing. @@ -14,7 +51,8 @@ class PhasePlateProfile(SpeckleProfile): Parameters ---------- - rpp_cpp: string, keyword only, can be 'rpp' or 'cpp', whether to assign beamlet phases according to RPP or CPP scheme + rpp_cpp: string, can be 'rpp' or 'cpp' + Whether to assign beamlet phases according to RPP or CPP scheme """ def __init__( diff --git a/lasy/profiles/speckled/speckle_profile.py b/lasy/profiles/speckled/speckle_profile.py index 1469e73ec..c9449a90c 100644 --- a/lasy/profiles/speckled/speckle_profile.py +++ b/lasy/profiles/speckled/speckle_profile.py @@ -41,9 +41,6 @@ class SpeckleProfile(Profile): This is an adaptation of work by `Han Wen `__ to LASY. - - Notes - ----- This assumes a flat-top rectangular laser and so a rectangular arrangement of beamlets in the near-field. The longitudinal profile is currently applied to the beamlets individually in the near-field before they are propagated to the focal plane. From c90a69fa2ee0b5ca92d07a3cbec84074ce4728db Mon Sep 17 00:00:00 2001 From: RTSandberg Date: Thu, 9 May 2024 13:27:54 -0700 Subject: [PATCH 13/18] combine base and implementation documentation --- .../api/profiles/speckled/gp_rpm_ssd.rst | 2 +- .../profiles/speckled/documentation_splice.py | 28 +++++++++++++ lasy/profiles/speckled/fm_ssd.py | 9 ++-- lasy/profiles/speckled/gp_isi.py | 5 ++- lasy/profiles/speckled/gp_rpm_ssd.py | 9 ++-- lasy/profiles/speckled/rpp_cpp.py | 41 +------------------ lasy/profiles/speckled/speckle_profile.py | 21 ++++++---- 7 files changed, 56 insertions(+), 59 deletions(-) create mode 100644 lasy/profiles/speckled/documentation_splice.py diff --git a/docs/source/api/profiles/speckled/gp_rpm_ssd.rst b/docs/source/api/profiles/speckled/gp_rpm_ssd.rst index 80fd3cdb1..c1b34b2fa 100644 --- a/docs/source/api/profiles/speckled/gp_rpm_ssd.rst +++ b/docs/source/api/profiles/speckled/gp_rpm_ssd.rst @@ -1,4 +1,4 @@ -Random Phase Modulated (RPM) Smoothing by Spectral Dispersion (FM-SSD) Laser Profile +Random Phase Modulated (RPM) Smoothing by Spectral Dispersion (RPM-SSD) Laser Profile ==================================================================================== .. autoclass:: lasy.profiles.speckled.GP_RPM_SSD_Profile diff --git a/lasy/profiles/speckled/documentation_splice.py b/lasy/profiles/speckled/documentation_splice.py new file mode 100644 index 000000000..08b6bab3c --- /dev/null +++ b/lasy/profiles/speckled/documentation_splice.py @@ -0,0 +1,28 @@ + +class _DocumentedMetaClass(type): + """This is used as a metaclass that combines the __doc__ of the picmistandard base and of the implementation""" + def __new__(cls, name, bases, attrs): + # "if bases" skips this for the _ClassWithInit (which has no bases) + # "if bases[0].__doc__ is not None" skips this for the picmistandard classes since their bases[0] (i.e. _ClassWithInit) + # has no __doc__. + if bases and bases[0].__doc__ is not None: + implementation_doc = attrs.get('__doc__', '') + # print('implementation doc',implementation_doc) + base_doc = bases[0].__doc__ + param_delimiter = 'Parameters\n ----------\n' + opt_param_delimiter = ' do_include_transverse_envelope' + + if implementation_doc: + # The format of the added string is intentional. + # The double return "\n\n" is needed to start a new section in the documentation. + # Then the four spaces matches the standard level of indentation for doc strings + # (assuming PEP8 formatting). + # The final return "\n" assumes that the implementation doc string begins with a return, + # i.e. a line with only three quotes, """. + implementation_notes, implementation_params = implementation_doc.split(param_delimiter) + base_doc_notes, base_doc_params = base_doc.split(param_delimiter) + base_doc_needed_params, base_doc_opt_params = base_doc_params.split(opt_param_delimiter) + attrs['__doc__'] = base_doc_notes + implementation_notes + param_delimiter + base_doc_needed_params + implementation_params[:-5] + '\n\n' + opt_param_delimiter + base_doc_opt_params + else: + attrs['__doc__'] = base_doc + return super(_DocumentedMetaClass, cls).__new__(cls, name, bases, attrs) \ No newline at end of file diff --git a/lasy/profiles/speckled/fm_ssd.py b/lasy/profiles/speckled/fm_ssd.py index e2a5fc72f..0722dbf3e 100644 --- a/lasy/profiles/speckled/fm_ssd.py +++ b/lasy/profiles/speckled/fm_ssd.py @@ -1,9 +1,10 @@ import numpy as np from .speckle_profile import SpeckleProfile +from .documentation_splice import _DocumentedMetaClass -class FM_SSD_Profile(SpeckleProfile): - r"""Generate a speckled laser profile with smoothing by frequency modulated (FM) spectral dispersion (SSD). +class FM_SSD_Profile(SpeckleProfile, metaclass=_DocumentedMetaClass): + r"""Specific speckled laser profile information for smoothing by frequency modulated (FM) spectral dispersion (SSD). In frequency-modulated smoothing by spectral dispersion, or FM-SSD, the amplitude of the beamlets is always :math:`A_{ml}(t)=1`. There are two contributions to the phase :math:`\phi_{ml}` of each beamlet: @@ -41,13 +42,13 @@ class FM_SSD_Profile(SpeckleProfile): relative_laser_bandwidth : float Resulting bandwidth :math:`\Delta_\nu` of the laser pulse, relative to central frequency, due to the frequency modulation. - phase_modulation_amplitude :list of 2 floats + phase_modulation_amplitude : list of 2 floats Amplitudes :math:`\delta_{x},\delta_{y}` of phase modulation in each transverse direction. number_color_cycles : list of 2 floats Number of color cycles :math:`N_{cc,x},N_{cc,y}` of SSD spectrum to include in modulation - transverse_bandwidth_distribution: list of 2 floats + transverse_bandwidth_distribution : list of 2 floats Determines how much SSD is distributed in the :math:`x` and :math:`y` directions. if `transverse_bandwidth_distribution=[a,b]`, then the SSD frequency modulation is :math:`r_x=a/\sqrt{a^2+b^2}` in :math:`x` and :math:`r_y=b/\sqrt{a^2+b^2}` in :math:`y`. """ diff --git a/lasy/profiles/speckled/gp_isi.py b/lasy/profiles/speckled/gp_isi.py index df5889619..74f95273e 100644 --- a/lasy/profiles/speckled/gp_isi.py +++ b/lasy/profiles/speckled/gp_isi.py @@ -1,10 +1,11 @@ import numpy as np from .speckle_profile import SpeckleProfile from .stochastic_process_utilities import gen_gaussian_time_series +from .documentation_splice import _DocumentedMetaClass -class GP_ISI_Profile(SpeckleProfile): - r"""Generate a speckled laser profile with smoothing inspired by Induced Spatial Incoherence (ISI). +class GP_ISI_Profile(SpeckleProfile, metaclass=_DocumentedMetaClass): + r"""Specific speckled laser profile information for smoothing inspired by Induced Spatial Incoherence (ISI). This is a smoothing technique with temporal stochastic variation in the beamlet phases and amplitudes to simulate the random phase differences and amplitudes the beamlets pick up when passing through ISI echelons. diff --git a/lasy/profiles/speckled/gp_rpm_ssd.py b/lasy/profiles/speckled/gp_rpm_ssd.py index 0f18695fe..0a50465a4 100644 --- a/lasy/profiles/speckled/gp_rpm_ssd.py +++ b/lasy/profiles/speckled/gp_rpm_ssd.py @@ -1,10 +1,11 @@ import numpy as np from .speckle_profile import SpeckleProfile from .stochastic_process_utilities import gen_gaussian_time_series +from .documentation_splice import _DocumentedMetaClass -class GP_RPM_SSD_Profile(SpeckleProfile): - r"""Generate a speckled laser profile with smoothing by a random phase modulated (RPM) spectral dispersion (SSD). +class GP_RPM_SSD_Profile(SpeckleProfile, metaclass=_DocumentedMetaClass): + r"""Specific speckled laser profile information for smoothing by a random phase modulated (RPM) spectral dispersion (SSD). This provides a version of smoothing by spectral dispersion (SSD) where the phases are randomly modulated. Here the amplitude of the beamlets is always :math:`A_{ml}(t)=1`. @@ -38,7 +39,7 @@ class GP_RPM_SSD_Profile(SpeckleProfile): Bandwidth :math:`\Delta_\nu` of the laser pulse, relative to central frequency. Only used if ``temporal_smoothing_type`` is ``'FM SSD'``, ``'GP RPM SSD'`` or ``'GP ISI'``. - phase_modulation_amplitude :list of 2 floats + phase_modulation_amplitude : list of 2 floats Amplitudes :math:`\delta_{x},\delta_{y}` of phase modulation in each transverse direction. Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. @@ -46,7 +47,7 @@ class GP_RPM_SSD_Profile(SpeckleProfile): Number of color cycles :math:`N_{cc,x},N_{cc,y}` of SSD spectrum to include in modulation Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. - transverse_bandwidth_distribution: list of 2 floats + transverse_bandwidth_distribution : list of 2 floats Determines how much SSD is distributed in the :math:`x` and :math:`y` directions. if `transverse_bandwidth_distribution=[a,b]`, then the SSD frequency modulation is :math:`a/\sqrt{a^2+b^2}` in :math:`x` and :math:`b/\sqrt{a^2+b^2}` in :math:`y`. Only used if ``temporal_smoothing_type`` is ``'FM SSD'`` or ``'GP RPM SSD'``. diff --git a/lasy/profiles/speckled/rpp_cpp.py b/lasy/profiles/speckled/rpp_cpp.py index 79709495c..627b0ca34 100644 --- a/lasy/profiles/speckled/rpp_cpp.py +++ b/lasy/profiles/speckled/rpp_cpp.py @@ -1,46 +1,9 @@ import numpy as np from .speckle_profile import SpeckleProfile - -class _DocumentedMetaClass(type): - """This is used as a metaclass that combines the __doc__ of the picmistandard base and of the implementation""" - def __new__(cls, name, bases, attrs): - # "if bases" skips this for the _ClassWithInit (which has no bases) - # "if bases[0].__doc__ is not None" skips this for the picmistandard classes since their bases[0] (i.e. _ClassWithInit) - # has no __doc__. - if bases and bases[0].__doc__ is not None: - implementation_doc = attrs.get('__doc__', '') - # print('implementation doc',implementation_doc) - base_doc = bases[0].__doc__ - param_delimiter = 'Parameters\n ----------\n' - opt_param_delimiter = 'do_include_transverse_envelope' - - if implementation_doc: - # The format of the added string is intentional. - # The double return "\n\n" is needed to start a new section in the documentation. - # Then the four spaces matches the standard level of indentation for doc strings - # (assuming PEP8 formatting). - # The final return "\n" assumes that the implementation doc string begins with a return, - # i.e. a line with only three quotes, """. - implementation_notes, implementation_params = implementation_doc.split(param_delimiter) - # print('implementation intro',implementation_notes) - # print('implementation params', implementation_params) - base_doc_notes, base_doc_params = base_doc.split(param_delimiter) - base_doc_needed_params, base_doc_opt_params = base_doc_params.split(opt_param_delimiter) - # print('base doc notes', base_doc_notes) - # print('base doc needed params', base_doc_needed_params) - # print('base doc opt params',base_doc_opt_params) - print('implementation params',repr(implementation_params)) - attrs['__doc__'] = base_doc_notes + implementation_notes + param_delimiter + base_doc_needed_params# + implementation_params - # attrs['__doc__'] = base_doc_notes + implementation_notes + param_delimiter + base_doc_needed_params + '\n' + implementation_params + '\n ' + opt_param_delimiter + base_doc_opt_params - # attrs['__doc__'] = bases[0].__doc__ + """\n\n Implementation specific documentation\n""" + implementation_doc - print('New doc:\n--------') - print(repr(attrs['__doc__'])) - else: - attrs['__doc__'] = base_doc - return super(_DocumentedMetaClass, cls).__new__(cls, name, bases, attrs) +from .documentation_splice import _DocumentedMetaClass class PhasePlateProfile(SpeckleProfile, metaclass=_DocumentedMetaClass): - r"""Generate a speckled laser profile with a random phase plate. + r"""Specific speckled laser profile information for random phase plate class. This has no temporal smoothing. The amplitude of the beamlets is always :math:`A_{ml}(t)=1` and diff --git a/lasy/profiles/speckled/speckle_profile.py b/lasy/profiles/speckled/speckle_profile.py index c9449a90c..11d7dfac6 100644 --- a/lasy/profiles/speckled/speckle_profile.py +++ b/lasy/profiles/speckled/speckle_profile.py @@ -5,7 +5,7 @@ class SpeckleProfile(Profile): r""" - Derived class for the profile of a speckled laser pulse. + Profile of a speckled laser pulse. Speckled lasers are used to mitigate laser-plasma interactions in fusion and ion acceleration contexts. More on the subject can be found in chapter 9 of `P. Michel, Introduction to Laser-Plasma Interactions `__. @@ -39,12 +39,6 @@ class SpeckleProfile(Profile): :math:`\Delta y=\frac{\lambda_0fN_{by}}{D_{y}}`. The other parameters in these formulas are defined below. - This is an adaptation of work by `Han Wen `__ to LASY. - - This assumes a flat-top rectangular laser and so a rectangular arrangement of beamlets in the near-field. - The longitudinal profile is currently applied to the beamlets - individually in the near-field before they are propagated to the focal plane. - Parameters ---------- wavelength : float (in meters) @@ -72,12 +66,21 @@ class SpeckleProfile(Profile): n_beamlets : list of 2 integers Number of RPP/CPP elements :math:`N_{bx},N_{by}` in each direction, in the near field. - do_include_transverse_envelope : boolean + do_include_transverse_envelope : boolean (optional, default: False) Whether to include the transverse sinc envelope or not. I.e. whether it is assumed to be close enough to the laser axis to neglect the transverse field decay. - long_profile : Lasy Longitudinal laser object (or None). + long_profile : Lasy Longitudinal laser object (optional, default: None). If this is not None, the longitudinal profile is applied individually to the beamlets in the near-field. + + Notes + ----- + + This is an adaptation of work by `Han Wen `__ to LASY. + + This assumes a flat-top rectangular laser and so a rectangular arrangement of beamlets in the near-field. + The longitudinal profile is currently applied to the beamlets + individually in the near-field before they are propagated to the focal plane. """ def __init__( From 36d281f4746ea211e9ba78368525d636e62c4729 Mon Sep 17 00:00:00 2001 From: RTSandberg Date: Thu, 9 May 2024 13:34:28 -0700 Subject: [PATCH 14/18] fix doc underline --- docs/source/api/profiles/speckled/gp_rpm_ssd.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/api/profiles/speckled/gp_rpm_ssd.rst b/docs/source/api/profiles/speckled/gp_rpm_ssd.rst index c1b34b2fa..4f7d17d70 100644 --- a/docs/source/api/profiles/speckled/gp_rpm_ssd.rst +++ b/docs/source/api/profiles/speckled/gp_rpm_ssd.rst @@ -1,5 +1,5 @@ Random Phase Modulated (RPM) Smoothing by Spectral Dispersion (RPM-SSD) Laser Profile -==================================================================================== +===================================================================================== .. autoclass:: lasy.profiles.speckled.GP_RPM_SSD_Profile :members: evaluate From f94b6adc30cf09ce01cf7fe987652d033c4127af Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 9 May 2024 20:36:16 +0000 Subject: [PATCH 15/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../profiles/speckled/documentation_splice.py | 31 +++++++++++----- lasy/profiles/speckled/rpp_cpp.py | 7 ++-- tests/test_speckles.py | 37 ++++++++++--------- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/lasy/profiles/speckled/documentation_splice.py b/lasy/profiles/speckled/documentation_splice.py index 08b6bab3c..0dee630dc 100644 --- a/lasy/profiles/speckled/documentation_splice.py +++ b/lasy/profiles/speckled/documentation_splice.py @@ -1,16 +1,16 @@ - class _DocumentedMetaClass(type): """This is used as a metaclass that combines the __doc__ of the picmistandard base and of the implementation""" + def __new__(cls, name, bases, attrs): # "if bases" skips this for the _ClassWithInit (which has no bases) # "if bases[0].__doc__ is not None" skips this for the picmistandard classes since their bases[0] (i.e. _ClassWithInit) # has no __doc__. if bases and bases[0].__doc__ is not None: - implementation_doc = attrs.get('__doc__', '') + implementation_doc = attrs.get("__doc__", "") # print('implementation doc',implementation_doc) base_doc = bases[0].__doc__ - param_delimiter = 'Parameters\n ----------\n' - opt_param_delimiter = ' do_include_transverse_envelope' + param_delimiter = "Parameters\n ----------\n" + opt_param_delimiter = " do_include_transverse_envelope" if implementation_doc: # The format of the added string is intentional. @@ -19,10 +19,23 @@ def __new__(cls, name, bases, attrs): # (assuming PEP8 formatting). # The final return "\n" assumes that the implementation doc string begins with a return, # i.e. a line with only three quotes, """. - implementation_notes, implementation_params = implementation_doc.split(param_delimiter) + implementation_notes, implementation_params = implementation_doc.split( + param_delimiter + ) base_doc_notes, base_doc_params = base_doc.split(param_delimiter) - base_doc_needed_params, base_doc_opt_params = base_doc_params.split(opt_param_delimiter) - attrs['__doc__'] = base_doc_notes + implementation_notes + param_delimiter + base_doc_needed_params + implementation_params[:-5] + '\n\n' + opt_param_delimiter + base_doc_opt_params + base_doc_needed_params, base_doc_opt_params = base_doc_params.split( + opt_param_delimiter + ) + attrs["__doc__"] = ( + base_doc_notes + + implementation_notes + + param_delimiter + + base_doc_needed_params + + implementation_params[:-5] + + "\n\n" + + opt_param_delimiter + + base_doc_opt_params + ) else: - attrs['__doc__'] = base_doc - return super(_DocumentedMetaClass, cls).__new__(cls, name, bases, attrs) \ No newline at end of file + attrs["__doc__"] = base_doc + return super(_DocumentedMetaClass, cls).__new__(cls, name, bases, attrs) diff --git a/lasy/profiles/speckled/rpp_cpp.py b/lasy/profiles/speckled/rpp_cpp.py index 627b0ca34..c955c7f03 100644 --- a/lasy/profiles/speckled/rpp_cpp.py +++ b/lasy/profiles/speckled/rpp_cpp.py @@ -1,7 +1,8 @@ import numpy as np from .speckle_profile import SpeckleProfile from .documentation_splice import _DocumentedMetaClass - + + class PhasePlateProfile(SpeckleProfile, metaclass=_DocumentedMetaClass): r"""Specific speckled laser profile information for random phase plate class. @@ -17,7 +18,7 @@ class PhasePlateProfile(SpeckleProfile, metaclass=_DocumentedMetaClass): rpp_cpp: string, can be 'rpp' or 'cpp' Whether to assign beamlet phases according to RPP or CPP scheme """ - + def __init__( self, wavelength, @@ -28,7 +29,7 @@ def __init__( n_beamlets, rpp_cpp, do_include_transverse_envelope=True, - long_profile=None + long_profile=None, ): super().__init__( wavelength, diff --git a/tests/test_speckles.py b/tests/test_speckles.py index 1f1ca8220..4ec915421 100644 --- a/tests/test_speckles.py +++ b/tests/test_speckles.py @@ -9,11 +9,12 @@ import pytest from scipy.constants import c + def _get_arg_string( - temporal_smoothing_type, - speckle_args, - ssd_args=None, - isi_args=None, + temporal_smoothing_type, + speckle_args, + ssd_args=None, + isi_args=None, ): if temporal_smoothing_type.upper() in ["RPP", "CPP"]: args = [*speckle_args, temporal_smoothing_type] @@ -31,6 +32,7 @@ def _get_arg_string( raise ValueError(f"Invalid smoothing type provided: {temporal_smoothing_type}") return args + def _get_laser_profile( temporal_smoothing_type, *args, @@ -48,6 +50,7 @@ def _get_laser_profile( raise ValueError(f"Invalid smoothing type provided: {temporal_smoothing_type}") return profile + @pytest.mark.parametrize( "temporal_smoothing_type", ["RPP", "CPP", "FM SSD", "GP RPM SSD", "GP ISI"] ) @@ -75,8 +78,8 @@ def test_intensity_distribution(temporal_smoothing_type): n_beamlets, ) opt_args = { - 'do_include_transverse_envelope': do_sinc_profile, - 'long_profile': long_profile, + "do_include_transverse_envelope": do_sinc_profile, + "long_profile": long_profile, } relative_laser_bandwidth = 0.005 @@ -91,7 +94,7 @@ def test_intensity_distribution(temporal_smoothing_type): ) isi_args = (relative_laser_bandwidth,) - args = _get_arg_string(temporal_smoothing_type,speckle_args,ssd_args,isi_args) + args = _get_arg_string(temporal_smoothing_type, speckle_args, ssd_args, isi_args) profile = _get_laser_profile(temporal_smoothing_type, *args, **opt_args) dimensions = "xyt" @@ -156,8 +159,8 @@ def test_spatial_correlation(temporal_smoothing_type): n_beamlets, ) opt_args = { - 'do_include_transverse_envelope': do_sinc_profile, - 'long_profile': long_profile, + "do_include_transverse_envelope": do_sinc_profile, + "long_profile": long_profile, } relative_laser_bandwidth = 0.005 @@ -172,7 +175,7 @@ def test_spatial_correlation(temporal_smoothing_type): ) isi_args = (relative_laser_bandwidth,) - args = _get_arg_string(temporal_smoothing_type,speckle_args,ssd_args,isi_args) + args = _get_arg_string(temporal_smoothing_type, speckle_args, ssd_args, isi_args) profile = _get_laser_profile(temporal_smoothing_type, *args, **opt_args) dimensions = "xyt" @@ -250,8 +253,8 @@ def test_sinc_zeros(temporal_smoothing_type): n_beamlets, ) opt_args = { - 'do_include_transverse_envelope': do_sinc_profile, - 'long_profile': long_profile, + "do_include_transverse_envelope": do_sinc_profile, + "long_profile": long_profile, } relative_laser_bandwidth = 0.005 @@ -265,8 +268,8 @@ def test_sinc_zeros(temporal_smoothing_type): transverse_bandwidth_distribution, ) isi_args = (relative_laser_bandwidth,) - - args = _get_arg_string(temporal_smoothing_type,speckle_args,ssd_args,isi_args) + + args = _get_arg_string(temporal_smoothing_type, speckle_args, ssd_args, isi_args) profile = _get_laser_profile(temporal_smoothing_type, *args, **opt_args) dimensions = "xyt" dx = wavelength * focal_length / beam_aperture[0] @@ -291,7 +294,7 @@ def test_sinc_zeros(temporal_smoothing_type): def test_FM_periodicity(): """Test that the frequency modulated Smoothing by spectral dispersion (SSD) has the correct temporal frequency.""" - temporal_smoothing_type = 'FM SSD' + temporal_smoothing_type = "FM SSD" wavelength = 0.351e-6 # Laser wavelength in meters polarization = (1, 0) # Linearly polarized in the x direction laser_energy = 1.0 # J (this is the laser energy stored in the box defined by `lo` and `hi` below) @@ -309,8 +312,8 @@ def test_FM_periodicity(): n_beamlets, ) opt_args = { - 'do_include_transverse_envelope': do_sinc_profile, - 'long_profile': long_profile, + "do_include_transverse_envelope": do_sinc_profile, + "long_profile": long_profile, } relative_laser_bandwidth = 0.005 From 2ef2538f7c651f29a29c398c042a4c6ad998c512 Mon Sep 17 00:00:00 2001 From: RTSandberg Date: Thu, 9 May 2024 13:40:51 -0700 Subject: [PATCH 16/18] address CI suggestions --- lasy/profiles/speckled/documentation_splice.py | 2 +- lasy/profiles/speckled/speckle_profile.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lasy/profiles/speckled/documentation_splice.py b/lasy/profiles/speckled/documentation_splice.py index 0dee630dc..b0983f2f6 100644 --- a/lasy/profiles/speckled/documentation_splice.py +++ b/lasy/profiles/speckled/documentation_splice.py @@ -1,5 +1,5 @@ class _DocumentedMetaClass(type): - """This is used as a metaclass that combines the __doc__ of the picmistandard base and of the implementation""" + """Metaclass that combines the __doc__ of the SpeckleProfile base and of the implementation.""" def __new__(cls, name, bases, attrs): # "if bases" skips this for the _ClassWithInit (which has no bases) diff --git a/lasy/profiles/speckled/speckle_profile.py b/lasy/profiles/speckled/speckle_profile.py index 11d7dfac6..f1b00c843 100644 --- a/lasy/profiles/speckled/speckle_profile.py +++ b/lasy/profiles/speckled/speckle_profile.py @@ -4,8 +4,7 @@ class SpeckleProfile(Profile): - r""" - Profile of a speckled laser pulse. + r"""Profile of a speckled laser pulse. Speckled lasers are used to mitigate laser-plasma interactions in fusion and ion acceleration contexts. More on the subject can be found in chapter 9 of `P. Michel, Introduction to Laser-Plasma Interactions `__. @@ -75,7 +74,6 @@ class SpeckleProfile(Profile): Notes ----- - This is an adaptation of work by `Han Wen `__ to LASY. This assumes a flat-top rectangular laser and so a rectangular arrangement of beamlets in the near-field. From 3acbe804c1d03cda44950da5f94b160d3ba5940f Mon Sep 17 00:00:00 2001 From: RTSandberg Date: Fri, 17 May 2024 13:54:01 -0700 Subject: [PATCH 17/18] clarify phases and small tweak to doc wording --- lasy/profiles/speckled/fm_ssd.py | 2 +- lasy/profiles/speckled/gp_isi.py | 2 +- lasy/profiles/speckled/gp_rpm_ssd.py | 2 +- lasy/profiles/speckled/rpp_cpp.py | 6 +++--- lasy/profiles/speckled/speckle_profile.py | 11 +++-------- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/lasy/profiles/speckled/fm_ssd.py b/lasy/profiles/speckled/fm_ssd.py index 0722dbf3e..6687387fb 100644 --- a/lasy/profiles/speckled/fm_ssd.py +++ b/lasy/profiles/speckled/fm_ssd.py @@ -4,7 +4,7 @@ class FM_SSD_Profile(SpeckleProfile, metaclass=_DocumentedMetaClass): - r"""Specific speckled laser profile information for smoothing by frequency modulated (FM) spectral dispersion (SSD). + r"""Speckled laser profile information specific to smoothing by frequency modulated (FM) spectral dispersion (SSD). In frequency-modulated smoothing by spectral dispersion, or FM-SSD, the amplitude of the beamlets is always :math:`A_{ml}(t)=1`. There are two contributions to the phase :math:`\phi_{ml}` of each beamlet: diff --git a/lasy/profiles/speckled/gp_isi.py b/lasy/profiles/speckled/gp_isi.py index 74f95273e..201261674 100644 --- a/lasy/profiles/speckled/gp_isi.py +++ b/lasy/profiles/speckled/gp_isi.py @@ -5,7 +5,7 @@ class GP_ISI_Profile(SpeckleProfile, metaclass=_DocumentedMetaClass): - r"""Specific speckled laser profile information for smoothing inspired by Induced Spatial Incoherence (ISI). + r"""Speckled laser profile information specific to smoothing inspired by Induced Spatial Incoherence (ISI). This is a smoothing technique with temporal stochastic variation in the beamlet phases and amplitudes to simulate the random phase differences and amplitudes the beamlets pick up when passing through ISI echelons. diff --git a/lasy/profiles/speckled/gp_rpm_ssd.py b/lasy/profiles/speckled/gp_rpm_ssd.py index 0a50465a4..3d981112f 100644 --- a/lasy/profiles/speckled/gp_rpm_ssd.py +++ b/lasy/profiles/speckled/gp_rpm_ssd.py @@ -5,7 +5,7 @@ class GP_RPM_SSD_Profile(SpeckleProfile, metaclass=_DocumentedMetaClass): - r"""Specific speckled laser profile information for smoothing by a random phase modulated (RPM) spectral dispersion (SSD). + r"""Speckled laser profile information specific to smoothing by a random phase modulated (RPM) spectral dispersion (SSD). This provides a version of smoothing by spectral dispersion (SSD) where the phases are randomly modulated. Here the amplitude of the beamlets is always :math:`A_{ml}(t)=1`. diff --git a/lasy/profiles/speckled/rpp_cpp.py b/lasy/profiles/speckled/rpp_cpp.py index c955c7f03..a0c4816c1 100644 --- a/lasy/profiles/speckled/rpp_cpp.py +++ b/lasy/profiles/speckled/rpp_cpp.py @@ -4,14 +4,14 @@ class PhasePlateProfile(SpeckleProfile, metaclass=_DocumentedMetaClass): - r"""Specific speckled laser profile information for random phase plate class. + r"""Laser profile information for the random phase plate / continuous phase plate class of speckled lasers. This has no temporal smoothing. The amplitude of the beamlets is always :math:`A_{ml}(t)=1` and the relative phases of the beamlets, resulting from the randomly sized phase plate sections, are assigned randomly. - If the user specifies Random Phase Plate (RPP: `rpp`), the beamlet phases are drawn with equal probabilities from the set :math:`{0,2\pi}`. - If the user specifies Continuous Phase Plate (CPP: `cpp`), the beamlet phases are drawn from a uniform distribution on the interval :math:`[0,2\pi]`. + If the user specifies Random Phase Plate (RPP: `rpp`), the beamlet phases :math:`\phi_{ml}(t)` are drawn with equal probabilities from the set :math:`{0,2\pi}`. + If the user specifies Continuous Phase Plate (CPP: `cpp`), the beamlet phases :math:`\phi_{ml}(t)` are drawn from a uniform distribution on the interval :math:`[0,2\pi]`. Parameters ---------- diff --git a/lasy/profiles/speckled/speckle_profile.py b/lasy/profiles/speckled/speckle_profile.py index f1b00c843..faa23b55e 100644 --- a/lasy/profiles/speckled/speckle_profile.py +++ b/lasy/profiles/speckled/speckle_profile.py @@ -167,18 +167,12 @@ def generate_speckle_pattern(self, t_now, x, y): x_focus_list = X_focus_matrix[:, 0] y_focus_list = Y_focus_matrix[0, :] x_phase_focus_matrix = np.exp( - -2 - * np.pi - * 1j - / self.n_beamlets[0] + -2 * np.pi * 1j / self.n_beamlets[0] * self.x_lens_list[:, np.newaxis] * x_focus_list[np.newaxis, :] ) y_phase_focus_matrix = np.exp( - -2 - * np.pi - * 1j - / self.n_beamlets[1] + -2 * np.pi * 1j / self.n_beamlets[1] * self.y_lens_list[:, np.newaxis] * y_focus_list[np.newaxis, :] ) @@ -186,6 +180,7 @@ def generate_speckle_pattern(self, t_now, x, y): if self.long_profile is not None: # have to unnormalize t_now to evaluate in longitudinal profile bca = bca * self.long_profile.evaluate(t_now / c * self.lambda0) + # propagate from near-field to focus speckle_amp = np.einsum( "jk,jl->kl", np.einsum("ij,ik->jk", bca, x_phase_focus_matrix), From 43c110359a15a68f198cff30e4650bd8a75ebc42 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 17 May 2024 20:54:14 +0000 Subject: [PATCH 18/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- lasy/profiles/speckled/speckle_profile.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lasy/profiles/speckled/speckle_profile.py b/lasy/profiles/speckled/speckle_profile.py index faa23b55e..4a631580f 100644 --- a/lasy/profiles/speckled/speckle_profile.py +++ b/lasy/profiles/speckled/speckle_profile.py @@ -167,12 +167,18 @@ def generate_speckle_pattern(self, t_now, x, y): x_focus_list = X_focus_matrix[:, 0] y_focus_list = Y_focus_matrix[0, :] x_phase_focus_matrix = np.exp( - -2 * np.pi * 1j / self.n_beamlets[0] + -2 + * np.pi + * 1j + / self.n_beamlets[0] * self.x_lens_list[:, np.newaxis] * x_focus_list[np.newaxis, :] ) y_phase_focus_matrix = np.exp( - -2 * np.pi * 1j / self.n_beamlets[1] + -2 + * np.pi + * 1j + / self.n_beamlets[1] * self.y_lens_list[:, np.newaxis] * y_focus_list[np.newaxis, :] )