Source code for lasy.profiles.transverse.laguerre_gaussian_profile

from math import factorial

import numpy as np
from scipy.special import genlaguerre

from .transverse_profile import TransverseProfile


[docs] class LaguerreGaussianTransverseProfile(TransverseProfile): r""" A high-order Gaussian laser pulse expressed in the Laguerre-Gaussian formalism. Definition is according to Siegman "Lasers" pg. 646 eq. 64. More precisely, the transverse envelope (to be used in the :class:`.CombinedLongitudinalTransverseLaser` class) corresponds to: .. math:: \mathcal{T}(x, y) = \, \mathcal{L}_{p,m} (x) \, \exp(i \Phi) with .. math:: \mathcal{L}_{p,m}(x) = A \left ( \frac{\sqrt{2}r}{w(z)} \right)^m l_{p,m} \left ( \frac{2 r^2}{w^2(z)} \right) \exp{\left( -\frac{r^2}{w^2(z)}\right)} \exp{ \left ( -i k_0 \frac{r^2}{2 R(z)} \right )} w(z) = w_{0} \sqrt{1 + \left( \frac{z}{Z_R}\right)^2} A = \frac{1}{w(z)} \sqrt{\frac{2 p!}{\pi (p + m)!}} R(z) = z + \frac{Z_R^2}{z} \Phi(z) = \left(2 p + m + 1\right) \arctan\left({\frac{z}{Z_R}}\right) Z_R = \frac{\pi w_0^2}{\lambda_0} where :math:`l_{p,m}` is the Laguerre polynomial of radial order :math:`p` and azimuthal order :math:`m`. The z-depedence shown in the above equations is required to correctly define the electric field of the transverse profile relative to that of the pulse at the focus. The absolute z position will be overwritten when creating a laser object. Parameters ---------- w_0 : float (in meter) The waist of the laser pulse, i.e. :math:`w_{0}` in the above formula. p : int (dimensionless) The order of Laguerre polynomial in the x direction i.e. :math:`m` in the above formula. m : int (dimensionless) The order of Laguerre polynomial in the y direction i.e. :math:`n` in the above formula. wavelength : float (in meter) The main laser wavelength :math:`\lambda_0` of the laser. z_foc : float (in meter), optional Position of the focal plane. (The laser pulse is initialized at ``z=0``.) Warnings -------- In order to initialize the pulse out of focus, you can either: - Use a non-zero ``z_foc`` - Use ``z_foc=0`` (i.e. initialize the pulse at focus) and then call ``laser.propagate(-z_foc)`` Both methods are in principle equivalent, but note that the first method uses the paraxial approximation, while the second method does not make this approximation. Examples -------- >>> import matplotlib.pyplot as plt >>> import numpy as np >>> from lasy.profiles.transverse.laguerre_gaussian_profile import ( ... LaguerreGaussianTransverseProfile, ... ) >>> # Create evaluation grid >>> xy = np.linspace(-30e-6, 30e-6, 200) >>> X, Y = np.meshgrid(xy, xy) >>> # Create an array of plots >>> fig, ax = plt.subplots(3, 6, figsize=(10, 5), tight_layout=True) >>> extent = (1e6 * xy[0], 1e6 * xy[-1], 1e6 * xy[0], 1e6 * xy[-1]) >>> for p in range(3): >>> for m in range(3): >>> transverse_profile = LaguerreGaussianTransverseProfile( ... w_0 = 10e-6, # m ... p = p, # ... m = m, # ... wavelength = 0.8e-6, # m ... ) ... intensity = np.abs(transverse_profile.evaluate(X,Y))**2 ... vmax_intensity = np.max(intensity) >>> ax[p,m].imshow(intensity,extent=extent,cmap='bone_r',vmin=0,vmax=vmax_intensity) >>> ax[p,m].set_title('Inten: p,m = %i,%i' %(p,m)) >>> phase = np.angle(transverse_profile.evaluate(X,Y)) ... vmax_phase = np.max(np.abs(phase)) >>> ax[p,m+3].imshow(phase,extent=extent,cmap='seismic',vmin=-vmax_phase,vmax=vmax_phase) >>> ax[p,m+3].set_title('Phase: p,m = %i,%i' %(p,m)) >>> if p==2: >>> ax[p,m].set_xlabel("x (µm)") >>> ax[p,m+3].set_xlabel("x (µm)") >>> else: >>> ax[p,m].set_xticks([]) >>> ax[p,m+3].set_xticks([]) >>> if m==0: >>> ax[p,m].set_ylabel("y (µm)") >>> ax[p,m+3].set_yticks([]) >>> else: >>> ax[p,m].set_yticks([]) >>> ax[p,m+3].set_yticks([]) """ def __init__(self, w_0, p, m, wavelength, z_foc=0): super().__init__() self.w_0 = w_0 self.p = p self.m = m self.wavelength = wavelength self.z_foc = z_foc z_eval = -z_foc # this links our observation position to Siegman's definition self.k0 = 2 * np.pi / wavelength # Calculate Rayleigh Length Zr = np.pi * w_0**2 / wavelength # Calculate Size at Location Z w0Z = w_0 * np.sqrt(1 + (z_eval / Zr) ** 2) # Calculate Multiplicative Factors A = np.sqrt(2.0 * factorial(p) / (np.pi * factorial(m + p))) / w0Z # Calculate the Phase contributions from propagation phiZ = (2.0 * p + m + 1) * np.arctan2(z_eval, Zr) self.z_eval = z_eval self.Zr = Zr self.w0Z = w0Z self.A = A self.phiZ = phiZ def _evaluate(self, x, y): """ Return the transverse envelope. Parameters ---------- x, y : 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 """ z_eval = self.z_eval Zr = self.Zr k0 = self.k0 w0Z = self.w0Z A = self.A p = self.p m = self.m phiZ = self.phiZ # Calculate the LG in each plane LG = ( A * (np.sqrt(2.0) * np.sqrt(x**2 + y**2) / w0Z) ** (m) * genlaguerre(p, m)(2.0 * (x**2 + y**2) / w0Z**2) * np.exp(-(x**2 + y**2) / w0Z**2) * np.exp(-1j * k0 * (x**2 + y**2) / 2 / (z_eval**2 + Zr**2) * z_eval) ) # Put it altogether envelope = LG * np.exp(1j * phiZ) * np.exp(-1j * m * np.arctan2(y, x)) return envelope