Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add axiparabola #254

Merged
merged 39 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a641a0c
Add `show` method to visualize the laser
RemiLehe Oct 26, 2023
8c61135
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 26, 2023
8c4ba85
Fix doc
RemiLehe Oct 26, 2023
773381f
Apply suggestions from code review
RemiLehe Oct 26, 2023
2f0752c
Base class for optical elements
RemiLehe Oct 20, 2023
b9b22f3
Add documentation
RemiLehe Oct 20, 2023
bbb95b2
Add parabolic mirror
RemiLehe Oct 20, 2023
63ad71e
Add automated test for parabolic mirror
RemiLehe Oct 24, 2023
751c6a6
Add 3D automated test
RemiLehe Oct 24, 2023
4458be0
Update comments
RemiLehe Oct 24, 2023
d6bd90a
Add axiparabola
RemiLehe Oct 25, 2023
4b5bb3e
Fix minor bugs
RemiLehe Oct 25, 2023
85d1fde
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 25, 2023
3cf7182
Implement simplified axiparabola
RemiLehe Oct 26, 2023
3d57f18
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 26, 2023
452dc5f
Fix documentations
RemiLehe Oct 26, 2023
c1a1996
Add tutorial with axiparabola
RemiLehe Oct 27, 2023
ca23bac
Merge branch 'development' into optical_elements
RemiLehe Oct 27, 2023
50ba746
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 27, 2023
4ba6ec5
Merge branch 'development' into optical_elements
RemiLehe Jul 15, 2024
343e05e
Remove axiparabola
RemiLehe Jul 15, 2024
c63a122
Add new API
RemiLehe Jul 15, 2024
12d6ee8
Add missing documentation
RemiLehe Jul 15, 2024
3f4151c
Add axiparabola
RemiLehe Jul 15, 2024
b58b1f9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 15, 2024
a243d5d
Merge branch 'development' into add_axiparabola
RemiLehe Jul 16, 2024
776af2b
Merge branch 'development' into add_axiparabola
RemiLehe Jul 31, 2024
ea5548d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 31, 2024
e8cb6cf
Cleanup
RemiLehe Jul 31, 2024
b2b6dea
Merge branch 'add_axiparabola' of github.com:RemiLehe/lasy into add_a…
RemiLehe Aug 1, 2024
c2b6247
Update test and tutorial
RemiLehe Aug 1, 2024
489c91f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 1, 2024
cf5dca4
Update docstring
RemiLehe Aug 1, 2024
7d59d18
Merge branch 'add_axiparabola' of github.com:RemiLehe/lasy into add_a…
RemiLehe Aug 1, 2024
fe2e24e
Update docstring
RemiLehe Aug 1, 2024
a818692
Update name of class
RemiLehe Aug 1, 2024
6cd7695
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 1, 2024
5a60427
Include new tutorial
RemiLehe Aug 1, 2024
b6acd81
Update lasy/optical_elements/axiparabola.py
MaxThevenet Aug 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The sections below describe the main objects that are accessible in the ``lasy``

laser
profiles/index
optical_elements/index
utils/index

If you are looking for a specific class or function, see the :ref:`genindex` or use the search bar of this website.
8 changes: 8 additions & 0 deletions docs/source/api/optical_elements/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Optical elements
================

.. toctree::
:maxdepth: 1

parabolic_mirror
axiparabola
5 changes: 5 additions & 0 deletions docs/source/api/optical_elements/parabolic_mirror.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Parabolic mirror
================

.. autoclass:: lasy.optical_elements.ParabolicMirror
:members:
240 changes: 240 additions & 0 deletions docs/source/tutorials/axiparabola.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
{
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is easier to review this file by looking at the rendering here:
https://lasydoc--254.org.readthedocs.build/en/254/tutorials/axiparabola.html

"cells": [
{
"cell_type": "markdown",
"id": "996ae31d-192b-4988-bd6b-649c833ae967",
"metadata": {},
"source": [
"# Initializing a flying-focus laser from an axiparabola"
]
},
{
"cell_type": "markdown",
"id": "f25efcc9-1ed0-4f9a-8c62-da36f56ba02f",
"metadata": {},
"source": [
"In this example, we generate a \"flying-focus\" laser from an axiparabola. This is done by sending a super-Gaussian laser (in the near-field) onto an axiparabola and propagating it to the far field."
]
},
{
"cell_type": "markdown",
"id": "7f076564-8330-4af0-ad92-708e1825596f",
"metadata": {},
"source": [
"## Generate a super-Gaussian laser"
]
},
{
"cell_type": "markdown",
"id": "eb5e1364-1b92-42de-9518-dedf62be4544",
"metadata": {},
"source": [
"Define the physical profile, as combination of a longitudinal and transverse profile."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ed0a409c-3c02-4fc4-b41b-ef41d0fb5a3a",
"metadata": {},
"outputs": [],
"source": [
"from lasy.laser import Laser\n",
"from lasy.profiles.gaussian_profile import CombinedLongitudinalTransverseProfile\n",
"from lasy.profiles.longitudinal import GaussianLongitudinalProfile\n",
"from lasy.profiles.transverse import SuperGaussianTransverseProfile\n",
"\n",
"wavelength = 800e-9 # Laser wavelength in meters\n",
"polarization = (1,0) # Linearly polarized in the x direction\n",
"energy = 1.5 # Energy of the laser pulse in joules\n",
"spot_size = 1e-3 # Spot size in the near-field: millimeter-scale\n",
"pulse_duration = 30e-15 # Pulse duration of the laser in seconds\n",
"t_peak = 0.0 # Location of the peak of the laser pulse in time\n",
"\n",
"laser_profile = CombinedLongitudinalTransverseProfile(\n",
" wavelength, polarization, energy,\n",
" GaussianLongitudinalProfile(wavelength, pulse_duration, t_peak),\n",
" SuperGaussianTransverseProfile(spot_size, n_order=16)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "c303d3b6-2422-445b-a332-bc3f48388612",
"metadata": {},
"source": [
"Define the grid on which this profile is evaluated. \n",
"\n",
"**The grid needs to be wide enough to contain the millimeter-scale spot size, but also fine enough to resolve the micron-scale laser wavelength.**"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "84cb6fd2-2f44-4cc9-a5c1-6dd1e0d72c67",
"metadata": {},
"outputs": [],
"source": [
"dimensions = 'rt' # Use cylindrical geometry\n",
"lo = (0,-2.5*pulse_duration) # Lower bounds of the simulation box\n",
"hi = (1.1*spot_size,2.5*pulse_duration) # Upper bounds of the simulation box\n",
"num_points = (3000, 30) # Number of points in each dimension\n",
"\n",
"laser = Laser(dimensions,lo,hi,num_points,laser_profile)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bb8dc634-b3c4-4448-834b-fe9acb6c9645",
"metadata": {},
"outputs": [],
"source": [
"laser.show()"
]
},
{
"cell_type": "markdown",
"id": "1f588477-90c0-45c3-a47c-2b57733b462f",
"metadata": {},
"source": [
"## Propagate the laser through the axiparabola, and to the far field.\n",
"\n",
"First, define the parameters of the axiparabola."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e3eae91f-edb7-44ac-9b92-5c1a1328a4b3",
"metadata": {},
"outputs": [],
"source": [
"from lasy.optical_elements import AxiParabola\n",
"f0 = 3e-2 # Focal distance\n",
"delta = 1.5e-2 # Focal range\n",
"R = spot_size # Radius\n",
"axiparabola = AxiParabola( f0, delta, R )"
]
},
{
"cell_type": "markdown",
"id": "292c6e63-0480-4fb9-b1ad-6b679a3472d0",
"metadata": {},
"source": [
"Propagate the laser through the axiparabola, and for a distance z=f0 (beginning of the focal range)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3158e51d-e331-438d-90c1-5f8c63bf9841",
"metadata": {},
"outputs": [],
"source": [
"laser.propagate( f0, initial_optical_element=axiparabola )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fa581cef-fc3d-414b-9f19-df8545d3b62f",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"laser.show()\n",
"plt.ylim(-0.25e-3, 0.25e-3)"
]
},
{
"cell_type": "markdown",
"id": "a5582bfe-ce2d-4aec-a757-03a48d4f16f2",
"metadata": {},
"source": [
"At this point, the laser can be saved to file, and used e.g. as input to a PIC simulation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9b8c068a-715b-400f-b6ed-7711a60c0f1f",
"metadata": {},
"outputs": [],
"source": [
"laser.write_to_file('flying_focus', 'h5')"
]
},
{
"cell_type": "markdown",
"id": "192cabcd-b96b-4bff-80b0-2c59d5a780cb",
"metadata": {},
"source": [
"## Check that the electric field on axis remains high over many Rayleigh ranges\n",
"\n",
"An axiparabola can maintain a high laser field over a long distance (larger than the Rayleigh length).\n",
"Here, we can check that the laser field remains high over several Rayleigh length."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ef0d6f2e-3aac-47a0-baef-39b42d4c5749",
"metadata": {},
"outputs": [],
"source": [
"import math\n",
"ZR = math.pi*wavelength*f0**2/spot_size**2\n",
"print('Rayleigh length: ', ZR)\n",
"assert delta > 5*ZR"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e2113f14-3ec7-4392-8d91-96f6e28dcc71",
"metadata": {},
"outputs": [],
"source": [
"laser.propagate(2*ZR)\n",
"laser.show()\n",
"plt.ylim(-0.25e-3, 0.25e-3)\n",
"plt.title('Laser field after 2 Rayleigh range')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3cb4bd3b-ad8d-471f-a2c0-1257c1b3bd39",
"metadata": {},
"outputs": [],
"source": [
"laser.propagate(2*ZR)\n",
"laser.show()\n",
"plt.ylim(-0.25e-3, 0.25e-3)\n",
"plt.title('Laser field after 4 Rayleigh range')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
47 changes: 47 additions & 0 deletions lasy/laser.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,46 @@ def normalize(self, value, kind="energy"):
else:
raise ValueError(f'kind "{kind}" not recognized')

def apply_optics(self, optical_element):
"""
Propagate the laser pulse through a thin optical element.

Parameter
---------
optical_element: an :class:`.OpticalElement` object (optional)
Represents a thin optical element, through which the laser
propagates.
"""
# Transform the field from temporal to frequency domain
time_axis_indx = -1
field_fft = np.fft.ifft(self.grid.field, axis=time_axis_indx, norm="backward")

# Create the frequency axis
dt = self.grid.dx[time_axis_indx]
omega0 = self.profile.omega0
Nt = self.grid.field.shape[time_axis_indx]
omega = 2 * np.pi * np.fft.fftfreq(Nt, dt) + omega0

# Apply optical element
if self.dim == "rt":
r, w = np.meshgrid(self.grid.axes[0], omega, indexing="ij")
# The line below assumes that amplitude_multiplier
# is cylindrically-symmetric, hence we pass
# `r` as `x` and 0 as `y`
multiplier = optical_element.amplitude_multiplier(r, 0, w)
for i_m in range(self.grid.azimuthal_modes.size):
field_fft[i_m, :, :] *= multiplier
else:
x, y, w = np.meshgrid(
self.grid.axes[0], self.grid.axes[1], omega, indexing="ij"
)
field_fft *= optical_element.amplitude_multiplier(x, y, w)

# Transform field from frequency to temporal domain
self.grid.field[:, :, :] = np.fft.fft(
field_fft, axis=time_axis_indx, norm="backward"
)

def propagate(self, distance, nr_boundary=None, backend="NP", show_progress=True):
"""
Propagate the laser pulse by the distance specified.
Expand All @@ -169,10 +209,16 @@ def propagate(self, distance, nr_boundary=None, backend="NP", show_progress=True
distance : scalar
Distance by which the laser pulse should be propagated

initial_optical_element: an :class:`.OpticalElement` object (optional)
Represents a thin optical element, through which the laser
propagates, before propagating for `distance` in free space.
If this is `None`, no optical element is used.

nr_boundary : integer (optional)
Number of cells at the end of radial axis, where the field
will be attenuated (to assert proper Hankel transform).
Only used for ``'rt'``.

backend : string (optional)
Backend used by axiprop (see axiprop documentation).
show_progress : bool (optional)
Expand Down Expand Up @@ -212,6 +258,7 @@ def propagate(self, distance, nr_boundary=None, backend="NP", show_progress=True
omega0 = self.profile.omega0
Nt = self.grid.field.shape[time_axis_indx]
omega = 2 * np.pi * np.fft.fftfreq(Nt, dt) + omega0

# make 3D shape for the frequency axis
omega_shape = (1, 1, self.grid.field.shape[time_axis_indx])

Expand Down
4 changes: 4 additions & 0 deletions lasy/optical_elements/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .parabolic_mirror import ParabolicMirror
from .axiparabola import AxiParabola

__all__ = ["ParabolicMirror", "AxiParabola"]
Loading
Loading