Source code for lasy.profiles.transverse.flattened_gaussian_profile
import math
import numpy as np
from scipy.special import binom
from .transverse_profile import TransverseProfile
[docs]
class FlattenedGaussianTransverseProfile(TransverseProfile):
r"""
Class for the analytic profile of a Flattened-Gaussian laser pulse.
Define a complex transverse profile with a flattened Gaussian intensity
distribution **far from focus** that transforms into a distribution
with rings **in the focal plane**. (See `Santarsiero et al., J.
Modern Optics, 1997 <http://doi.org/10.1080/09500349708232927>`_)
Increasing the parameter ``N`` increases the
flatness of the transverse profile **far from focus**,
and increases the number of rings **in the focal plane**.
The implementation of this class is based on that from `FBPIC`
<https://github.com/fbpic/fbpic/blob/dev/fbpic/lpa_utils/laser/transverse_laser_profiles.py>.
**In the focal plane** (:math:`z=0`), or in the far field, the profile translates to a
laser with a transverse electric field:
.. math::
E(x,y,z=0) \propto
\exp\left(-\frac{r^2}{(N+1)w_0^2}\right)
\sum_{n=0}^N c'_n L^0_n\left(\frac{2\,r^2}{(N+1)w_0^2}\right)
with Laguerre polynomials :math:`L^0_n` and
.. math::
\qquad c'_n=\sum_{m=n}^{N}\frac{1}{2^m}\binom{m}{n}
- For :math:`N=0`, this is a Gaussian profile: :math:`E\propto\exp\left(-\frac{r^2}{w_0^2}\right)`.
- For :math:`N\rightarrow\infty`, this is a Jinc profile: :math:`E\propto\frac{J_1(r/w_0)}{r/w_0}`.
The equivalent expression for **the collimated beam** in the near field which produces this focus is
given by:
.. math::
E(x,y) \propto
\exp\left(-\frac{(N+1)r^2}{w^2}\right)
\sum_{n=0}^N \frac{1}{n!}\left(\frac{(N+1)\,r^2}{w^2}\right)^n
with the relationship between the spot sizes of the beams in the far field and in the near field given by:
.. math::
w = \frac{\lambda_0}{\pi w_0}|z_{\mathrm{foc}}|
where :math:`z_{\mathrm{foc}}` is the distance between the far field and near field planes.
- Note that a beam defined using the near field definition would be
equivalent to a beam defined with the corresponding parameters far from focus in
the far field, but without the parabolic phase arising from being
defined far from the focus.
- For :math:`N=0`, the near field profile is a Gaussian profile: :math:`E\propto\exp\left(-\frac{r^2}{w^2}\right)`.
- For :math:`N\rightarrow\infty`, the near field profile is a flat profile: :math:`E\propto\Theta(w-r)`.
Parameters
----------
field_type : string
Options: 'nearfield', when the beam is defined far from focus (e.g., right before the focusing optics), or 'farfield', when the beam is in the vicinity of the focus.
w : float (in meter)
The waist of the laser pulse. If ``field_type == 'farfield'`` then this
variable corresponds to :math:`w_{0}` in the above far field formula.
If ``field_type == 'nearfield'`` then this variable corresponds to
:math:`w` in the above near field formula.
N : int
Determines the "flatness" of the transverse profile, far from
focus (see the above formula).
Default: ``N=6`` ; somewhat close to an 8th order supergaussian.
wavelength : float (in meter)
The main laser wavelength :math:`\lambda_0` of the laser.
z_foc : float (in meter), optional
Only required if defining the pulse in the far field. Gives the position
of the focal plane. (The laser pulse is initialized at ``z=0``.)
"""
def __init__(self, field_type, w, N, wavelength, z_foc=0):
super().__init__()
# Ensure that N is an integer
assert isinstance(N, int) and N >= 0
assert field_type in ["nearfield", "farfield"]
self.field_type = field_type
self.N = N
if field_type == "farfield":
w0 = w
# Calculate effective waist of the Laguerre-Gauss modes, at focus
self.w_foc = w0 * (self.N + 1) ** 0.5
# Calculate Rayleigh Length
self.zr = np.pi * self.w_foc**2 / wavelength
# Evaluation distance w.r.t focal position
self.z_eval = z_foc
# Calculate the coefficients for the Laguerre-Gaussian modes
self.cn = np.empty(self.N + 1)
for n in range(self.N + 1):
m_values = np.arange(n, self.N + 1)
self.cn[n] = np.sum((1.0 / 2) ** m_values * binom(m_values, n)) / (
self.N + 1
)
else:
self.w = w
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
"""
if self.field_type == "farfield":
# Term for wavefront curvature + Gouy phase
diffract_factor = 1.0 - 1j * self.z_eval / self.zr
w = self.w_foc * np.abs(diffract_factor)
psi = np.angle(diffract_factor)
# Argument for the Laguerre polynomials
scaled_radius_squared = 2 * (x**2 + y**2) / w**2
# Sum recursively over the Laguerre polynomials
laguerre_sum = np.zeros_like(x, dtype=np.complex128)
for n in range(0, self.N + 1):
# Recursive calculation of the Laguerre polynomial
# - `L` represents $L_n$
# - `L1` represents $L_{n-1}$
# - `L2` represents $L_{n-2}$
if n == 0:
L = 1.0
elif n == 1:
L1 = L
L = 1.0 - scaled_radius_squared
else:
L2 = L1
L1 = L
L = (((2 * n - 1) - scaled_radius_squared) * L1 - (n - 1) * L2) / n
# Add to the sum, including the term for the additional Gouy phase
laguerre_sum += self.cn[n] * np.exp(-(2j * n) * psi) * L
# Final envelope: multiply by n-independent propagation factors
exp_argument = -(x**2 + y**2) / (self.w_foc**2 * diffract_factor)
envelope = laguerre_sum * np.exp(exp_argument) / diffract_factor
return envelope
else:
N = self.N
w = self.w
sumseries = 0
for n in range(N + 1):
sumseries += (
1 / math.factorial(n) * ((N + 1) * (x**2 + y**2) / w**2) ** n
)
envelope = np.exp(-(N + 1) * (x**2 + y**2) / w**2) * sumseries
return envelope