Luna.jl
Nonlinear optical pulse propagator
Install / Use
/learn @LupoLab/Luna.jlREADME
Luna.jl
[!IMPORTANT]
To stay up-to-date with Luna.jl and learn about bugfixes and new features, subscribe to our mailing list.
Luna.jl is a flexible platform for the simulation of nonlinear optical dynamics—both in waveguides (such as optical fibres) and free-space geometries—using the unidirectional pulse propagation equation (UPPE) and its approximate forms, such as the commonly used generalised nonlinear Schrödinger equation (GNLSE). Some of the key features of Luna:
- A variety of propagation geometries treated in a unified way:
- Single-mode (mode-averaged) propagation in waveguides
- Multi-mode propagation in waveguides with arbitrary (including non-symmetric) mode-shapes, full polarisation resolution, and intermodal coupling for arbitrary nonlinear polarisation terms
- Waveguides with arbitrarily varying material properties and cross-sections (e.g. tapered fibres)
- Free-space propagation with radial symmetry
- Full (3+1)-dimensional free-space propagation
- Both field-resolved and envelope propagation equations
- A range of linear and nonlinear optical effects:
- Modal dispersion and loss in waveguides
- Optical Kerr effect (third-order nonlinearity)
- Raman scattering in molecular gases or glasses
- Strong-field photoionisation and plasma dynamics
- A built-in interface for the running and processing of multi-dimensional parameter scans in serial or parallel
- A standard library of plotting and processing functions, including calculation of spectrograms and beam properties
Luna is designed to be extensible: adding e.g. a new type of waveguide or a new nonlinear effect is straightforward, even without editing the main source code.
Luna was originally developed for modelling ultrafast pulse propagation in gas-filled hollow capillary fibres and hollow-core photonic crystal fibres. Therefore, such simulations have particularly good support. It is also excellent for modelling propagation in solid-core fibres.
Luna is written in the Julia programming language, chosen for its unique combination of readability, ease of use, and speed. If you want to use Luna but are new to Julia, see the relevant section of this README.
There are two ways of using Luna:
- A very simple high-level interface for the most heavily optimised applications of Luna: propagation in gas-filled hollow capillary fibres and hollow-core photonic crystal fibres (consisting of the function
prop_capillaryand some helper functions to create input pulses); or propagation of simple GNLSE simulations (consisting of the functionprop_gnlse). - A low-level interface which allows for full control and customisation of the simulation parameters, the use of custom waveguide modes and gas fills (including gas mixtures), and free-space propagation simulations.
For a short introduction on how to use the simple interface, see the Quickstart or GNLSE sections below. More information, including on the internals of Luna, can be found in the Documentation.
Installation
Luna requires Julia v1.9 or later, which can be obtained from here. In a Julia terminal, to install Luna simply enter the package manager with ] and run add Luna:
]
add Luna
This will install and precompile Luna and all its dependencies.
Quickstart
To run a simple simulation of ultrafast pulse propagation in a gas-filled hollow capillary fibre, you can use prop_capillary. As an example, take a 3-metre length of HCF with 125 μm core radius, filled with 1 bar of helium gas, and driving pulses centred at 800 nm wavelength with 120 μJ of energy and 10 fs duration. We consider a frequency grid which spans from 120 nm to 4 μm and a time window of 1 ps.
julia> using Luna
julia> output = prop_capillary(125e-6, 3, :He, 1; λ0=800e-9, energy=120e-6, τfwhm=10e-15, λlims=(150e-9, 4e-6), trange=1e-12)
The first time you run this code, you will see the precompilation message:
julia> using Luna
[ Info: Precompiling Luna [30eb0fb0-5147-11e9-3356-d75b018717ce]
This will take some time to complete (and you may see additional precompilation messages for the packages Luna depends on), but is only necessary once, unless you update Luna or edit the package source code. Since this is using the default options including FFT planning and caching of the PPT ionisation rate, you will also have to wait for those processes to finish. After the simulation finally runs (which for this example should take between 10 seconds and one minute), you will have the results stored in output:
julia> output = prop_capillary(125e-6, 3, :He, 1; λ0=800e-9, energy=120e-6, τfwhm=10e-15, λlims=(150e-9, 4e-6), trange=1e-12)
[...]
MemoryOutput["simulation_type", "dumps", "meta", "Eω", "grid", "stats", "z"]
You can access the results by indexing into output like a Dict, for example for the frequency-domain field Eω:
julia> output["Eω"]
8193×201 Array{Complex{Float64},2}:
[...]
The shape of this array is (Nω x Nz) where Nω is the number of frequency samples and Nz is the number of steps that were saved during the propagation. By default, prop_capillary will solve the full-field (carrier-resolved) UPPE. In this case, the numerical Fourier transforms are done using rfft, so the number of frequency samples is (Nt/2 + 1) with Nt the number of samples in the time domain.
Multi-mode propagation
prop_capillary accepts many keyword arguments (for a full list see the documentation) to customise the simulation parameters and input pulse. One of the most important is modes, which defines whether mode-averaged or multi-mode propagation is used, and which modes are included. By default, prop_capillary considers mode-averaged propagation in the fundamental (HE₁₁) mode of the capillary, which is fast and simple but less accurate, especially at high intensity when self-focusing and photoionisation play important roles in the propagation dynamics.
Mode-averaged propagation is activated using modes=:HE11 (the default) or replacing the :HE11 with a different mode designation (for mode-averaged propagation in a different mode). To run the same simulation as above with the first four modes (HE₁₁ to HE₁₄) of the capillary, set modes to 4 (this example also uses smaller time and frequency windows to make the simulation run a little faster):
julia> prop_capillary(125e-6, 3, :He, 1; λ0=800e-9, modes=4, energy=120e-6, τfwhm=10e-15, trange=400e-15, λlims=(150e-9, 4e-6))
The propagation will take much longer, and the output field Eω now has shape (Nω x Nm x Nz) with Nm the number of modes:
julia> output_multimode["Eω"]
2049×4×201 Array{Complex{Float64},3}:
[...]
NOTE: Setting modes=:HE11 and modes=1 are not equivalent, except if only the Kerr effect is included in the simulation. The former uses mode-averaged propagation (treating all spatial dependence of the nonlinear polarisation the same as the Kerr effect) whereas the latter projects the spatially dependent nonlinear polarisation onto a single mode. This difference is especially important when photoionisation plays a major role.
Plotting results
More usefully, you can directly plot the propagation results using Plotting.prop_2D() (Plotting is imported at the same time as prop_capillary by the using Luna statement):
julia> Plotting.prop_2D(output)
PyPlot.Figure(PyObject <Figure size 2400x800 with 4 Axes>)
This should show a plot like this:
You can also display the power spectrum at the input and output (and anywhere in between):
julia> Plotting.spec_1D(output, [0, 1.5, 3]; log10=true)
PyPlot.Figure(PyObject <Figure size 1700x1000 with 1 Axes>)
which will show this:
Plotting functions accept many additional keyword arguments to quickly display relevant information. For example, you can show the bandpass-filtered UV pulse from the simulation using the bandpass argument:
julia> Plotting.time_1D(output, [2, 2.5, 3]; trange=(-10e-15, 30e-15), bandpass=(180e-9, 220e-9))
PyPlot.Figure(PyObject <Figure size 1700x1000 with 1 Axes>)

For multi-mode simulations, the plotting functions will display all modes individually by default. You can display the sum over modes instead using modes=:sum:
julia> Plotting.spec_1D(output_multimode; log10=true, modes=:sum)
PyPlot.Figure(PyObject <Figure size 1700x1000 with 1 Axes>)
(Compare this to the mode-averaged case above and note the important differences, e.g. the appearance of additional ultraviolet dispersive waves in higher-order modes.)
More plotting functions are available in the Plotting module, including for propagation statistics (Plotting.stats(output)) and spectrograms (Plotting.spectrogram())
Output processing
The Processing module contains many useful functions for more detailed processing and manual plotting, including:
- Spectral energy density on frequency or wavelength axis with optional spectral resolution setting (
Processing.getEωandProcessing.getIω) - Time-domain fields and pulse envelopes with flexible frequency bandpass and linear (dispersive) propagation operators (
Processing.getEt) - Ene
