from math import factorial
import numpy as np
from scipy.special import hermite
from .transverse_profile import TransverseProfile
[docs]
class HermiteGaussianTransverseProfile(TransverseProfile):
r"""
A high-order Gaussian laser pulse expressed in the Hermite-Gaussian formalism.
Definition is according to Siegman "Lasers" pg. 646 eq. 60, as explicitly given
in https://doi.org/10.1364/JOSAB.489884 eq. 3, with the beam center upon the
optical axis, :math:`x_0,y_0 = (0,0)`.
More precisely, the transverse envelope (to be used in the
:class:`.CombinedLongitudinalTransverseLaser` class) corresponds to:
.. math::
\mathcal{T}(x, y) = \,
\mathcal{H}_m (x) \, \mathcal{H}_n(y) \, \exp(i \left ( \Phi_x(z) + \Phi_y(z)\right ) )
with
.. math::
\mathcal{H}_p(q) = A_p h_p \left ( \frac{\sqrt{2}q}{w_q(z)} \right) \exp{\left( -\frac{q^2}{w_q^2(z)}\right)} \exp{ \left ( -i k_0 \frac{q^2}{2 R_q(z)} \right )}
\Phi_q(z) = \left(p+\frac{1}{2}\right) \arctan\left({\frac{z}{Z_q}}\right)
w_q(z) = w_{0,q} \sqrt{1 + \left( \frac{z}{Z_q}\right)^2}
Z_q = \frac{\pi w_{0,q}^2}{\lambda_0}
A_p = \frac{1}{\sqrt{w_q(z) 2^{p-1/2} p!\sqrt{\pi}}}
R_q(z) = z + \frac{Z_q^2}{z}
where :math:`h_{p}` is the Hermite polynomial of order :math:`p`, :math:`w_q(z)` is the
spot size of the laser along the :math:`q` axis (:math:`q` is :math:`x` or :math:`y`), :math:`Z_q` is the corresponding Rayleigh
length and, :math:`\lambda_0` and :math:`k_0` are the wavelength and central wavenumber of the
laser respectively.
The z-dependence 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_0x : float (in meter)
The waist of the laser pulse in the x direction,
w_0y : float (in meter)
The waist of the laser pulse in the y direction,
m : int (dimensionless)
The order of hermite polynomial in the x direction
n : int (dimensionless)
The order of hermite polynomial in the y direction
wavelength : float (in meter), optional
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.hermite_gaussian_profile import (
... HermiteGaussianTransverseProfile,
... )
>>> # 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 m in range(3):
>>> for n in range(3):
>>> transverse_profile = HermiteGaussianTransverseProfile(
... w_0x = 10e-6, # m
... w_0y = 15e-6, # m
... m = m, #
... n = n, #
... wavelength = 0.8e-6, # m
... )
... intensity = np.abs(transverse_profile.evaluate(X,Y))**2
... vmax_intensity = np.max(intensity)
>>> ax[m,n].imshow(intensity,extent=extent,cmap='bone_r',vmin=0,vmax=vmax_intensity)
>>> ax[m,n].set_title('Inten: m,n = %i,%i' %(m,n))
>>> phase = np.angle(transverse_profile.evaluate(X,Y))
... vmax_phase = np.max(np.abs(phase))
>>> ax[m,n+3].imshow(phase,extent=extent,cmap='seismic',vmin=-vmax_phase,vmax=vmax_phase)
>>> ax[m,n+3].set_title('Phase: m,n = %i,%i' %(m,n))
>>> if m==2:
>>> ax[m,n].set_xlabel("x (µm)")
>>> ax[m,n+3].set_xlabel("x (µm)")
>>> else:
>>> ax[m,n].set_xticks([])
>>> ax[m,n+3].set_xticks([])
>>> if n==0:
>>> ax[m,n].set_ylabel("y (µm)")
>>> ax[m,n+3].set_yticks([])
>>> else:
>>> ax[m,n].set_yticks([])
>>> ax[m,n+3].set_yticks([])
"""
def __init__(self, w_0x, w_0y, m, n, wavelength, z_foc=0):
super().__init__()
self.w_0x = w_0x
self.w_0y = w_0y
self.m = m
self.n = n
self.wavelength = wavelength
self.z_foc = z_foc
z_eval = -z_foc # this links our observation position to Siegmann's definition
self.k0 = 2 * np.pi / wavelength
# Calculate Rayleigh Lengths
Zx = np.pi * w_0x**2 / wavelength
Zy = np.pi * w_0y**2 / wavelength
# Calculate Size at Location Z
wxZ = w_0x * np.sqrt(1 + (z_eval / Zx) ** 2)
wyZ = w_0y * np.sqrt(1 + (z_eval / Zy) ** 2)
# Calculate Multiplicative Factors
Anx = 1 / np.sqrt(wxZ * 2 ** (m - 1 / 2) * factorial(m) * np.sqrt(np.pi))
Any = 1 / np.sqrt(wyZ * 2 ** (n - 1 / 2) * factorial(n) * np.sqrt(np.pi))
# Calculate the Phase contributions from propagation
phiXz = (m + 1 / 2) * np.arctan2(z_eval, Zx)
phiYz = (n + 1 / 2) * np.arctan2(z_eval, Zy)
self.z_eval = z_eval
self.Zx = Zx
self.Zy = Zy
self.wxZ = wxZ
self.wyZ = wyZ
self.Anx = Anx
self.Any = Any
self.phiXz = phiXz
self.phiYz = phiYz
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
Zx = self.Zx
Zy = self.Zy
k0 = self.k0
wxZ = self.wxZ
wyZ = self.wyZ
Anx = self.Anx
Any = self.Any
m = self.m
n = self.n
phiXz = self.phiXz
phiYz = self.phiYz
# Calculate the HG in each plane
HGnx = (
Anx
* hermite(m)(np.sqrt(2) * (x) / wxZ)
* np.exp(-((x) ** 2) / wxZ**2)
* np.exp(-1j * k0 * (x) ** 2 / 2 / (z_eval**2 + Zx**2) * z_eval)
)
HGny = (
Any
* hermite(n)(np.sqrt(2) * (y) / wyZ)
* np.exp(-((y) ** 2) / wyZ**2)
* np.exp(-1j * k0 * (y) ** 2 / 2 / (z_eval**2 + Zy**2) * z_eval)
)
# Put it altogether
envelope = HGnx * HGny * np.exp(1j * (phiXz + phiYz))
return envelope