diff --git a/.github/workflows/documenter.yml b/.github/workflows/documenter.yml index 2550af9f..051261ff 100644 --- a/.github/workflows/documenter.yml +++ b/.github/workflows/documenter.yml @@ -15,9 +15,11 @@ jobs: - uses: julia-actions/setup-julia@latest with: version: ^1.7.0-rc1 - - name: Install dependencies + - name: Install dependencies and build package env: PYTHON: # remove PYTHON env variable to make sure packages are installed + uses: julia-actions/julia-buildpkg@v1 + - name: Instantiate documentation pkg run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate();' - name: Build and deploy env: diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index be61c16c..e789dfcb 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -8,17 +8,19 @@ jobs: strategy: fail-fast: false matrix: - julia-version: ['1.7', '1.8', '1.9'] + julia-version: ['1.9', '1'] julia-arch: [x64] os: [ubuntu-latest, windows-latest, macos-latest] if: github.event.pull_request.draft == false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@latest with: version: ${{ matrix.julia-version }} arch: ${{ matrix.julia-arch }} - - name: Install dependencies + - name: Install dependencies and build env: PYTHON: # remove PYTHON env variable to make sure packages are installed + uses: julia-actions/julia-buildpkg@v1 + - name: Run tests uses: julia-actions/julia-runtest@v1 diff --git a/Project.toml b/Project.toml index 354d30ee..61eaba2f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,12 +1,13 @@ name = "Luna" uuid = "30eb0fb0-5147-11e9-3356-d75b018717ce" authors = ["chrisbrahms <38351086+chrisbrahms@users.noreply.github.com>"] -version = "0.3.0" +version = "0.5.0" [deps] ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" BlackBoxOptim = "a134a8b2-14d6-55f6-9291-3336d3ab0209" CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" +Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d" CoolProp = "e084ae63-2819-5025-826e-f8e611a84251" Cubature = "667455a9-e2ce-5579-9412-b964f529a492" DSP = "717857b8-e6f2-59f4-9121-6e50c889abd2" @@ -18,6 +19,7 @@ Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" EllipsisNotation = "da5c29d0-fa7d-589e-88eb-ea29b0a81949" FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" +FileWatching = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" FiniteDifferences = "26cc04aa-876d-5657-8c51-4c34ba976000" GSL = "92c85e6c-cbff-5e0c-80f7-495c94daaecd" Glob = "c27321d9-0574-5035-807b-f59d2c89b15c" @@ -33,7 +35,7 @@ NumericalIntegration = "e7bfaba1-d571-5449-8927-abc22e82249b" Optim = "429524aa-4258-5aef-a3af-852621145aeb" Peaks = "18e31ff7-3703-566c-8e60-38913d67486b" PhysicalConstants = "5ad8b20f-a522-5ce9-bfc9-ddf1d5bda6ab" -Pidfile = "fa939f87-e72e-5be4-a000-7fc836dbe307" +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Polynomials = "f27b6e38-b328-58d1-80ce-0feddd5e7a45" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" ProgressLogging = "33c8b6b6-d38a-422a-b730-caa89a2f386c" @@ -43,6 +45,7 @@ QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" Roots = "f2b01f46-fcfa-551c-844a-d8ac1e96c665" +Scratch = "6c6a2e73-6563-6170-7368-637461726353" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" @@ -53,34 +56,46 @@ Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" ArgParse = "1" BlackBoxOptim = "0.6" CSV = "0.9, 0.10" -CoolProp = "0.1" +Conda = "1" +CoolProp = "0.1, 0.2" Cubature = "1.5.1" DSP = "0.7.3" DataStructures = "0.18.10" +Dates = "1.9" DelimitedFiles = "1" Dierckx = "0.5.1" -Documenter = "0.27.5" +Distributed = "1.9" +Documenter = "0.27.5, 1" EllipsisNotation = "1" FFTW = "1.3" +FileWatching = "1.9" FiniteDifferences = "0.11, 0.12" GSL = "1" Glob = "1.3" H5Zblosc = "0.1" HCubature = "1.5" -HDF5 = "0.15, 0.16" +HDF5 = "0.15, 0.16, 0.17" Hankel = "0.5.5" +LibGit2 = "1.9" +LinearAlgebra = "1.9" +Logging = "1.9" Memoize = "0.4.4" NumericalIntegration = "0.3.3" Optim = "1.4" -Peaks = "0.3.2, 0.4" +Peaks = "0.3.2, 0.4, 0.5" PhysicalConstants = "0.2" -Pidfile = "1.2" -Polynomials = "2, 3" +Pkg = "1.9" +Polynomials = "2, 3, 4" +Printf = "1.9" ProgressLogging = "0.1" QuadGK = "2.4" +Random = "1.9" Reexport = "1.2" Roots = "2" +Scratch = "1" SpecialFunctions = "0.10.3, 1, 2" StaticArrays = "1" +Statistics = "1.9" +Test = "1.9" Unitful = "1" -julia = "1.7" +julia = "1.9" diff --git a/README.md b/README.md index e77e543c..f9449074 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ [![DOI](https://zenodo.org/badge/190623784.svg)](https://zenodo.org/badge/latestdoi/190623784) [![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://lupo-lab.com/Luna.jl) +> [!IMPORTANT] +> To stay up-to-date with Luna.jl and learn about bugfixes and new features, subscribe to our [mailing list](https://jiscmail.ac.uk/luna). 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: @@ -34,7 +36,7 @@ There are two ways of using Luna: For a short introduction on how to use the simple interface, see the [Quickstart](#quickstart) or [GNLSE](#gnlse) sections below. More information, including on the internals of Luna, can be found in the [Documentation](http://lupo-lab.com/Luna.jl). ## Installation -Luna requires Julia v1.7 or later, which can be obtained from [here](https://julialang.org/downloads/). In a Julia terminal, to install Luna simply enter the package manager with `]` and run `add Luna`: +Luna requires Julia v1.9 or later, which can be obtained from [here](https://julialang.org/downloads/). In a Julia terminal, to install Luna simply enter the package manager with `]` and run `add Luna`: ```julia ] diff --git a/deps/build.jl b/deps/build.jl new file mode 100644 index 00000000..73979184 --- /dev/null +++ b/deps/build.jl @@ -0,0 +1,9 @@ +# from https://github.com/GeoscienceAustralia/HiQGA.jl/commit/db833bf32840503ee3bd0909b2b92993c239413c#diff-708e1c220f34be9ffbf04c0619b1f1a56388096c1df2b95603950d7adc80feaa +import Pkg, Conda +@info "building!" +Conda.pip_interop(true) +Conda.pip("install", "matplotlib") +Conda.add("matplotlib") +ENV["PYTHON"] = joinpath(Conda.ROOTENV, "bin", "python") +Pkg.build("PyCall") +@info "built PyCall!" diff --git a/docs/src/model/modal_decompositions.md b/docs/src/model/modal_decompositions.md index b0579c57..0bffb20a 100644 --- a/docs/src/model/modal_decompositions.md +++ b/docs/src/model/modal_decompositions.md @@ -5,31 +5,33 @@ ## Multi-mode guided For propagation in waveguides taking into account multiple modes and the coupling between them, Luna uses the model laid out in [Kolesik and Moloney, *Nonlinear optical pulse propagation simulation: From Maxwell’s to unidirectional equations*](https://journals.aps.org/pre/abstract/10.1103/PhysRevE.70.036604) and [Tani et al., *Multimode ultrafast nonlinear optics in optical waveguides: numerical modeling and experiments in kagomé photonic-crystal fiber*](http://josab.osa.org/abstract.cfm?URI=josab-31-2-311). This is implemented in [`NonlinearRHS.TransModal`](@ref). The electric field ``\mathbf{E}(t, \mathbf{r_\perp}, z)`` is expressed as the inverse Fourier transform in time and the superposition of waveguide modes in space. This means that the transverse wave vector ``\mathbf{k}_\perp`` turns into a modal index ``j`` (this transform is implemented in [`Modes.ToSpace`](@ref) and [`Modes.to_space!`](@ref)): ```math -\mathbf{E}(t, \mathbf{r_\perp}, z) = \frac{1}{2\pi} \int_{-\infty}^\infty \mathrm{d} \omega \sum_j \hat{\mathbf{e}}_j(\mathbf{r_\perp}, z) \tilde{E}_j(\omega, z) \mathrm{e}^{-i \omega t}\,, +\mathbf{E}(t, \mathbf{r_\perp}, z) = \frac{1}{2\pi} \int_{-\infty}^\infty \mathrm{d} \omega \sum_j \hat{\mathbf{e}}_j(\mathbf{r_\perp}, z) \tilde{A}_j(\omega, z) \mathrm{e}^{-i \omega t}\,, ``` -where ``\hat{\mathbf{e}}_j(\mathbf{r_\perp}, z)`` is the orthonormal transverse field distribution of the ``j^{\mathrm{th}}`` mode and ``\tilde{E}_j(\omega, z)`` is the frequency-domain field in mode ``j``. The mode fields ``\hat{\mathbf{e}}_j(\mathbf{r_\perp}, z)`` are taken to be independent of frequency but can depend on the propagation coordinate ``z`` (e.g. in tapered waveguides). They can be vector quantities if polarisations other than purely lineary ``x``- or ``y``-polarisations need to be taken into account. The modes are normalised such that ``\vert \tilde{E}_j(\omega, z) \vert^2`` gives the spectral energy density in mode ``j``, and equivalently ``\vert E_j(t, z)\vert^2`` gives the instantaneous power. The inverse transform is simply the overlap integral of the total field with each mode combined with the Fourier transform: +where ``\hat{\mathbf{e}}_j(\mathbf{r_\perp}, z)`` is the orthonormal transverse field distribution of the ``j^{\mathrm{th}}`` mode and ``\tilde{A}_j(\omega, z)`` is the frequency-domain amplitude in mode ``j``. The mode fields ``\hat{\mathbf{e}}_j(\mathbf{r_\perp}, z)`` are taken to be independent of frequency but can depend on the propagation coordinate ``z`` (e.g. in tapered waveguides). They can be vector quantities if polarisations other than purely lineary ``x``- or ``y``-polarisations need to be taken into account. The modes are normalised such that ``\vert \tilde{A}_j(\omega, z) \vert^2`` gives the spectral energy density in mode ``j`` (when also taking into account the normalisation of the FFT), and equivalently ``\vert A_j(t, z)\vert^2`` gives the instantaneous power. The forward transform to reciprocal space is simply the overlap integral of the total field with each mode combined with the Fourier transform in time: ```math -\tilde{E}_j(\omega, z) = \int_S \mathrm{d}^2\mathbf{r_\perp} \int_{-\infty}^\infty \mathrm{d} t\,\, \hat{\mathbf{e}}_j^*(\mathbf{r_\perp}, z) \cdot \mathbf{E}(t, \mathbf{r_\perp}, z) \mathrm{e}^{i \omega t}\,, +\tilde{A}_j(\omega, z) = \int_S \mathrm{d}^2\mathbf{r_\perp} \int_{-\infty}^\infty \mathrm{d} t\,\, \hat{\mathbf{e}}_j^*(\mathbf{r_\perp}, z) \cdot \mathbf{E}(t, \mathbf{r_\perp}, z) \mathrm{e}^{i \omega t}\,, ``` -where ``S`` is the cross-sectional area of the waveguide. This transform is implemented in [`NonlinearRHS.TransModal`](@ref) for use within simulations and in [`Modes.overlap`](@ref) for decomposition of existing sampled fields. +where ``S`` is the cross-sectional area of the waveguide. This transform is implemented in [`NonlinearRHS.TransModal`](@ref) for use within simulations and in [`Modes.overlap`](@ref) for decomposition of existing sampled fields. In both cases, the mode overlap integral is solved explicitly with a p-adaptive or h-adaptive cubature method. The linear operator for a mode ``\mathcal{L}_j(\omega, z)`` is given by (see [`LinearOps.make_const_linop`](@ref)) ```math \mathcal{L}_j(\omega, z) = i\left(\beta_j(\omega, z) - \frac{\omega}{v}\right) - \frac{1}{2}\alpha_j(\omega, z)\,, ``` -where ``\beta_j(\omega, z)`` describes the phase evolution of the mode, ``v`` is a chosen frame velocity (this is the same for all modes) and ``\alpha(\omega, z)`` describes the attenuation of the waveguide (i.e. ``1/\alpha`` is the ``1/\mathrm{e}`` loss-length). This can also be expressed in terms of the *effective index* of the mode: +where ``\beta_j(\omega, z)`` is real-valued and describes the phase evolution of the mode, ``v`` is a chosen frame velocity (this is the same for all modes) and ``\alpha(\omega, z)`` (also real) describes the attenuation of the waveguide (i.e. ``1/\alpha`` is the ``1/\mathrm{e}`` power/energy loss length). This can also be expressed in terms of the *effective index* of the mode: ```math \mathcal{L}_j(\omega, z) = i \left(\frac{\omega}{c} n_\mathrm{eff}(\omega, z) - \frac{\omega}{v}\right)\,, ``` -where ``c`` is the speed of light in vacuum and ``n_\mathrm{eff}`` is complex, ``n_\mathrm{eff} = n + i k``, with ``n`` describing the effective refractive index and ``k`` describing the attenuation. With the modal power normalisation for ``\hat{\mathbf{e}}_j(\mathbf{r_\perp}, z)``, the normalisation factor ``N_{\mathrm{nl}}`` comes out as simply ``N_{\mathrm{nl}}=4``. The propagation equation, coupling the modes through the nonlinear polarisation, is therefore +where ``c`` is the speed of light in vacuum and ``n_\mathrm{eff}`` is complex, ``n_\mathrm{eff} = n + i k``, with ``n`` describing the effective refractive index and ``k`` describing the attenuation. + +With the modal power normalisation for ``\hat{\mathbf{e}}_j(\mathbf{r_\perp}, z)``, the normalisation factor ``N_{\mathrm{nl}}`` comes out as simply ``N_{\mathrm{nl}}=4``. The propagation equation, coupling the modes through the nonlinear polarisation, is therefore ```math -\partial_z \tilde{E}_j(\omega, z) = i \left(\frac{\omega}{c} n_\mathrm{eff}(\omega, z) - \frac{\omega}{v}\right)\tilde{E}_j(\omega, z) + i\frac{\omega}{4} \tilde{\mathbf{P}}_\mathrm{nl}\,, +\partial_z \tilde{A}_j(\omega, z) = i \left(\frac{\omega}{c} n_\mathrm{eff}(\omega, z) - \frac{\omega}{v}\right)\tilde{A}_j(\omega, z) + i\frac{\omega}{4} \tilde{\mathbf{P}}_\mathrm{nl}\,, ``` where ``\tilde{\mathbf{P}}_\mathrm{nl}`` is given by ```math \tilde{\mathbf{P}}_\mathrm{nl} = \int_S \mathrm{d}^2\mathbf{r_\perp} \int_{-\infty}^\infty \mathrm{d} t\,\, \hat{\mathbf{e}}_j^*(\mathbf{r_\perp}, z) \cdot \mathbf{P}_\mathrm{nl}\left[\mathbf{E}(t, \mathbf{r_\perp}, z)\right] \mathrm{e}^{i \omega t} ``` -and ``\mathbf{E}(t, \mathbf{r_\perp}, z)`` is obtained from the set of ``\tilde{E}_j(\omega, z)`` as above. +and ``\mathbf{E}(t, \mathbf{r_\perp}, z)`` is obtained from the set of ``\tilde{A}_j(\omega, z)`` as above. The transverse coordinate ``\mathbf{r_\perp}`` for circular waveguides (e.g. hollow capillaries, optical fibres, and anti-resonant fibres) is in polar coordinates, ``\mathbf{r_\perp} = (r, \theta)``. For other waveguides (e.g. rectangular), it is Cartesian, ``\mathbf{r_\perp} = (x, y)``. @@ -49,6 +51,87 @@ The modules and functions that define and implement this decomposition for diffe ## Single-mode guided +In some situations, inter-mode coupling in a waveguide is negligible, so including several waveguide modes in the simulation unnecessarily slows down the computation. Simulating propagation in a single mode is trivially achieved by including only that single mode in both the forward and inverse transforms as defined above for [multi-mode propagation](#multi-mode-guided). For example, setting `modes=1` when calling `prop_capillary` achieves this and leads to a significant speed-up. However, in this simple implementation, the overlap integral between the nonlinear polarisation and the waveguide mode still needs to be calculated explicitly. We can make this unnecessary by making an assumption about the nonlinear polarisation. + +If the nonlinear polarisation is *only due to third-order effects* like the Kerr effect or Raman scattering, we can express it as +```math +P_\mathrm{nl}\left(t, \mathbf{r}_\perp, z \right) = C\, E(t, \mathbf{r}_\perp, z)^3\,, +``` +where ``C`` is a constant which depends on the specific effect (e.g. for the Kerr effect, ``C`` becomes ``\varepsilon_0 \chi^{(3)}`` with ``\chi^{(3)}`` the third-order susceptibility of the nonlinear medium) and we have switched to *explicitly real-valued* and *scalar* fields to make the notation simpler; the same result can be obtained with vector fields and more algebra. Expanding the field in terms of its modal content as above, this turns into +```math +P_\mathrm{nl}\left(t, \mathbf{r}_\perp, z \right) = C\, \Big[\sum_j \hat{e}_j(\mathbf{r_\perp}, z) A_j(t, z)\Big]^3\,, +``` +where we have simply carried out the time-domain inverse Fourier transform to obtain ``A_j(t, z)``. For a single mode, this simplifies greatly to +```math +P_\mathrm{nl}\left(t, \mathbf{r}_\perp, z \right) = C\, \hat{e}_0(\mathbf{r_\perp}, z)^3 A(t, z)^3\,. +``` +Now we can explicitly calculate the overlap integral with the single mode we are considering: +```math +\begin{align*} +P_\mathrm{nl}(t, z) &= CA(t, z)^3\times\int_S \mathrm{d}^2\mathbf{r_\perp} \, \hat{e}_0^*(\mathbf{r_\perp}, z) \hat{e}_0(\mathbf{r_\perp}, z)^3\\[1em] +&= CA(t, z)^3\int_S \mathrm{d}^2\mathbf{r_\perp} \, \hat{e}_0(\mathbf{r_\perp}, z)^4\,, +\end{align*} +``` +where ``A`` is now the modal amplitude in the single mode. In the second step we have made use of the fact that we are considering real-valued fields and hence ``\hat{e}_0(\mathbf{r_\perp}, z)`` is also real. + +The mode normalisation in Luna is chosen such that the absolute value squared of the modal field amplitudes ``A_j(t, z)`` is the instantaneous power. For this to be fulfilled, we need +```math +\frac{1}{2} c \varepsilon_0 \int_S \mathrm{d}^2\mathbf{r_\perp} \left\vert \hat{e}_j(\mathbf{r_\perp}, z) \right\vert^2 = 1\,. +``` +This, in turn, means that the *effective area* of the mode, +```math +A_{\mathrm{eff}, j}(z) = \frac{\left(\int_S \mathrm{d}^2\mathbf{r_\perp} \,\left\vert \hat{e}_j(\mathbf{r}_\perp, z)\right\vert^2\right)^2}{\int_S \mathrm{d}^2\mathbf{r_\perp} \,\left\vert \hat{e}_j(\mathbf{r}_\perp, z)\right\vert^4}\,, +``` +for the single mode we are considering is +```math +A_\mathrm{eff} = \Big(\frac{1}{4} c^2 \varepsilon_0^2 \int_S \mathrm{d}^2\mathbf{r_\perp} \, \hat{e}_0(\mathbf{r_\perp}, z)^4 \Big)^{-1}\,. +``` +Note that ``A_\mathrm{eff}`` is **independent of the normalisation**, because the overall power of ``\hat{e}_j`` and any constants inside it is the same in the numerator and denominator. From this we can see that we can replace the integral expression in the projection of ``P_\mathrm{nl}`` with +```math +\int_S \mathrm{d}^2\mathbf{r_\perp} \, \hat{e}_0(\mathbf{r_\perp}, z)^4 = \frac{4}{\varepsilon_0^2c^2 A_\mathrm{eff}}\,. +``` +We can now write down the **single-mode UPPE:** +```math + +\partial_z \tilde{A}(\omega, z) = i \left(\frac{\omega}{c} n_\mathrm{eff}(\omega, z) - \frac{\omega}{v}\right)\tilde{A}(\omega, z) + + i\frac{\omega}{4} C\frac{4}{\varepsilon_0^2c^2 A_\mathrm{eff}} \int_{-\infty}^\infty \mathrm{d} t\, A(t, z)^3 \mathrm{e}^{i \omega t}\,. +``` +Crucially, the effective area depends on the mode shape ``\hat{e}_0(\mathbf{r_\perp}, z)``, but only needs to be calculated *once* (assuming the cross-section of the waveguide does not change along its length). When only third-order nonlinear effects are present, the single-mode UPPE is exactly equivalent to explicitly solving the projection integral, but *much* faster. However, it has two important drawbacks: + +1. For obvious reasons, inter-modal coupling mediated by the nonlinearity is completely ignored. +2. The equation only works for third-order nonlinear effects, and hence photoionisation and plasma dynamics cannot be modelled in this way. + +We can derive a different single-mode equation which can treat other nonlinear effects approximately. As written above, in the single-mode UPPE only the modal amplitude ``A`` appears. We now define a re-scaled **mode-averaged field** ``E_\mathrm{av}`` through +```math +A(t, z) = \sqrt{\frac{1}{2}\varepsilon_0 c A_\mathrm{eff}}E_\mathrm{av}(t, z)\,. +``` +Note that, because ``\vert A(t, z)\vert^2`` has units of power, ``E_\mathrm{av}`` is in fact an electric field (with units of ``\mathrm{V/m}``). We can also define the *mode-averaged intensity* by +```math +I_\mathrm{av} = \frac{1}{2}\varepsilon_0 c \vert E_\mathrm{av}\vert^2 = \frac{\vert A(t, z)\vert^2}{A_\mathrm{eff}}\,. +``` +Plugging in the definition of ``E_\mathrm{av}``, the UPPE reads +```math +\begin{align*} +\sqrt{\frac{1}{2}\varepsilon_0 c A_\mathrm{eff}}\partial_z \tilde{E}_\mathrm{av}(\omega, z) &= i \left(\frac{\omega}{c} n_\mathrm{eff}(\omega, z) - \frac{\omega}{v}\right)\sqrt{\frac{1}{2}\varepsilon_0 c A_\mathrm{eff}}\tilde{E}_\mathrm{av}(\omega, z)\\[1em] +&\qquad + i\frac{\omega}{4} C\Big(\frac{1}{2}\varepsilon_0 c A_\mathrm{eff}\Big)^{\frac{3}{2}}\frac{4}{\varepsilon_0^2c^2 A_\mathrm{eff}} \int_{-\infty}^\infty \mathrm{d} t\, E_\mathrm{av}(t, z)^3 \mathrm{e}^{i \omega t}\,. +\end{align*} +``` +Cancelling the various constants, we arrive at the **mode-averaged field UPPE** +```math +\partial_z \tilde{E}_\mathrm{av}(\omega, z) = i \left(\frac{\omega}{c} n_\mathrm{eff}(\omega, z) - \frac{\omega}{v}\right)\tilde{E}_\mathrm{av}(\omega, z) + i\frac{\omega}{4} \frac{2}{\varepsilon_0 c} \int_{-\infty}^\infty \mathrm{d} t\, P_\mathrm{nl}\left[E_\mathrm{av}(t, z)\right] \mathrm{e}^{i \omega t}\,. +``` +This now includes only a single inverse Fourier transform to obtain ``E_\mathrm{av}(t, z)`` followed by the calculation of ``P_\mathrm{nl}`` and then a forward transform. This equation is **still only valid for third-order responses** but we have now written it for an arbitrary polarisation ``P_\mathrm{nl}\left[E_\mathrm{av}(t, z)\right]``. Because ``E_\mathrm{av}`` is (a version of) the actual electric field, we can calculate arbitrary polarisation contributions, including the photoionisation and plasma term. However, this is still a significant approximation. For example, the mode-averaged intensity is approximately half of the on-axis intensity for the fundamental mode of a capillary fibre. Due to the exponential scaling of strong-field ionisation with intensity, this means that the peak ionisation fraction can be underestimated significantly. Only fully mode-resolved (and multi-mode) propagation can accurately model that situation. + +The mode-averaged field UPPE as written above is very useful, but the scaling from ``A`` to ``E_\mathrm{av}`` changes the normalisation: ``\vert E_\mathrm{av}(t, z) \vert^2`` no longer gives the instantaneous power. To remain consistent with modal propagation simulations (e.g. for data analysis), Luna internally uses the same normalisation for both, which leads to a "hybrid" equation. The propagating quantity (and hence the simulation output) is ``A(z, t)`` and we switch to ``E_\mathrm{av}(z, t)`` to calculate the nonlinear polarisation. This leads to the appearance of an additional factor of in the equation +```math +\begin{align*} +\left(\frac{1}{2}\varepsilon_0 c A_\mathrm{eff}\right)^{-\frac{1}{2}}\partial_z \tilde{A}(\omega, z) &= i\left(\frac{1}{2}\varepsilon_0 c A_\mathrm{eff}\right)^{-\frac{1}{2}} \left(\frac{\omega}{c} n_\mathrm{eff}(\omega, z) - \frac{\omega}{v}\right)\tilde{A}(\omega, z) + i\frac{\omega}{4} \frac{2}{\varepsilon_0 c} \int_{-\infty}^\infty \mathrm{d} t\, P_\mathrm{nl}\left[E_\mathrm{av}(t, z)\right] \mathrm{e}^{i \omega t}\\[1em] + +\Rightarrow \partial_z\tilde{A}(\omega, z) &= i \left(\frac{\omega}{c} n_\mathrm{eff}(\omega, z) - \frac{\omega}{v}\right)\tilde{A}(\omega, z) + i\frac{\omega}{4}\sqrt{\frac{2A_\mathrm{eff}}{\varepsilon_0 c} }\int_{-\infty}^\infty \mathrm{d} t\, P_\mathrm{nl}\left[E_\mathrm{av}(t, z)\right] \mathrm{e}^{i \omega t}\,. +\end{align*} +``` + + ## Radially symmetric free-space diff --git a/docs/src/model/model.md b/docs/src/model/model.md index 1513f089..362cad43 100644 --- a/docs/src/model/model.md +++ b/docs/src/model/model.md @@ -14,7 +14,7 @@ P_{\mathrm{nl}}(\omega, \mathbf{k}_\perp, z) = \int_{-\infty}^{\infty} \mathcal{ ``` where ``\mathcal{T}_\perp`` is a transform from (transverse) real space to reciprocal space (i.e. spatial frequency), ``\mathbf{r}_\perp`` is the transverse spatial coordinate, ``t`` is time, and ``\mathcal{P}`` is an operator which calculates the nonlinear response of the medium given an electric field. Naturally, the real-space field ``E(t, \mathbf{r}_\perp, z)`` first has to be obtained from ``E(\omega, \mathbf{k}_\perp, z)``: ```math -E(t, \mathbf{r}_\perp, z) = \mathcal{T}_\perp^{-1}\Big[E(\omega, \mathbf{k}_\perp, z)\Big]\,, +E(t, \mathbf{r}_\perp, z) = \int_{-\infty}^{\infty} \mathrm{d}\omega \mathcal{T}_\perp^{-1}\Big[E(\omega, \mathbf{k}_\perp, z)\Big]\mathrm{e}^{-i\omega t}\,, ``` where ``\mathcal{T}_\perp^{-1}`` is simply the inverse of ``\mathcal{T}_\perp`` so transforms from transverse reciprocal space to real space. The chief difference between variations of the UPPE implemented in `Luna` is the definition of ``\mathbf{k}_\perp`` and ``\mathcal{T}_\perp``, that is, the choice of [Modal decompositions](@ref) of the field. diff --git a/test/Chang_PPT.csv b/examples/low_level_interface/PPT_ionisation_rate/Chang_PPT.csv similarity index 100% rename from test/Chang_PPT.csv rename to examples/low_level_interface/PPT_ionisation_rate/Chang_PPT.csv diff --git a/examples/low_level_interface/PPT_ionisation_rate/Couairon_PPT.csv b/examples/low_level_interface/PPT_ionisation_rate/Couairon_PPT.csv new file mode 100644 index 00000000..11d04d27 --- /dev/null +++ b/examples/low_level_interface/PPT_ionisation_rate/Couairon_PPT.csv @@ -0,0 +1,236 @@ +883228247738.8362, 1.2067824525304158 +938091830530.8828, 1.828854171807098 +962562225005.7161, 2.240953346697669 +1018896525567.091, 3.2724584560426333 +1039958436902.2006, 4.292909424846475 +1065965810269.6982, 5.21626547757313 +1098358329572.3942, 6.32166098578537 +1127009321644.4019, 7.719040575484485 +1158270337978.6157, 9.476815030307451 +1180540893554.6536, 11.24913006343858 +1205824465826.8115, 12.799212747030117 +1253312241848.984, 17.72938054589913 +1286005251168.579, 21.52234836528084 +1319551067013.7007, 26.464580293021488 +1358904253446.9607, 34.297669219061774 +1396253850526.0603, 41.16309211115487 +1432874477420.7034, 49.437182130608285 +1458015398393.5798, 57.33700176858866 +1496048148267.1987, 70.50344420192313 +1537545573977.5027, 87.2123663996103 +1577652891293.0913, 106.22810288557453 +1607712638062.4807, 124.1142108581166 +1663375849639.6084, 157.32449221217075 +1685268388603.491, 178.83521028300785 +1732014410674.3708, 220.61614304038463 +1771483162481.1165, 263.90879628125566 +1829432348555.6606, 337.3142008110075 +1880177194611.6748, 415.58065348889335 +1947942075752.72, 545.5495195641647 +1998754703532.2534, 663.811364450805 +2054196217843.8213, 821.9838214919017 +2131661048009.9128, 1087.9089846356055 +2187266037877.3965, 1322.196075181216 +2236594667027.3154, 1645.4632513985487 +2244321499854.9043, 1618.2380037997134 +2302865269923.469, 1966.7343201965869 +2362936171026.5415, 2384.7045804167633 +2428479361149.7485, 2934.9751809886156 +2495840590128.269, 3605.664334782401 +2560945307810.5205, 4387.2845195348045 +2627748300727.677, 5307.258953443135 +2700636857169.6104, 6514.1333744519025 +2780017842067.696, 8042.277256052215 +2857130055339.8164, 9971.515972721058 +2936381209357.4526, 12182.003940741086 +3022691529760.6743, 14884.444745265551 +3106535032622.9937, 18431.09869416096 +3187569890004.8706, 22114.405338913497 +3275986793471.5723, 27037.774953638902 +3372279287402.5923, 33311.30488163015 +3471402148173.316, 41168.48540227898 +3573438569977.96, 50144.83935590226 +3684399203196.488, 62340.36333684851 +3780507813218.128, 74473.16365445234 +3854421759979.0894, 85798.22832140872 +3929391762227.303, 97255.26954469187 +4016461130531.8906, 114678.95426009085 +4190674997341.317, 147669.15491481457 +4327761372386.9106, 181875.9317237588 +4476531015730.467, 222074.2126524178 +4637873038745.355, 270903.90579190507 +4805030111024.784, 326009.59012331517 +4978211817135.176, 376651.2437469236 +5157635295438.106, 407264.0328370843 +5309235896018.724, 373782.97474853357 +5391035622560.954, 302209.5157482758 +5421489031582.728, 247211.578561548 +5513251484768.778, 195912.64911894183 +5540572320633.101, 180142.48399500697 +5717214285982.084, 230558.6391972808 +5847496869249.731, 278300.461150998 +5990381644083.382, 341636.8437726443 +6136757837439.507, 420929.6808267645 +6286710762839.507, 515950.6973996832 +6450701416322.549, 648664.9925489479 +6618969813039.582, 808876.6135546827 +6780705686100.124, 991638.5253633689 +6946393608100.866, 1220165.0375803972 +7127592277821.179, 1503863.232511387 +7313517566814.854, 1881879.3490118852 +7504292770301.385, 2341203.606965848 +7687661689814.441, 2880742.038567 +7875511266155.907, 3515172.6491905833 +8080946236487.934, 4362944.190776383 +8291740036946.51, 5453256.017561368 +8508032454152.642, 6736899.411428874 +8729966921101.339, 8381224.444640966 +8957690612278.46, 10366182.537953058 +9176573603545.348, 12725346.805357538 +9385687282832.602, 15422015.76508809 +9615028459758.941, 18805877.43062855 +9898409351105.738, 23980746.61276133 +10123192481328.031, 29408448.49089746 +10420747847702.992, 36721658.71159234 +10692576044321.773, 45471667.066515006 +10971494957418.621, 56372418.617272615 +11257689550366.672, 69968033.11577833 +11551349611361.746, 86034971.5983765 +11852669879279.193, 105298346.67467158 +12161850172813.73, 130541122.88798074 +12459027454368.453, 159849488.30454293 +12763466323216.008, 193950083.75169462 +13096405003236.912, 238487337.77421167 +13438028484223.87, 292567626.83448535 +13788563311701.11, 359750679.6233364 +14148241940698.008, 443913745.9814788 +14517302889899.893, 542672632.8421766 +14919984246703.395, 677496326.4738 +15333835190333.52, 838929625.6510991 +15733822679519.023, 1032780645.7821115 +16144243957089.496, 1266977884.0129855 +16592053451078.25, 1569075989.1802812 +17052284297435.035, 1933647853.6939905 +17497098024486.785, 2372129140.9795766 +17953514845195.74, 2879614976.2494416 +18421837429588.24, 3512039080.893489 +18902376342924.863, 4288362224.589621 +19395450251650.156, 5205800928.86984 +19933441797489.094, 6378833913.403535 +20519354056700.4, 7899802560.711339 +21088520477966.723, 9678624009.534088 +21717915130434.184, 11481640237.529877 +22310532243640.918, 14620756157.707417 +22966315348429.42, 17864206761.220123 +23641374169091.92, 21895264044.095078 +24375474335244.99, 27093458721.123024 +25172850752828.22, 33431927814.787815 +25996311140822.582, 41061004460.380066 +26889951432855.312, 51064631778.35722 +27859112407660.168, 61765871514.013245 +28863203642470.51, 73140011882.59663 +29903484085072.91, 82165807418.53871 +30832032166855.195, 79210603444.50264 +31789413060818.453, 63900409246.72908 +32776522069074.19, 76841526132.86562 +33631507048625.105, 94376557187.00084 +34564378739213.652, 116591880248.842 +35523126451053.44, 144880023835.17282 +36508467933951.234, 178820865008.01172 +37521140846678.78, 218291889881.98996 +38499890567955.27, 265612253470.3574 +39504171256448.875, 322060153708.1349 +40599939072384.73, 396067043152.6097 +41793310606885.04, 483930069954.2473 +43091055655085.83, 601999600470.9298 +44429097635634.73, 743762353855.357 +45735021280676.98, 911592288501.2854 +47155162363890.664, 1113240815018.6394 +48697713451067.36, 1364726864597.0066 +50371729377172.93, 1682858534894.2856 +52187214781365.39, 2037252481154.3464 +54068133461198.336, 2377827827590.835 +56016843746599.164, 2449576956688.4487 +58035788965838.66, 2154719361801.6716 +59934269744540.26, 2661197177972.608 +61695943606333, 3265083962316.899 +63509399108388.516, 4026854144163.1064 +65481461311891.98, 4982359695579.676 +67514759007922.3, 6125040867605.011 +69611193650469.29, 7536829108531.77 +71888331869537.55, 9179202229632.73 +74359540555171.34, 11156703822409.42 +77039588713325.95, 13540658328024.166 +79816230503937.17, 15498553494171.291 +82692947330800.95, 14104115596078.113 +85398019043259.6, 15510259249726.404 +88049755762132.72, 19077365249252.258 +90930060688842.25, 23540482759113.344 +93904586847615.61, 28858382475319.926 +97132618777189.62, 35507064964421.46 +100633448584729.88, 42437938487421.31 +104260454433806.44, 50213032107199.44 +108018183930082.27, 51904216085821.24 +111911348582898.61, 54202004168375.57 +115944829712648.6, 64019926058381.836 +120123684571070.12, 75040393252794.16 +124453152682113.38, 87659608729124.5 +128938662411356.02, 102400942605095.9 +133585837772183.9, 119316842196204.48 +138400505477281.56, 137500460004930.61 +143388702244270.06, 158859496302060.44 +148556682364653.03, 183224885090431.16 +153910925545560.62, 211327363474501.2 +159458145034123.8, 240858922148871.62 +165205296034665.94, 277565278984689.06 +171159584429260.53, 316890993238757.6 +177328475812604.38, 357511799211017.56 +183719704852505.75, 409205786797068.3 +190341284987758.8, 461660003618831.56 +197201518475534.84, 521723606682060 +204309006800903.4, 586604601343508.4 +211672661461530.28, 661236886950626 +219301715141074.66, 742205719396689.4 +226986436246167.62, 829065654036980 +235394626095379.72, 927192645964519.6 +243878660953535.1, 1026680175279855 +252668475296413.03, 1148489141750925.5 +261775089952531.6, 1279305384543990.2 +271209922960375.53, 1409373708707700.5 +280984803884551.2, 1559274177296933 +291111988647903.75, 1722189965698847.8 +301604174898216.44, 1894066570532734.5 +312474517928752.94, 2088411391755974 +323736647172595.75, 2290992186966018.5 +335404683291490.6, 2513223793673231 +347493255880568.7, 2736017226527347 +360017521811219.5, 2988697765112144.5 +372993184235029.94, 3250878864405907 +386436512272696.25, 3539064287374274 +400364361412534.75, 3852796905878219 +414794194644200.56, 4162400752507259.5 +429744104354100, 4500704948924165 +445232835009952.8, 4854120628019255 +461279806662947.2, 5244189052374359 +477905139296942.7, 5670416911898259 +495129678055291.4, 6079430195946332 +512975019376827.1, 6523484547824414 +531463538073904.3, 7029764609782309 +550618415386311.94, 7524036865832765 +570463668046355, 8018934449908906 +591024178391458.8, 8590049606709979 +612325725562095.6, 9155064753932340 +634395017824142.1, 9715894396700322 +657259726056173, 10346171133276298 +680948518443739.1, 11007980535390562 +705491096424018.6, 11692244675064854 +730918231926060.1, 12387438375357856 +757261805953143.5, 13101691399283372 +784554848555790.6, 13857127867818520 +812831580245427.1, 14656122396194894 +842127454900682.2, 15409296274738086 +872479204220105, 16242510552215996 +904409948277050.6, 17281081018221368 +928665780078636.1, 18913818832184800 +960932894126686, 19797092691492988 +982830415110050, 20062289081971936 diff --git a/examples/low_level_interface/PPT_ionisation_rate/Gonzalez_PPT_Ar.csv b/examples/low_level_interface/PPT_ionisation_rate/Gonzalez_PPT_Ar.csv new file mode 100644 index 00000000..e5f0f048 --- /dev/null +++ b/examples/low_level_interface/PPT_ionisation_rate/Gonzalez_PPT_Ar.csv @@ -0,0 +1,213 @@ +1023460805331.7522, 1.7608272007498226e-7 +1067759921349.7925, 2.775252037976209e-7 +1111730986698.9624, 4.2991117609028947e-7 +1157512809831.1099, 6.646099414823494e-7 +1205179958958.8315, 0.0000010358759722884319 +1254810073063.4075, 0.0000016079490061096029 +1306483988351.1423, 0.000002490855745173668 +1360285869917.1826, 0.000003858556658009906 +1416303348831.421, 0.000005989483788834434 +1474627664869.6013, 0.000009335343870160034 +1535353815122.279, 0.000014431730758173258 +1598580708723.5005, 0.000022356032022911562 +1664411327951.4124, 0.00003477342288907557 +1732952895963.0054, 0.0000540879051427036 +1804317051436.3516, 0.00008344494875186961 +1878620030404.7205, 0.000129793448494449 +1955982855578.718, 0.00020147319958115374 +2036531533464.8328, 0.00031273882172260355 +2120397259601.4875, 0.0004844601961049252 +2207716632246.8613, 0.0007520082632148176 +2298631874866.428, 0.001167312469610787 +2393291067782.8154, 0.001815682233227582 +2491848389365.011, 0.00280117173149186 +2599704674945.0596, 0.004446248977310127 +2712229373895.9907, 0.006917150795766919 +2823920787533.9536, 0.010781226489103174 +2940211728041.023, 0.01683826355459987 +3061291606999.82, 0.026137365738310398 +3187357636088.154, 0.04040637713077961 +3318615148292.2427, 0.06233756068072518 +3455277932347.5024, 0.09636912194966536 +3597568580952.0444, 0.14837119206223215 +3753284468771.307, 0.23415341731869246 +3915740308081.0635, 0.3716674635055036 +4076993104270.822, 0.5710551081526222 +4244886398101.6743, 0.8846150540521316 +4419693649693.19, 1.3453545985140793 +4601699580434.887, 2.0755665287814096 +4791200636732.976, 3.189041239693206 +4988505472854.215, 4.919942491980545 +5193935453653.902, 7.528476330063487 +5407825178006.223, 11.614672816119526 +5630523023790.134, 17.77272590484853 +5862391715317.796, 27.363127061331983 +6103808914130.435, 41.870947624599545 +6355167834123.202, 64.33336482751929 +6616877882001.68, 98.24153118341212 +6889365324112.422, 150.63656111940685 +7187562179520.639, 235.74784573423466 +7498666082297.146, 365.94198956406854 +7823235696521.421, 568.0371725520432 +8161853867291.893, 881.7414743394125 +8515128667368.145, 1368.6921651223624 +8883694489116.453, 2137.6417075311383 +9268213183718.178, 3297.8796694783077 +9688905486115.174, 5171.78369522541 +10128693380055.896, 8110.467715891697 +10588443631141.5, 12718.95547976016 +11069062348221.842, 19946.054181208852 +11571496769220.043, 31279.697301623175 +12096737128017.025, 49053.28414293288 +12645818606075.518, 76926.08601690162 +13219823372650.215, 120636.62633956998 +13819882717604.906, 186044.81986190405 +14447179281039.508, 284790.9191740432 +15102949384122.635, 429511.7061531255 +15788485465721.86, 647774.5366938054 +16505138629635.316, 986075.2187570177 +17254321307444.412, 1470666.3194138706 +18037510042235.07, 2152998.0625760695 +18856248398675.562, 3065216.9428778025 +19712150005184.234, 4158003.1254992713 +20606901734184.61, 5054383.723898055 +21520544356374.586, 3740475.6021328853 +21804655698926.5, 3336839.109220826 +22748440714931.16, 5112984.402362839 +23685236668092.125, 7888135.439866093 +24660610503089.2, 12070393.311337486 +25728011658131.906, 18851701.519247793 +26895828609037.88, 29563517.26438972 +28116653792719.4, 45762466.38285971 +29392893299223.086, 71765376.9124979 +30727062433127.14, 111709645.49783716 +32121790670884.816, 175184718.99749976 +33579826843184.82, 274216957.0293472 +35104044552545.484, 415105126.2172083 +36697447836816.28, 631894054.1117252 +38363177089752.01, 938930504.4259857 +40104515250327.03, 1392564013.3437135 +41924894272986.4, 2027319873.1064718 +43827901891589.664, 2780933714.519893 +45817288690373.61, 3405680319.1443505 +47896975495873.75, 2913246012.9572754 +50071061104371.59, 4063616067.478378 +52343830360096.695, 6255234662.578858 +54719762600105.31, 9646771808.07152 +57203540482480.29, 14821954728.626257 +59800059215251, 22353953849.65231 +62514436204219.664, 33092426133.243496 +65352021138714.58, 47553609951.06865 +68318406535140.69, 65473683210.67929 +71419438759113.78, 78413435860.63426 +74661229547899.05, 74159476546.76476 +78050168055860.25, 108566577760.80304 +81592933446666.98, 165881228218.29282 +85296508057068.42, 244201944426.4896 +89168191158185.48, 357502110633.14874 +93215613341440.34, 486762514650.00977 +97446751557472.03, 575424846521.6549 +101869944837687.3, 592799714181.5641 +106493910729420.6, 874313777010.8113 +111327762477104.19, 1279961227872.5967 +116381026983306.44, 1795373064389.7402 +121663663585035.64, 2282008020086.9287 +127186083682316.33, 2324833075894.549 +132959171257725.9, 3273140053303.394 +138994304327331.77, 4659959765961.487 +145303377365299.56, 6181818213195.618 +151898824746382.56, 6646717280735.363 +158793645252477.5, 8654991779620.878 +166001427691560, 12185391092643.627 +173536377679486.5, 15259568255586.832 +181413345637438.66, 16653016420425.365 +189647856060206.7, 23099683824722.65 +198256138112974.78, 29470222757461.363 +207255157616928.88, 31982447812438.332 +216662650486719.3, 43789763393337.85 +226497157685670.8, 53131066827055.14 +236778061767653, 62808731269030.77 +247525625077603.84, 81482538826542.8 +258761029686023.47, 90591995430640.56 +270506419136113.7, 118844509592275.81 +282784942085873.44, 134861028282270.17 +295620797931130.56, 172694605505530.62 +309039284499454.44, 192716529897670.12 +323066847908936.94, 244952247280868.1 +337731134690094.75, 273860303129051 +353061046273649.8, 339146307137557.4 +369086795951537.75, 395736720544275 +385839968423439.3, 464352717227047.7 +403353582046182.7, 555091070872534.7 +421662153908683.8, 645310270835474.2 +440801767860719.94, 743251182469638.9 +460810145629563.56, 867271084192846.2 +481726721164679.2, 1021436632946814.9 +503592718356990.56, 1169919764472256.2 +526451232285884.94, 1355019334565648 +550347314154089.9, 1543361839513516.2 +575328060077826.1, 1761154612546718.8 +601442703907227.4, 2043592780091737.2 +628742714259934.9, 2297546970888453 +657281895959169.4, 2616897212389777.5 +687116496076127.6, 2920295908458456.5 +718305314785761.2, 3313860357995107.5 +750909821254398.6, 3753479832610182.5 +784994274787576.6, 4243522495072199.5 +820625851476924.2, 4770858631636209 +857874776595612.2, 5343818082168092 +896814463003379.2, 5886262612401974 +937521655833881.4, 6580930288348124 +980076583749513.2, 7316654772877460 +1024563117061816.2, 8089384053819750 +1071068933029129, 8977042208756456 +1119685688657224.4, 9869923239255830 +1170509201343539.5, 10791253633430972 +1223639637720984, 11667700149862414 +1279181711073530.5, 12875991738432414 +1337244887712694.2, 14077930421864390 +1397943602721624.2, 15306452802905338 +1461397485491957.8, 16642183217761720 +1527731595498068.8, 18331511169951730 +1597076668773233, 19968317387823600 +1669569375573617.2, 21470019931279160 +1745352589737860.8, 22956255059782564 +1824575670273097.8, 24682663246409024 +1907394755722410.8, 26737009995674852 +1993973071893918.8, 28747747061552520 +2084481253557921, 30794975987689580 +2179097680746193.5, 32865557425639000 +2278008830316168, 35271547830145724 +2381409643472909, 37573200521948720 +2489503909973284, 40025048071635010 +2602504669769449, 42636891478032810 +2720634632883333, 45250593964219336 +2844126618339546, 47846272825717860 +2973224013021824, 50779317846219656 +3108181251357422, 53492853913775010 +3249264316774815, 56246721579542910 +3396751265922872.5, 59583841975538730 +3550932776685000.5, 62534904013847330 +3712112721067917.5, 65632125931499624 +3880608764094492, 69010935926002850 +4056752989880841, 72294363899136220 +4240892556131557, 76016152822195000 +4433390378343032.5, 79484957931834030 +4634625845063465, 83112053200102240 +4844995565619049, 86743234634506830 +5064914151780208, 90364896084736240 +5294815034908248, 94312955606196740 +5535151320193000, 98068161781191680 +5786396679665171, 101972886899009310 +6049046285743189, 105836125676722720 +6323617787154683, 109845723102381540 +6610652329155767, 113795453247064300 +6910715620058963, 117887204107485580 +7224399046171866, 122353356161759540 +7552320837343855, 126047797553900580 +7895127285417881, 129853792054417740 +8253494017988834, 133774707992826540 +8628127329979067, 138327430111419410 +9019765575655152, 141975289103431680 +9429180623829654, 145178496009892060 +9837309947624062, 150342773794420100 diff --git a/examples/low_level_interface/PPT_ionisation_rate/Ilkov_PPT_He.csv b/examples/low_level_interface/PPT_ionisation_rate/Ilkov_PPT_He.csv new file mode 100644 index 00000000..cf6f2677 --- /dev/null +++ b/examples/low_level_interface/PPT_ionisation_rate/Ilkov_PPT_He.csv @@ -0,0 +1,126 @@ +99338555.87471054, 2.2916210093724653e-19 +99789522.90608676, 5.076651607059269e-19 +101562830.39463875, 1.3211329693124005e-18 +101792077.05106239, 2.30384912227832e-18 +102425176.98978277, 6.2292817030926825e-18 +103716204.61656256, 9.616175662824402e-18 +105056397.8979229, 2.189534223947162e-17 +105785662.52468514, 3.7631175747067836e-17 +106547284.41980602, 9.576504769697469e-17 +108059328.55479607, 1.5618477299962916e-16 +110043012.89788914, 6.079872404096058e-16 +110212307.62727128, 3.5551356570899976e-16 +110835286.31214832, 1.7345316584754183e-15 +112408182.75461338, 2.859966648181426e-15 +113043573.69569288, 6.800905809161486e-15 +114583204.22810882, 1.0763263233201672e-14 +116165628.20487277, 2.5247356237075534e-14 +117382308.7028188, 4.503966712010054e-14 +119261816.57669681, 9.698390551887921e-14 +120003570.26670662, 2.0589914565491824e-13 +121895376.51657885, 4.651664846624883e-13 +123017006.52101775, 8.810806905871759e-13 +124481798.23731455, 1.555477192702726e-12 +126652248.80873829, 5.11097524666931e-12 +129216131.18867718, 1.5063880843201532e-11 +131794391.21597005, 4.5077843064179764e-11 +134500652.335736, 1.0709601938810896e-10 +135260921.68596622, 2.1303136100108763e-10 +137309382.40192986, 5.402618953960454e-10 +139946503.05041656, 1.3261414105979192e-9 +140440388.03059378, 2.869803068641755e-9 +142701285.04192263, 4.812261759825784e-9 +145578658.51242214, 1.3624182697247453e-8 +146092419.923436, 2.8492995171223668e-8 +148355589.96643105, 4.511605136646194e-8 +149423701.30482385, 9.482361106633048e-8 +151307126.18286395, 1.481683471417687e-7 +152830944.43458912, 3.1556939386771255e-7 +155364272.62964022, 5.756838796170669e-7 +158088037.20069513, 0.0000015537345610968777 +162377846.7836308, 0.000004929317058622502 +165146978.89499545, 0.000012201086074358921 +165846609.4095826, 0.000019021662111400923 +169150944.94521213, 0.00003969750474585561 +171068612.0406943, 0.00010231966882951469 +174406757.9948885, 0.00021232249199094826 +177853001.47029778, 0.0004914775018045124 +182353366.2672894, 0.0017118334639078428 +185637466.21022266, 0.004520225670047086 +189530254.2860336, 0.011786704412533853 +194931136.0401878, 0.03546387538884439 +198069259.11908582, 0.08705058441879297 +202481963.22602305, 0.21615336400102816 +205543438.32988197, 0.4816447122471049 +206775373.55837566, 0.9402263008640845 +213488671.79119796, 2.0809894754114913 +218603057.84302607, 5.356634053363343 +223657793.94517684, 12.428446518057918 +227400751.1001276, 27.54630399725447 +232658915.36179778, 57.58722517288431 +236923174.80746052, 120.43529988089975 +242173911.2603047, 278.41735898694486 +246072578.0175552, 492.7188522686176 +252251761.07137498, 1086.3531473806906 +255666790.95245042, 2067.142285048689 +259608412.08970687, 3695.247781967413 +264780862.1293555, 7749.219805078524 +270709530.20604503, 15924.154984813154 +279170894.96630436, 35634.5028256485 +287330608.2296332, 78800.36295321978 +293055396.59056044, 149597.54255198612 +300100066.2852527, 289552.2751830508 +308208982.4546268, 549488.2300138037 +315632044.10033023, 1088054.5235278269 +325002815.3994574, 2468840.3130181306 +333724899.3204849, 4905601.580739131 +344402309.72503895, 10798123.534519313 +354142988.4429378, 21664227.752841294 +366432111.0095147, 45597929.601091884 +376988982.3960997, 87513350.89127934 +390106578.7269594, 188735289.19644254 +400692344.54088306, 341259935.438681 +414241838.2703759, 677585865.3083946 +427827290.43423754, 1247978841.7370791 +442903655.2623199, 2309183710.7414694 +460619389.5777074, 4145495492.228279 +476524858.62938863, 7397595347.582645 +494539793.88725483, 12997555774.45029 +514442577.7411521, 23353808843.918175 +535146350.3325205, 41455219908.70394 +559830018.3877126, 75913096459.16243 +585542196.3560168, 140839446116.0759 +611515438.5086296, 266624967260.3838 +640924722.0354476, 430769272179.615 +673011686.3431495, 829525114358.9054 +709365650.864273, 1390710645811.2466 +744879010.7610548, 2692893109395.8438 +790294992.5553383, 4782515579404.277 +838480029.958037, 8527463739810.697 +892113666.8977078, 15517801537349.592 +949177996.1718366, 28379077039352.047 +1009892463.0868672, 51771107995392.44 +1074490549.8367944, 85298723725053.92 +1143220673.3770463, 138804389933460.2 +1216347140.7311883, 215459244130652.6 +1294151165.404076, 332789258676016.75 +1376931948.8102207, 472380015316473.4 +1465007830.8760247, 701186569202590.5 +1558717514.2407033, 1040820078937309.5 +1658421366.7635546, 1451930106689360.2 +1764502807.346504, 1932044038272768.2 +1877369780.4012852, 2374456081486786 +1997456324.6313062, 2910934142926986 +2125224242.16118, 3658385175730940.5 +2261164874.4325967, 4364052603502534.5 +2405800991.696016, 5498268020782474 +2559688803.36421, 6807845153741560 +2723420096.958689, 8100863910998466 +2897624513.8746004, 9104136102458050 +3082971970.7155313, 11470297206963266 +3280175235.5098205, 12763405065109374 +3489992668.7152667, 14888713468760558 +3713231139.5531974, 17153525279470230 +3950749128.8866386, 17804779570379490 +4203460030.5750384, 21081451360284788 +4422201096.119489, 22186798001542340 diff --git a/examples/low_level_interface/PPT_ionisation_rate/PPT_ionisation_rate.jl b/examples/low_level_interface/PPT_ionisation_rate/PPT_ionisation_rate.jl new file mode 100644 index 00000000..3e46d6e3 --- /dev/null +++ b/examples/low_level_interface/PPT_ionisation_rate/PPT_ionisation_rate.jl @@ -0,0 +1,175 @@ +using Luna +import DelimitedFiles: readdlm +import PyPlot: plt, pygui +import Printf: @sprintf +import GSL: hypergeom +import SpecialFunctions: dawson, gamma +import Luna.Modes: hquadrature + +this_folder = dirname(@__FILE__) +## Ilkov et al +k = collect(range(10, stop=12, length=50)) +E = 10 .^ k +# ppt = Ionisation.ionrate_PPT(:He, 10.6e-6, E) +ppt_cycavg = Ionisation.ionrate_PPT(:He, 10.6e-6, E, cycle_average=true, sum_tol=1e-6, stark_shift=false) +ppt_cycavg_rough = Ionisation.ionrate_PPT(:He, 10.6e-6, E, cycle_average=true, sum_tol=1e-4, stark_shift=false) +ppt_cycavg_intg = Ionisation.ionrate_PPT(:He, 10.6e-6, E, cycle_average=true, sum_integral=true, stark_shift=false) +adk = Ionisation.ionrate_ADK(:He, E) + +dat = readdlm(joinpath(this_folder, "Ilkov_PPT_He.csv"), ',') # ionrate [1/s] vs field [V/cm] + +fig = plt.figure() +# plt.loglog(E, ppt, label="PPT") +plt.loglog(E, ppt_cycavg/2, label="PPT cycle averaged") +plt.loglog(E, ppt_cycavg_rough/2, label="PPT cycle averaged, rough tolerance") +plt.loglog(E, ppt_cycavg_intg/2, label="PPT cycle averaged, integral for sum") +# plt.loglog(E, adk, label="ADK") +plt.loglog(dat[:, 1].*100, dat[:, 2], label="Ilkov et al. PPT") +plt.xlim(1e10, 1e12) +plt.ylim(0.1, 1e18) +plt.legend() +plt.xlabel("Field strength (V/m)") +plt.ylabel("Ionisation rate (s⁻¹)") +plt.title("He ionisation at 10.6 μm (Stark shift off, divided by 2)") + + +## Chang, Fundamentals of Attosecond Optics +dat = readdlm(joinpath(this_folder, "Chang_PPT.csv"), ',') # ionrate [1/fs] vs intensity [1e14 W/cm^2] +intensity = range(0.1, 25; length=100) * 1e18 # W/m^2 +E = Tools.intensity_to_field.(intensity) +ppt = Ionisation.ionrate_PPT(:He, 390e-9, E) +ppt_cycavg = Ionisation.ionrate_PPT(:He, 390e-9, E, cycle_average=true) +adk = Ionisation.ionrate_ADK(:He, E) + +s = sortperm(dat[:, 1]) + +plt.figure() +plt.loglog(intensity*1e-18, ppt*1e-15/2, label="PPT") +plt.loglog(intensity*1e-18, adk*1e-15, label="ADK") +plt.loglog(intensity*1e-18, ppt_cycavg*1e-15/2, label="PPT cycle averaged") +plt.loglog(dat[s, 1], dat[s, 2], label="Chang PPT") +plt.ylim(1e-14, 1) +plt.xlim(extrema(intensity.*1e-18)) +plt.legend() +plt.xlabel("Intensity (10\$^{14}\$ W/cm\$^2\$)") +plt.ylabel("Ionisation rate (1/fs)") +plt.title("PPT rate divided by 2") + +## Couairon +Ip = 12.063 * PhysData.electron +dat = readdlm(joinpath(this_folder, "Couairon_PPT.csv"), ',') # ionrate [1/s] vs intensity [W/cm^2] +k = collect(range(12, 15, length=100)) +intensity = 10 .^ k .* 1e4 # W/m^2 +E = Tools.intensity_to_field.(intensity) +ppt = Ionisation.ionrate_PPT(Ip, 800e-9, 1, 0, E) +ppt_cycavg = Ionisation.ionrate_PPT(Ip, 800e-9, 1, 0, E; cycle_average=true) +adk = Ionisation.ionrate_ADK(Ip, E) + +s = sortperm(dat[:, 1]) + +plt.figure() +# plt.loglog(intensity*1e-4, ppt, label="PPT") +plt.loglog(intensity*1e-4, adk, label="ADK") +plt.loglog(intensity*1e-4, ppt_cycavg/2, label="PPT cycle averaged") +plt.loglog(dat[s, 1], dat[s, 2], "--", label="Couairon PPT") +plt.ylim(1, 1e17) +plt.xlim(extrema(intensity.*1e-4)) +plt.legend() +plt.xlabel("Intensity (W/cm\$^2\$)") +plt.ylabel("Ionisation rate (1/s)") +plt.title("O\$_2\$ ionisation at 800 nm (PPT divided by 2)") + +## Gonzales et al +dat = readdlm(joinpath(this_folder, "Gonzalez_PPT_Ar.csv"), ',') # ionrate [1/s] vs intensity [W/cm^2] +k = collect(range(12, 16, length=100)) +intensity = 10 .^ k .* 1e4 # W/m^2 +E = Tools.intensity_to_field.(intensity) +λ0 = 800e-9 +gas = :Ar +# ppt = Ionisation.ionrate_PPT(gas, λ0, E) +sum_tol = 1e-5 +ppt_cycavg = Ionisation.ionrate_PPT(gas, λ0, E; cycle_average=true, sum_tol) +ppt_cycavg_m0 = Ionisation.ionrate_PPT(gas, λ0, E; cycle_average=true, sum_tol, msum=false) +ppt_cycavg_msum_nostark = Ionisation.ionrate_PPT(gas, λ0, E; cycle_average=true, stark_shift=false) + +s = sortperm(dat[:, 1]) + +plt.figure() +# plt.loglog(intensity*1e-4, ppt, label="PPT") +plt.loglog(intensity*1e-4, ppt_cycavg/2, label="PPT cycle averaged, sum over m") +plt.loglog(intensity*1e-4, ppt_cycavg_m0/2, ":", label="PPT cycle averaged, m=0") +plt.loglog(intensity*1e-4, ppt_cycavg_msum_nostark/2, "--", label="PPT cycle averaged, sum over m, no Stark shift") +plt.loglog(dat[s, 1], dat[s, 2], "--", label="Gonzalez PPT") +plt.ylim(1, 1e18) +plt.xlim(extrema(intensity.*1e-4)) +plt.legend() +plt.xlabel("Intensity (W/cm\$^2\$)") +plt.ylabel("Ionisation rate (1/s)") +plt.title("Ar ionisation at 800 nm (PPT divided by 2)") + +## Sum convergence test +k = collect(range(12, 16, length=100)) +intensity = 10 .^ k .* 1e4 # W/m^2 +E = Tools.intensity_to_field.(intensity) +sum_tol = [1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6] +ppt = zeros((length(E), length(sum_tol))) + +gas = :He +λ0 = 1800e-9 +for (idx, sti) in enumerate(sum_tol) + ppt[:, idx] .= Ionisation.ionrate_PPT(gas, λ0, E; sum_tol=sti) +end +## +cols = plt.get_cmap().(collect(range(0, 0.8, length(sum_tol)))) +fig = plt.figure() +for idx in eachindex(sum_tol) + plt.loglog(intensity*1e-4, ppt[:, idx]; + c=cols[idx], alpha=0.8, label=@sprintf("%.0e", sum_tol[idx])) +end +# plt.xlim(5e14, 5e15) +# plt.ylim(1e11, 1e16) +plt.xlabel("Intensity (W/cm²)") +plt.ylabel("Ionisation rate (s⁻¹)") +plt.legend(;title="Sum convergence tolerance") +plt.title(@sprintf("%s, %.0f nm", gas, λ0*1e9)) + +## Comparing ways of calculating φ +function φ(m, x) + if m == 0 + return dawson(x) + end + mabs = abs(m) + return (exp(-x^2) + * sqrt(π) + * x^(2mabs+1) + * gamma(mabs+1) + * hypergeom(1/2, 3/2 + mabs, x^2) + / (2*gamma(3/2 + mabs))) +end + +function φhard(m, x) + i, _ = hquadrature(0, x) do y + y = BigFloat(y) + x = BigFloat(x) + (x^2 - y^2)^(abs(m))*exp(y^2) + end + return exp(-x^2) * i +end + +m = [0, 1, 2] +x = collect(range(0, 26, 512)) + +φ1 = mapreduce(hcat, m) do mi + φ.(mi, x) +end +φ2 = Float64.( + mapreduce(hcat, m) do mi + Ionisation.φ.(mi, 2x) + end +) + +plt.figure() +for (idx, mi) in enumerate(m) + plt.plot(x, φ1[:, idx]) + plt.plot(2x, φ2[:, idx], linestyle="--") +end \ No newline at end of file diff --git a/src/Fields.jl b/src/Fields.jl index a287bccc..33663795 100644 --- a/src/Fields.jl +++ b/src/Fields.jl @@ -17,6 +17,12 @@ import DSP: unwrap import Logging: @warn abstract type AbstractField end + +""" + TimeField + +Abstract supertype for time-domain only fields. +""" abstract type TimeField <: AbstractField end """ diff --git a/src/Interface.jl b/src/Interface.jl index 7156a029..a9ee080c 100644 --- a/src/Interface.jl +++ b/src/Interface.jl @@ -311,6 +311,9 @@ In this case, all keyword arguments except for `λ0` are ignored. - `true` (default) -- same as `:PPT`. - `false` -- ignore plasma. Note that plasma is only available for full-field simulations. +- `PPT_stark_shift::Bool`: when using the PPT ionisation rate, determines whether + to include the effect of the Stark shift of the ground-state energy levels. + *The necessary data is only available for helium, neon, and argon!* - `thg::Bool`: Whether to include third-harmonic generation. Defaults to `true` for full-field simulations and to `false` for envelope simulations. If `raman` is `true`, then the following options apply: @@ -355,6 +358,7 @@ function prop_capillary_args(radius, flength, gas, pressure; shotnoise=true, modes=:HE11, model=:full, loss=true, raman=nothing, kerr=true, plasma=nothing, + PPT_stark_shift=true, rotation=true, vibration=true, saveN=201, filepath=nothing, scan=nothing, scanidx=nothing, filename=nothing) @@ -368,7 +372,7 @@ function prop_capillary_args(radius, flength, gas, pressure; mode_s = makemode_s(modes, flength, radius, gas, pressure, model, loss, pol) check_orth(mode_s) density = makedensity(flength, gas, pressure) - resp = makeresponse(grid, gas, raman, kerr, plasma, thg, pol, rotation, vibration) + resp = makeresponse(grid, gas, raman, kerr, plasma, thg, pol, rotation, vibration, PPT_stark_shift) inputs = makeinputs(mode_s, λ0, pulses, τfwhm, τw, ϕ, power, energy, pulseshape, polarisation, propagator) inputs = shotnoise_maybe(inputs, mode_s, shotnoise) @@ -507,7 +511,7 @@ function makedensity(flength, gas, pressure) end function makeresponse(grid::Grid.RealGrid, gas, raman, kerr, plasma, thg, pol, - rotation, vibration) + rotation, vibration, PPT_stark_shift) out = Any[] if kerr if thg @@ -516,7 +520,7 @@ function makeresponse(grid::Grid.RealGrid, gas, raman, kerr, plasma, thg, pol, push!(out, Nonlinear.Kerr_field_nothg(PhysData.γ3_gas(gas), length(grid.to))) end end - makeplasma!(out, grid, gas, plasma, pol) + makeplasma!(out, grid, gas, plasma, pol, PPT_stark_shift) if isnothing(raman) raman = gas in (:N2, :H2, :D2, :N2O, :CH4, :SF6) end @@ -532,7 +536,7 @@ function makeresponse(grid::Grid.RealGrid, gas, raman, kerr, plasma, thg, pol, Tuple(out) end -function makeplasma!(out, grid, gas, plasma::Bool, pol) +function makeplasma!(out, grid, gas, plasma::Bool, pol, PPT_stark_shift) # simple true/false => default to PPT for atoms, ADK for molecules if ~plasma return @@ -544,15 +548,15 @@ function makeplasma!(out, grid, gas, plasma::Bool, pol) @info("Using PPT ionisation rate.") model = :PPT end - makeplasma!(out, grid, gas, model, pol) + makeplasma!(out, grid, gas, model, pol, PPT_stark_shift) end -function makeplasma!(out, grid, gas, plasma::Symbol, pol) +function makeplasma!(out, grid, gas, plasma::Symbol, pol, stark_shift) ionpot = PhysData.ionisation_potential(gas) if plasma == :ADK ionrate = Ionisation.ionrate_fun!_ADK(gas) elseif plasma == :PPT - ionrate = Ionisation.ionrate_fun!_PPTcached(gas, grid.referenceλ) + ionrate = Ionisation.ionrate_fun!_PPTcached(gas, grid.referenceλ; stark_shift) else throw(DomainError(plasma, "Unknown ionisation rate $plasma.")) end @@ -561,7 +565,7 @@ function makeplasma!(out, grid, gas, plasma::Symbol, pol) end function makeresponse(grid::Grid.EnvGrid, gas, raman, kerr, plasma, thg, pol, - rotation, vibration) + rotation, vibration, PPT_stark_shift) plasma && error("Plasma response for envelope fields has not been implemented yet.") isnothing(thg) && (thg = false) out = Any[] @@ -655,6 +659,10 @@ function scalefield(f::Fields.DataField, fac) Fields.DataField(f.ω, f.Iω, f.ϕω, nmult(f.energy, fac), f.ϕ, f.λ0) end +function scalefield(f::Fields.PropagatedField, fac) + Fields.PropagatedField(f.propagator!, scalefield(f.field, fac)) +end + _findmode(mode_s, md) = _findmode([mode_s], md) function makeinputs(mode_s, λ0, pulse::Pulses.AbstractPulse) diff --git a/src/Ionisation.jl b/src/Ionisation.jl index 9ffc4a35..157bb1ea 100644 --- a/src/Ionisation.jl +++ b/src/Ionisation.jl @@ -1,13 +1,14 @@ module Ionisation -import SpecialFunctions: gamma -import GSL: hypergeom +import SpecialFunctions: gamma, dawson +import HCubature: hquadrature import HDF5 -import Pidfile: mkpidlock +import FileWatching.Pidfile: mkpidlock +import GSL: hypergeom import Logging: @info -import Luna.PhysData: c, ħ, electron, m_e, au_energy, au_time, au_Efield, wlfreq +import Luna.PhysData: c, ħ, electron, m_e, au_energy, au_time, au_Efield, wlfreq, polarisability_difference import Luna.PhysData: ionisation_potential, quantum_numbers -import Luna: Maths -import Luna: @hlock +import Luna: Maths, Utils +import Printf: @sprintf """ ionrate_fun!_ADK(ionpot::Float64, threshold=true) @@ -100,10 +101,11 @@ end Create an accelerated (interpolated) PPT ionisation rate function. """ -function ionrate_fun!_PPTaccel(material::Symbol, λ0; kwargs...) - n, l, Z = quantum_numbers(material) +function ionrate_fun!_PPTaccel(material::Symbol, λ0; stark_shift=true, kwargs...) + _, l, Z = quantum_numbers(material) ip = ionisation_potential(material) - ionrate_fun!_PPTaccel(ip, λ0, Z, l; kwargs...) + Δα = stark_shift ? polarisability_difference(material) : 0 + ionrate_fun!_PPTaccel(ip, λ0, Z, l; Δα, kwargs...) end function ionrate_fun!_PPTaccel(ionpot::Float64, λ0, Z, l; kwargs...) @@ -126,55 +128,58 @@ exists, load this rather than recalculate. Other keyword arguments are passed on to [`ionrate_fun_PPT`](@ref) """ -function ionrate_fun!_PPTcached(material::Symbol, λ0; kwargs...) - n, l, Z = quantum_numbers(material) +function ionrate_fun!_PPTcached(material::Symbol, λ0; stark_shift=true, kwargs...) + _, l, Z = quantum_numbers(material) + Δα = stark_shift ? polarisability_difference(material) : 0 ip = ionisation_potential(material) - ionrate_fun!_PPTcached(ip, λ0, Z, l; kwargs...) + ionrate_fun!_PPTcached(ip, λ0, Z, l; Δα, kwargs...) end function ionrate_fun!_PPTcached(ionpot::Float64, λ0, Z, l; - sum_tol=1e-4, cycle_average=false, N=2^16, Emax=nothing, - cachedir=joinpath(homedir(), ".luna", "pptcache")) - h = hash((ionpot, λ0, Z, l, sum_tol, cycle_average, N, Emax)) + N=2^16, Emax=nothing, + cachedir=joinpath(Utils.cachedir(), "pptcache"), + stale_age=60*10, + kwargs...) + h = hash((ionpot, λ0, Z, l, N, Emax, collect(kwargs...))) fname = string(h, base=16)*".h5" fpath = joinpath(cachedir, fname) lockpath = joinpath(cachedir, "pptlock") isdir(cachedir) || mkpath(cachedir) if isfile(fpath) - @info "Found cached PPT rate for $(ionpot/electron) eV, $(λ0*1e9) nm" - pidlock = mkpidlock(lockpath) - rate = loadPPTaccel(fpath) - close(pidlock) + @info @sprintf("Found cached PPT rate for %.2f eV, %.1f nm", ionpot/electron, 1e9λ0) + rate = mkpidlock(lockpath; stale_age) do + loadPPTaccel(fpath) + end return rate else E, rate = makePPTcache(ionpot::Float64, λ0, Z, l; - sum_tol=sum_tol, cycle_average, N=N, Emax=Emax) - @info "Saving PPT rate cache for $(ionpot/electron) eV, $(λ0*1e9) nm in $cachedir" - pidlock = mkpidlock(lockpath) - if isfile(fpath) # makePPTcache takes a while - has another process saved first? - rate = loadPPTaccel(fpath) - close(pidlock) - return rate - end - @hlock HDF5.h5open(fpath, "cw") do file - file["E"] = E - file["rate"] = rate + N, Emax, kwargs...) + mkpidlock(lockpath; stale_age) do + if ~isfile(fpath) # makePPTcache takes a while - has another process saved first? + @info @sprintf( + "Saving PPT rate for %.2f eV, %.1f nm in %s", + ionpot/electron, 1e9λ0, fpath + ) + HDF5.h5open(fpath, "cw") do file + file["E"] = E + file["rate"] = rate + end + end end - close(pidlock) return makePPTaccel(E, rate) end end function loadPPTaccel(fpath) isfile(fpath) || error("PPT cache file $fpath not found!") - E, rate = @hlock HDF5.h5open(fpath, "r") do file + E, rate = HDF5.h5open(fpath, "r") do file (read(file["E"]), read(file["rate"])) end makePPTaccel(E, rate) end function makePPTcache(ionpot::Float64, λ0, Z, l; - sum_tol=1e-4, cycle_average=false, N=2^16, Emax=nothing) + N=2^16, Emax=nothing, kwargs...) Emax = isnothing(Emax) ? 2*barrier_suppression(ionpot, Z) : Emax # ω0 = 2π*c/λ0 @@ -182,9 +187,9 @@ function makePPTcache(ionpot::Float64, λ0, Z, l; Emin = Emax/5000 E = collect(range(Emin, stop=Emax, length=N)); - @info "Pre-calculating PPT rate for $(ionpot/electron) eV, $(λ0*1e9) nm" - rate = ionrate_PPT(ionpot, λ0, Z, l, E; sum_tol=sum_tol, cycle_average); - @info "PPT pre-calcuation done" + @info @sprintf("Pre-calculating PPT rate rate for %.2f eV, %.1f nm...", ionpot/electron, 1e9λ0) + rate = ionrate_PPT(ionpot, λ0, Z, l, E; kwargs...) + @info "...PPT pre-calcuation done" return E, rate end @@ -234,12 +239,23 @@ function ionfrac!(frac, rate, E, δt) end function makePPTaccel(E, rate) + # Interpolating the log and re-exponentiating makes the spline more accurate cspl = Maths.CSpline(E, log.(rate); bounds_error=true) Emin = minimum(E) - # Interpolating the log and re-exponentiating makes the spline more accurate - ir(E) = abs(E) <= Emin ? 0.0 : exp(cspl(abs(E))) + Emax = maximum(E) function ionrate!(out, E) - out .= ir.(E) + for ii in eachindex(out) + aE = abs(E[ii]) + if aE < Emin + out[ii] = 0.0 + elseif aE > Emax + error( + "Field strength $aE V/m exceeds maximum for PPT ionisation rate ($Emax V/m)." + ) + else + out[ii] = exp(cspl(aE)) + end + end end end @@ -252,13 +268,22 @@ function ionrate_fun!_PPT(args...) end """ - ionrate_fun_PPT(ionpot::Float64, λ0, Z, l; sum_tol=1e-4, cycle_average=false) + ionrate_fun_PPT(ionpot::Float64, λ0, Z, l; kwargs...) Create closure to calculate PPT ionisation rate. # Keyword arguments - `sum_tol::Number`: Relative tolerance used to truncate the infinite sum. - `cycle_average::Bool`: If `false` (default), calculate the cycle-averaged rate +- `sum_integral::Bool`: whether to approximate the infinite sum in the PPT rate equation with + an integral (this neglects the multiphoton thresholds). +- `Δα::Number`: polarisability difference between the ground state and the cation (in SI units) + to calculate the Stark shift of the ground-state energy levels. Defaults to 0. +- `msum::Bool`: for l ≠ 0, whether or not to sum over different m states. Defaults to `true`. +- `Cnl::Real` : Pre-calculated `Cₙₗ` constant. If not given, defaults to the approximate expression from + the PPT papers. +- `occupancy`: Occupancy of the state(s) from which ionisation is considered. Defaults to 2 for + a state with two electrons (spin up/down). # References [1] Ilkov, F. A., Decker, J. E. & Chin, S. L. @@ -266,75 +291,85 @@ Ionization of atoms in the tunnelling regime with experimental evidence using Hg atoms. Journal of Physics B: Atomic, Molecular and Optical Physics 25, 4005–4020 (1992) -[2] 1.Bergé, L., Skupin, S., Nuter, R., Kasparian, J. & Wolf, J.-P. +[2] Bergé, L., Skupin, S., Nuter, R., Kasparian, J. & Wolf, J.-P. Ultrashort filaments of light in weakly ionized, optically transparent media. Rep. Prog. Phys. 70, 1633–1713 (2007) (Appendix A) + +[3] A. Couairon and A. Mysyrowicz, +"Femtosecond filamentation in transparent media," +Physics Reports 441(2–4), 47–189 (2007). + """ -function ionrate_fun_PPT(ionpot::Float64, λ0, Z, l; sum_tol=1e-4, cycle_average=false) - Ip_au = ionpot / au_energy - ns = Z/sqrt(2Ip_au) - ls = ns-1 - Cnl2 = 2^(2ns)/(ns*gamma(ns + ls + 1)*gamma(ns - ls)) - - ω0 = 2π*c/λ0 - ω0_au = au_time*ω0 - E0_au = (2*Ip_au)^(3/2) - - ionrate = let ω0_au=ω0_au, Cnl2=Cnl2, ns=ns, sum_tol=sum_tol - function ionrate(E) - E_au = abs(E)/au_Efield - γ = ω0_au*sqrt(2Ip_au)/E_au - γ2 = γ*γ - β = 2γ/sqrt(1 + γ2) - α = 2*(asinh(γ) - γ/sqrt(1+γ2)) - Up_au = E_au^2/(4*ω0_au^2) - Uit_au = Ip_au + Up_au - v = Uit_au/ω0_au - ret = 0 - divider = 0 - for m = -l:l - divider += 1 - mabs = abs(m) - flm = ((2l + 1)*factorial(l + mabs) - / (2^mabs*factorial(mabs)*factorial(l - mabs))) - # Following 5 lines are [1] eq. 8 and lead to identical results: - # G = 3/(2γ)*((1 + 1/(2γ2))*asinh(γ) - sqrt(1 + γ2)/(2γ)) - # Am = 4/(sqrt(3π)*factorial(mabs))*γ2/(1 + γ2) - # lret = sqrt(3/(2π))*Cnl2*flm*Ip_au - # lret *= (2*E0_au/(E_au*sqrt(1 + γ2))) ^ (2ns - mabs - 3/2) - # lret *= Am*exp(-2*E0_au*G/(3E_au)) - # [2] eq. (A14) - lret = 4sqrt(2)/π*Cnl2 - lret *= (2*E0_au/(E_au*sqrt(1 + γ2))) ^ (2ns - mabs - 3/2) - lret *= flm/factorial(mabs) - lret *= exp(-2v*(asinh(γ) - γ*sqrt(1+γ2)/(1+2γ2))) - lret *= Ip_au * γ2/(1+γ2) - # Remove cycle average factor, see eq. (2) of [1] - if !cycle_average - lret *= sqrt(π*E0_au/(3E_au)) - end - k = ceil(v) - n0 = ceil(v) - sumfunc = let k=k, β=β, m=m - function sumfunc(x, n) - diff = n-v - return x + exp(-α*diff)*φ(m, sqrt(β*diff)) - end +function ionrate_fun_PPT(ionpot::Float64, λ0, Z, l; + sum_tol=1e-6, cycle_average=false, sum_integral=false, + Δα=0, msum=true, Cnl=missing, occupancy=2) + + if ismissing(Δα) + Δα = 0 + end + + function ionrate(E) + Ip_au = (ionpot + Δα/2 * E^2) / au_energy # Δα/2 * E^2 includes the Stark shift + ns = Z/sqrt(2Ip_au) + ls = ns-1 + Cnl2 = ismissing(Cnl) ? 2^(2ns)/(ns*gamma(ns + ls + 1)*gamma(ns - ls)) : Cnl^2 + + ω0 = 2π*c/λ0 + ω0_au = au_time*ω0 + E0_au = (2*Ip_au)^(3/2) + + E_au = abs(E)/au_Efield + γ = ω0_au*sqrt(2Ip_au)/E_au + γ2 = γ*γ + β = 2γ/sqrt(1 + γ2) + α = 2*(asinh(γ) - γ/sqrt(1+γ2)) + Up_au = E_au^2/(4*ω0_au^2) + Uit_au = Ip_au + Up_au + v = Uit_au/ω0_au + ret = 0 + mrange = msum ? (-l:l) : (0:0) + for m in mrange + mabs = abs(m) + flm = ((2l + 1)*factorial(l + mabs) + / (2^mabs*factorial(mabs)*factorial(l - mabs))) + # Following 5 lines are [1] eq. 8 and lead to identical results: + # G = 3/(2γ)*((1 + 1/(2γ2))*asinh(γ) - sqrt(1 + γ2)/(2γ)) + # Am = 4/(sqrt(3π)*factorial(mabs))*γ2/(1 + γ2) + # lret = sqrt(3/(2π))*Cnl2*flm*Ip_au + # lret *= (2*E0_au/(E_au*sqrt(1 + γ2))) ^ (2ns - mabs - 3/2) + # lret *= Am*exp(-2*E0_au*G/(3E_au)) + # [2] eq. (A14) + lret = 4sqrt(2)/π*Cnl2 + lret *= (2*E0_au/(E_au*sqrt(1 + γ2))) ^ (2ns - mabs - 3/2) + lret *= flm/factorial(mabs) + lret *= exp(-2v*(asinh(γ) - γ*sqrt(1+γ2)/(1+2γ2))) + lret *= Ip_au * γ2/(1+γ2) + # Remove cycle average factor, see eq. (2) of [1] + if !cycle_average + lret *= sqrt(π*E0_au/(3E_au)) + end + n0 = ceil(v) + if sum_integral + s = sqrt(π)*factorial(mabs)*β^mabs/(2*(α+β)^(mabs+1))*sqrt(β/α) + else + s, _, _ = Maths.converge_series(0, n0=n0, rtol=sum_tol, maxiter=Inf) do x, n + diff = n-v + x + exp(-α*diff)*φ(m, sqrt(β*diff)) end - # s, success, steps = Maths.aitken_accelerate( - # sumfunc, 0, n0=n0, rtol=sum_tol, maxiter=Inf) - s, success, steps = Maths.converge_series( - sumfunc, 0, n0=n0, rtol=sum_tol, maxiter=Inf) - lret *= s - ret += lret + end - return ret/(au_time*divider) + lret *= s + ret += occ(occupancy, m)*lret end + return ret/au_time end return ionrate end +occ(occupancy::Number, m) = occupancy +occ(occupancy, m) = occupancy(m) + """ φ(m, x) @@ -344,29 +379,50 @@ Note that w_m(x) in [1] and φ_m(x) in [2] look slightly different but are in fact identical. """ function φ(m, x) - mabs = abs(m) - return (exp(-x^2) + #= second half of [3], eq. 81 + for m = 0, φ₀(x) is just the Dawson integral so we can get this directly. + for m ≠ 0, we calculate it using the hypergeometric function where possible. + for m ≠ 0 and large x, we need to do it brute force with BigFloats (slow) + =# + if m == 0 + return dawson(x) + end + + if x <= 26 + mabs = abs(m) + return (exp(-x^2) * sqrt(π) - * x^(mabs+1) + * x^(2mabs+1) * gamma(mabs+1) * hypergeom(1/2, 3/2 + mabs, x^2) / (2*gamma(3/2 + mabs))) + else + i, _ = hquadrature(0, x) do y + y = BigFloat(y) + x = BigFloat(x) + (x^2 - y^2)^(abs(m))*exp(y^2) + end + return Float64(exp(-x^2) * i) + end end + -function ionrate_fun_PPT(material::Symbol, λ0; kwargs...) - n, l, Z = quantum_numbers(material) +function ionrate_fun_PPT(material::Symbol, λ0; stark_shift=true, kwargs...) + _, l, Z = quantum_numbers(material) + Δα = stark_shift ? polarisability_difference(material) : 0 ip = ionisation_potential(material) - return ionrate_fun_PPT(ip, λ0, Z, l; kwargs...) + return ionrate_fun_PPT(ip, λ0, Z, l; Δα, kwargs...) end function ionrate_PPT(ionpot, λ0, Z, l, E; kwargs...) return ionrate_fun_PPT(ionpot, λ0, Z, l; kwargs...).(E) end -function ionrate_PPT(material::Symbol, λ0, E; kwargs...) - n, l, Z = quantum_numbers(material) +function ionrate_PPT(material::Symbol, λ0, E; stark_shift=true, kwargs...) + _, l, Z = quantum_numbers(material) + Δα = stark_shift ? polarisability_difference(material) : 0 ip = ionisation_potential(material) - return ionrate_PPT(ip, λ0, Z, l, E; kwargs...) + return ionrate_PPT(ip, λ0, Z, l, E; Δα, kwargs...) end end \ No newline at end of file diff --git a/src/Luna.jl b/src/Luna.jl index ea7123e3..a8c66aed 100644 --- a/src/Luna.jl +++ b/src/Luna.jl @@ -5,32 +5,6 @@ import Logging import LinearAlgebra: mul!, ldiv! Logging.disable_logging(Logging.BelowMinLevel) -""" - HDF5LOCK - -Lock on the HDF5 library for multi-threaded execution. -""" -const HDF5LOCK = ReentrantLock() - -""" - @hlock - -Wait for HDF5LOCK, execute the expression, and release H5DFLOCK. - -!!! warning - For thread safety, any call to functions from HDF5.jl needs to be preceeded by @hlock. -""" -macro hlock(expr) - quote - try - lock(HDF5LOCK) - $(esc(expr)) - finally - unlock(HDF5LOCK) - end - end -end - """ Luna.settings diff --git a/src/NonlinearRHS.jl b/src/NonlinearRHS.jl index ed4dd674..6d130347 100644 --- a/src/NonlinearRHS.jl +++ b/src/NonlinearRHS.jl @@ -369,13 +369,13 @@ end function norm_mode_average(grid, βfun!, aeff; shock=true) β = zeros(Float64, length(grid.ω)) shockterm = shock ? grid.ω.^2 : grid.ω .* PhysData.wlfreq(grid.referenceλ) - pre = @. -im*shockterm/(2*PhysData.c^(3/2)*sqrt(2*PhysData.ε_0)) + pre = @. -im*shockterm/4 / nlscale / PhysData.c function norm!(nl, z) βfun!(β, z) sqrtaeff = sqrt(aeff(z)) for i in eachindex(nl) !grid.sidx[i] && continue - nl[i] *= pre[i]*sqrtaeff/β[i] + nl[i] *= pre[i]/β[i]*sqrtaeff end end end diff --git a/src/Output.jl b/src/Output.jl index 1f4587ce..b1b954d6 100644 --- a/src/Output.jl +++ b/src/Output.jl @@ -6,8 +6,8 @@ import Base: getindex, show, haskey using EllipsisNotation import EllipsisNotation: Ellipsis import Printf: @sprintf -import Luna: Scans, Utils, @hlock -import Pidfile: mkpidlock +import Luna: Scans, Utils +import FileWatching.Pidfile: mkpidlock abstract type AbstractOutput end @@ -169,7 +169,7 @@ end function HDF5Output(fpath, save_cond, yname, tname, statsfun, compression, script=nothing, cache=true, readonly=false) if isfile(fpath) && cache - @hlock HDF5.h5open(fpath, "cw") do file + HDF5.h5open(fpath, "cw") do file if HDF5.haskey(file["meta"], "cache") saved = read(file["meta"]["cache"]["saved"]) chash = hash((sort(keys(file["stats"])), size(file[yname])[1:end-1])) @@ -184,7 +184,7 @@ function HDF5Output(fpath, save_cond, yname, tname, statsfun, compression, end fdir, fname = splitdir(fpath) isdir(fdir) || mkpath(fdir) - @hlock HDF5.h5open(fpath, "cw") do file + HDF5.h5open(fpath, "cw") do file HDF5.create_group(file, "stats") HDF5.create_group(file, "meta") file["meta"]["sourcecode"] = Utils.sourcecode() @@ -221,7 +221,7 @@ function initialise(o::HDF5Output, y) mdims = copy(cdims) mdims[end] = -1 maxdims = Tuple(mdims) - @hlock HDF5.h5open(o.fpath, "r+") do file + HDF5.h5open(o.fpath, "r+") do file if o.compression HDF5.create_dataset(file, o.yname, HDF5.datatype(ComplexF64), (dims, maxdims), chunk=chdims, blosc=3) @@ -245,7 +245,7 @@ end # for single String index, read whole data set function getindex(o::HDF5Output, idx::AbstractString) - @hlock HDF5.h5open(o.fpath, "r") do file + HDF5.h5open(o.fpath, "r") do file read(file[idx]) end end @@ -253,7 +253,7 @@ end # more indices -> read slice of data function getindex(o::HDF5Output, ds::AbstractString, I::Union{AbstractRange, Int, Colon, Ellipsis}...) - @hlock HDF5.h5open(o.fpath, "r") do file + HDF5.h5open(o.fpath, "r") do file file[ds][to_indices(file[ds], I)...] end end @@ -264,7 +264,7 @@ function getindex(o::HDF5Output, ds::AbstractString, if count(isa.(I, Array)) > 1 error("Only one dimension can be index with an array.") end - @hlock HDF5.h5open(o.fpath, "r") do file + HDF5.h5open(o.fpath, "r") do file dset = file[ds] idcs = to_indices(dset, I) adim = findfirst(isa.(idcs, Array)) # which of the indices is the array @@ -282,7 +282,7 @@ end function show(io::IO, o::HDF5Output) if isfile(o.fpath) - fields = @hlock HDF5.h5open(o.fpath) do file + fields = HDF5.h5open(o.fpath) do file keys(file) end print(io, "HDF5Output$(fields)") @@ -293,7 +293,7 @@ end function haskey(o::HDF5Output, key) if isfile(o.fpath) - return @hlock HDF5.h5open(o.fpath) do file + return HDF5.h5open(o.fpath) do file haskey(file, key) end else @@ -315,7 +315,7 @@ function (o::HDF5Output)(y, t, dt, yfun) save, ts = o.save_cond(y, t, dt, o.saved) push!(o.stats_tmp, o.statsfun(y, t, dt)) if save - @hlock HDF5.h5open(o.fpath, "r+") do file + HDF5.h5open(o.fpath, "r+") do file !HDF5.haskey(file, o.yname) && initialise(o, y) statsnames = sort(collect(keys(o.stats_tmp[end]))) cachehash = hash((statsnames, size(y))) @@ -374,21 +374,21 @@ function append_stats!(parent, a::Array{Dict{String,Any},1}) end function create_dataset(parent, name, x::Number) - @hlock HDF5.create_dataset(parent, name, HDF5.datatype(typeof(x)), ((1,), (-1,)), + HDF5.create_dataset(parent, name, HDF5.datatype(typeof(x)), ((1,), (-1,)), chunk=(1,)) end function create_dataset(parent, name, x::AbstractArray) dims = (size(x)..., 1) maxdims = (size(x)..., -1) - @hlock HDF5.create_dataset(parent, name, HDF5.datatype(eltype(x)), (dims, maxdims), + HDF5.create_dataset(parent, name, HDF5.datatype(eltype(x)), (dims, maxdims), chunk=dims) end "Calling the output on a dictionary writes the items to the file" function (o::HDF5Output)(d::AbstractDict; force=false, meta=false, group=nothing) o.readonly && error("Cannot add data to read-only output!") - @hlock HDF5.h5open(o.fpath, "r+") do file + HDF5.h5open(o.fpath, "r+") do file parent = meta ? file["meta"] : file for (k, v) in pairs(d) if HDF5.haskey(parent, k) @@ -425,7 +425,7 @@ end "Calling the output on a key, value pair writes the value to the file" function (o::HDF5Output)(key::AbstractString, val; force=false, meta=false, group=nothing) o.readonly && error("Cannot add data to read-only output!") - @hlock HDF5.h5open(o.fpath, "r+") do file + HDF5.h5open(o.fpath, "r+") do file parent = meta ? file["meta"] : file if HDF5.haskey(parent, key) if force @@ -640,16 +640,20 @@ While running the given `scan`, save the variables given as keyword arguments in - `stats`: The statistics dictionary from a simulation is saved in scan grid and `NaN`-padded to account for variable lengths in the output arrays - `fpath`: Path to the file. Defaults to the scan name plus "_collected" - `script`: Path to the Julia scrpt file running the scan. Can be grabbed automatically using the macro [`@scansave`](@ref). + +Another keyword argument `lock_stale_age` sets the time in seconds after which the function ignores +the lock on the file and writes to it anyway. Defaults to 300 s (5 min). +(Note that if the PID of the locking process appears valid, this is automatically increased 25x.) """ function scansave(scan, scanidx; stats=nothing, fpath=nothing, - grid=nothing, script=nothing, kwargs...) + grid=nothing, script=nothing, + lock_stale_age=300, kwargs...) fpath = isnothing(fpath) ? "$(scan.name)_collected.h5" : fpath - lockpath = joinpath(Utils.cachedir(), "$(basename(fpath)).scanlock") - isdir(Utils.cachedir()) || mkpath(Utils.cachedir()) - pidlock = mkpidlock(lockpath) + lockpath = fpath*"_scanlock" + mkpidlock(lockpath; stale_age=lock_stale_age) do # note no indent to keep this legible if !isfile(fpath) # First save - set up file structure - @hlock HDF5.h5open(fpath, "cw") do file + HDF5.h5open(fpath, "cw") do file group = HDF5.create_group(file, "scanvariables") order = String[] shape = Int[] # scan shape @@ -703,7 +707,7 @@ function scansave(scan, scanidx; stats=nothing, fpath=nothing, end end end - @hlock HDF5.h5open(fpath, "r+") do file + HDF5.h5open(fpath, "r+") do file scanshape = Tuple([length(ai) for ai in scan.arrays]) cidcs = CartesianIndices(scanshape) scanidcs = Tuple(cidcs[scanidx]) @@ -739,7 +743,7 @@ function scansave(scan, scanidx; stats=nothing, fpath=nothing, file[string(k)][sidcs..., scanidcs...] = v end end - close(pidlock) + end # mkpidlock do end """ diff --git a/src/PhysData.jl b/src/PhysData.jl index 56620e29..9e0784d8 100644 --- a/src/PhysData.jl +++ b/src/PhysData.jl @@ -44,6 +44,8 @@ const N_A = ustrip(CODATA2014.N_A) const amg = atm/(k_B*273.15) "Atomic mass unit" const m_u = ustrip(CODATA2014.m_u) +"Atomic unit of electric polarisability" +const au_polarisability = electron^2*ustrip(CODATA2014.a_0)^2/au_energy const gas = (:Air, :He, :HeJ, :HeB, :Ne, :Ar, :Kr, :Xe, :N2, :H2, :O2, :CH4, :SF6, :N2O, :D2) const gas_str = Dict( @@ -63,7 +65,7 @@ const gas_str = Dict( :N2O => "NitrousOxide", :D2 => "Deuterium" ) -const glass = (:SiO2, :BK7, :KBr, :CaF2, :BaF2, :Si, :MgF2, :ADPo, :ADPe, :KDPo, :KDPe) +const glass = (:SiO2, :BK7, :KBr, :CaF2, :BaF2, :Si, :MgF2, :ADPo, :ADPe, :KDPo, :KDPe, :CaCO3) const metal = (:Ag,:Al) """ @@ -112,7 +114,7 @@ Sellmeier expansion for linear susceptibility from J. Opt. Soc. Am. 67, 1550 (1977) """ function γ_Peck(B1, C1, B2, C2, dens) - return μm -> @. (((B1 / (C1 - 1/μm^2) + B2 / (C2 - 1/μm^2)) + 1)^2 - 1)/dens + return μm -> (((B1 / (C1 - 1/μm^2) + B2 / (C2 - 1/μm^2)) + 1)^2 - 1)/dens end """ @@ -132,7 +134,7 @@ https://doi.org/10.5194/acp-21-14927-2021. """ function γ_QuanfuHe(A, B, C, dens) - return μm -> ((1 + 1e-8*(A + B/(C - (1e4/μm)^2))))/dens + return μm -> complex((1 + 1e-8*(A + B/(C - (1e4/μm)^2))))/dens end """ @@ -293,6 +295,12 @@ function sellmeier_glass(material::Symbol) + 0.0080/(1-(18.0/μm)^2) + 2.14973/(1-(25.0/μm)^2) )) + elseif material == :CaCO3 + return μm -> @. sqrt(complex(1 + + 0.73358749 + + 0.96464345/(1-1.94325203e-2/μm^2) + + 1.82831454/(1-120/μm^2) + )) elseif material == :ADPo return μm -> @. sqrt(complex( 2.302842 @@ -431,7 +439,7 @@ Get function which returns refractive index. function ref_index_fun(material::Symbol, P=1.0, T=roomtemp; lookup=nothing) if material in gas χ1 = χ1_fun(material, P, T) - return λ -> sqrt(1 + χ1(λ)) + return λ -> sqrt(1 + complex(χ1(λ))) elseif material in glass if isnothing(lookup) lookup = (material == :SiO2) @@ -661,6 +669,9 @@ function n2_glass(material::Symbol; λ=nothing) elseif material == :MgF2 # R. DeSalvo et al., IEEE J. Q. Elec. 32, 10 (1996). return 5.79e-21 + elseif material == :CaCO3 + # Kabaciński et al., 10.1364/OE.27.011018 + return 3.22e-20 else error("Unkown glass $material") end @@ -776,6 +787,63 @@ function quantum_numbers(material) end end +""" + polarisability_difference(material; unit=:SI) + +Return the difference in polarisability between the ground state and the ion for the +`material`. `unit` can be `:SI` or `:atomic` + +Reference: +Wang, K. et al. +Static dipole polarizabilities of atoms and ions from Z=1 to 20 +calculated within a single theoretical scheme. +Eur. Phys. J. D 75, 46 (2021). + +""" +function polarisability_difference(material; unit=:SI) + if unit == :SI + factor = au_polarisability + elseif unit == :atomic + factor = 1 + else + throw(DomainError(unit, "Unknown unit $unit")) + end + if material in (:He, :HeB, :HeJ) + return (1.3207 - 0.2811)*factor + elseif material == :Ne + return (2.376 - 1.2417)*factor + elseif material == :Ar + return (10.762 - 6.807)*factor + else + return missing + end +end + +""" + Cnl_ADK(material) + +Return the value of Cₙₗ from the ADK paper for the `material`. For `material`S +other than noble gases, this returns `missing`. + +Reference: +Ammosov, M. V., Delone, N. B. & Krainov, V. P. Tunnel Ionization Of Complex Atoms And Atomic Ions In Electromagnetic Field. Soviet Physics JETP 64, 1191–1194 (1986). +""" +function Cnl_ADK(material) + if material in (:He, :HeB, :HeJ) + return 1.99 + elseif material == :Ne + return 1.31 + elseif material == :Ar + return 1.9 + elseif material == :Kr + return 2.17 + elseif material == :Xe + return 2.27 + else + return missing + end +end + """ lookup_glass(material::Symbol) diff --git a/src/Processing.jl b/src/Processing.jl index 50557570..7ab4379b 100644 --- a/src/Processing.jl +++ b/src/Processing.jl @@ -311,15 +311,20 @@ end fwhm(x::Vector, I::Vector; minmax=:min) = Maths.fwhm(x, I; minmax) """ - peakpower(grid, Eω; bandpass=nothing, oversampling=1) - peakpower(output; bandpass=nothing, oversampling=1) + peakpower(grid, Eω; bandpass=nothing, oversampling=1, sumdims=nothing) + peakpower(output; bandpass=nothing, oversampling=1, sumdims=nothing) Extract the peak power. If `bandpass` is given, bandpass the field according to -[`window_maybe`](@ref). +[`window_maybe`](@ref). If `sumdims` is not `nothing`, sum the time-dependent power +over these dimensions (e.g. modes) before taking the maximum. """ -function peakpower(grid, Eω; bandpass=nothing, oversampling=1) +function peakpower(grid, Eω; bandpass=nothing, oversampling=1, sumdims=nothing) to, Eto = getEt(grid, Eω; oversampling=oversampling, bandpass=bandpass) - dropdims(maximum(abs2.(Eto); dims=1); dims=1) + Pt = abs2.(Eto) + if !isnothing(sumdims) + Pt = dropdims(sum(Pt; dims=sumdims); dims=sumdims) + end + dropdims(maximum(Pt; dims=1); dims=1) end function peakpower(output; kwargs...) diff --git a/src/Scans.jl b/src/Scans.jl index 9ae2855d..3f9abed3 100644 --- a/src/Scans.jl +++ b/src/Scans.jl @@ -3,8 +3,8 @@ import ArgParse: ArgParseSettings, parse_args, parse_item, @add_arg_table! import Logging: @info, @warn import Printf: @sprintf import Base: length, size -import Luna: @hlock, Utils -import Pidfile: mkpidlock +import Luna: Utils +import FileWatching.Pidfile: mkpidlock import HDF5 import Distributed: @spawnat, addprocs, rmprocs, fetch, Future, @everywhere import Dates @@ -266,6 +266,7 @@ function runscan(f, scan::Scan{LocalExec}) msg = "Error at scanidx $scanidx:\n"*sprint(showerror, e, bt) @warn msg end + Base.GC.gc() end out end @@ -282,6 +283,7 @@ function runscan(f, scan::Scan{RangeExec}) msg = "Error at scanidx $scanidx:\n"*sprint(showerror, e, bt) @warn msg end + Base.GC.gc() end out end @@ -328,6 +330,7 @@ function runscan(f, scan::Scan{BatchExec}) msg = "Error at scanidx $scanidx:\n"*sprint(showerror, e, bt) @warn msg end + Base.GC.gc() end end @@ -353,23 +356,23 @@ end function _runscan(f, scan::Scan{QueueExec}) if isempty(scan.exec.queuefile) h = string(hash(scan.name); base=16) - qfile = joinpath(Utils.cachedir(), "qfile_$h.h5") + qfile = "qfile_$h.h5" else qfile = scan.exec.queuefile end - lockpath = joinpath(Utils.cachedir(), basename(qfile)*"_lock") + lockpath = qfile*"_lock" combos = vec(collect(Iterators.product(scan.arrays...))) while true - mkpidlock(lockpath) do + mkpidlock(lockpath; stale_age=120) do # first process to catch the pidlock creates the queue file if ~isfile(qfile) - @hlock HDF5.h5open(qfile, "cw") do file + HDF5.h5open(qfile, "cw") do file file["qdata"] = zeros(Int, length(scan)) end end # read the queue data - global qdata = @hlock HDF5.h5open(qfile) do file + global qdata = HDF5.h5open(qfile) do file read(file["qdata"]) end # find the first index which is neither done nor in progress @@ -378,7 +381,7 @@ function _runscan(f, scan::Scan{QueueExec}) end if ~isnothing(scanidx) # mark the index as in progress - @hlock HDF5.h5open(qfile, "r+") do file + HDF5.h5open(qfile, "r+") do file file["qdata"][scanidx] = 1 end end @@ -400,11 +403,12 @@ function _runscan(f, scan::Scan{QueueExec}) msg = "Error at scanidx $scanidx:\n"*sprint(showerror, e, bt) @warn msg end - mkpidlock(lockpath) do # acquire lock on qfile again - @hlock HDF5.h5open(qfile, "r+") do file + mkpidlock(lockpath; stale_age=10) do # acquire lock on qfile again + HDF5.h5open(qfile, "r+") do file file["qdata"][scanidx] = code # mark as done/failed end end + Base.GC.gc() end end diff --git a/src/Stats.jl b/src/Stats.jl index a4048a21..fa9be2c0 100644 --- a/src/Stats.jl +++ b/src/Stats.jl @@ -87,8 +87,11 @@ function peakpower(grid) function addstat!(d, Eω, Et, z, dz) if ndims(Et) > 1 d["peakpower"] = dropdims(maximum(abs2.(Et), dims=1), dims=1) + d["peakpower_allmodes"] = maximum(eachindex(grid.t)) do ii + sum(abs2, Et[ii, :]) + end else - d["peakpower"] = maximum(abs2.(Et)) + d["peakpower"] = maximum(abs2, Et) end end return addstat! @@ -104,14 +107,19 @@ dataset is labeled as `peakpower_[label]`. function peakpower(grid, Eω, window::Vector{<:Real}; label) Etbuf, analytic! = plan_analytic(grid, Eω) # output buffer and function for inverse FT Eωbuf = similar(Eω) # buffer for Eω with window applied + Pt = zeros((length(grid.t), size(Eωbuf, 2))) key = "peakpower_$label" function addstat!(d, Eω, Et, z, dz) Eωbuf .= Eω .* window analytic!(Etbuf, Eωbuf) if ndims(Etbuf) > 1 - d[key] = dropdims(maximum(abs2.(Etbuf), dims=1), dims=1) + Pt .= abs2.(Etbuf) + d[key] = dropdims(maximum(Pt, dims=1), dims=1) + d[key*"_allmodes"] = maximum(eachindex(grid.t)) do ii + sum(Pt[ii, :]; dims=2) + end else - d[key] = maximum(abs2.(Etbuf)) + d[key] = maximum(abs2, Etbuf) end end end @@ -141,7 +149,7 @@ Create stats function to calculate the mode-averaged peak intensity given the ef """ function peakintensity(grid, aeff) function addstat!(d, Eω, Et, z, dz) - d["peakintensity"] = maximum(abs2.(Et))/aeff(z) + d["peakintensity"] = maximum(abs2, Et)/aeff(z) end end @@ -157,9 +165,11 @@ function peakintensity(grid, modes::Modes.ModeCollection; components=:y) function addstat!(d, Eω, Et, z, dz) Modes.to_space!(Et0, Et, (0, 0), tospace; z=z) if npol > 1 - d["peakintensity"] = c*ε_0/2 * maximum(sum(abs2.(Et0), dims=2)) + d["peakintensity"] = c*ε_0/2 * maximum(eachindex(grid.t)) do ii + sum(abs2, Et0[ii, :]) + end else - d["peakintensity"] = c*ε_0/2 * maximum(abs2.(Et0)) + d["peakintensity"] = c*ε_0/2 * maximum(abs2, Et0) end end end @@ -200,7 +210,7 @@ function fwhm_r(grid, modes; components=:y) function addstat!(d, Eω, Et, z, dz) function f(r) Modes.to_space!(Eω0, Eω, (r, 0), tospace; z=z) - sum(abs2.(Eω0)) + sum(abs2, Eω0) end d["fwhm_r"] = 2*Maths.hwhm(f) end diff --git a/src/Utils.jl b/src/Utils.jl index a9af75ec..d25c3101 100644 --- a/src/Utils.jl +++ b/src/Utils.jl @@ -3,10 +3,12 @@ import Dates import FFTW import Logging import LibGit2 -import Pidfile: mkpidlock +import FileWatching.Pidfile: mkpidlock import HDF5 -import Luna: @hlock, settings +import Luna: settings import Printf: @sprintf +import Scratch: get_scratch!, clear_scratchspaces! +import Luna subzero = '\u2080' subscript(digit::Char) = string(Char(codepoint(subzero)+parse(Int, digit))) @@ -44,7 +46,9 @@ lunadir() = dirname(srcdir()) datadir() = joinpath(srcdir(), "data") -cachedir() = joinpath(homedir(), ".luna") +cachedir() = get_scratch!(Luna, "lunacache") + +clear_cache() = clear_scratchspaces!(Luna) function sourcecode() src = dirname(@__FILE__) @@ -140,7 +144,7 @@ function save_dict_h5(fpath, d; force=false, rmold=false) end end - @hlock HDF5.h5open(fpath, "cw") do file + HDF5.h5open(fpath, "cw") do file for (k, v) in pairs(d) dict2h5(k, v, file) end @@ -170,7 +174,7 @@ function load_dict_h5(fpath) return dd end - d = @hlock HDF5.h5open(fpath) do file + d = HDF5.h5open(fpath) do file h52dict(file) end end diff --git a/test/Couairon_PPT.csv b/test/Couairon_PPT.csv deleted file mode 100644 index 64ca1d24..00000000 --- a/test/Couairon_PPT.csv +++ /dev/null @@ -1,82 +0,0 @@ -952505637584.2877, 0.1786999679557911 -1040513230535.7838, 0.43318326277201463 -1108514316096.0747, 0.6561663846214795 -1182247285103.6926, 1.2246342363893747 -1264384673507.5625, 2.06552662932027 -1387275904382.0945, 4.676662551046553 -1485240457011.8396, 7.597592305351441 -1609811875823.6562, 14.907778635509336 -1713337050219.6404, 25.614375484729962 -1861912019995.2395, 54.53349723720085 -1963860619660.576, 84.84539719945634 -2128706265539.3503, 165.89472738849759 -2279305737519.526, 269.33875351974416 -2493461617110.3545, 560.7433294098939 -2682258733354.7256, 1079.6644313206327 -2919832009954.657, 2210.1288544577656 -3132693119300.041, 3654.4398374902053 -3447553486667.5806, 7084.477510046762 -3703282655181.424, 11887.745598782209 -3972898065782.676, 19684.111457605766 -4406436245682.063, 39348.128490071664 -4741807978367.681, 62604.77211022206 -5288813431945.029, 74950.20113547376 -5691342465755.62, 45459.38650830013 -6171702855129.727, 92197.7760612079 -6635765898041.248, 179411.08480487217 -7157579732823.128, 361530.5395702001 -7736907297101.334, 721058.4408252558 -8318661284176.872, 1385196.8337170032 -8963250819366.703, 2720392.7194878836 -9646027161181.623, 4953465.332043269 -10339786831641.742, 9705674.404284673 -11164769305336.363, 19258216.870607693 -12004271293500.479, 35924024.16571446 -12906897164376.139, 67805158.25754797 -13877393332652.955, 126297000.05132051 -14794139343381.188, 218112595.5609423 -15742646593551.586, 371724344.1003682 -16885170080911.133, 664811330.6526406 -18096825438568.25, 1186176977.721868 -19492168729890.664, 2192436938.2992797 -20898324245757.805, 3851880687.59843 -22437793028764.68, 6547114092.733599 -24203452767149.914, 11428528627.637417 -26877357686444.68, 23361572820.696404 -29420599969864.1, 37492759480.58589 -32455735400370.03, 36685634792.96558 -35012090327722.676, 64578153994.73332 -37967178945886.39, 124027267601.33366 -41524351543511.35, 254787801062.1951 -44673861737812.42, 435874054226.59576 -49669743690459.59, 898988575869.7677 -53518511647790.77, 1395488841476.3357 -59582088976676.875, 1541105133967.4111 -64075199928751.88, 2717388233404.8228 -69440952657742.11, 4920010393788.423 -75278969423112.58, 8385061999259.844 -84080735659503.39, 10600303660798.357 -90899992762102.23, 16941129529146.36 -98926414945559.16, 27591611542488.957 -111684989719109.97, 42701489378749.6 -123985746195192.4, 68308950352594.31 -139175959766134.69, 115213140202736.17 -154966441631329.5, 178779393786204.5 -171448194503891.44, 272324347927003.44 -189844751605532.84, 403980663427743.2 -214471327911260, 642263175451656.1 -231912833829969.84, 908039444897865.5 -255813555307362.5, 1117244301313072.2 -280577493036494.97, 1493512821290377.8 -310816253883753.3, 2017751713863648.5 -352034834279407.25, 2833907630615405 -390640600686255.5, 3731923368706402.5 -431267343863162.3, 4685855722936682 -472075572006955.9, 5829373609956934 -520727242449184.7, 7251951125624000 -571460905718133.9, 8705018988930735 -640653722313725.6, 10872437923934384 -710910769466309.9, 13154982145246860 -775529551017944.9, 15176190088326394 -861312032946122.4, 18072953866961772 -957473531626707.5, 21902531966824030 diff --git a/test/Ilkov_PPT_He.csv b/test/Ilkov_PPT_He.csv deleted file mode 100644 index ff26a006..00000000 --- a/test/Ilkov_PPT_He.csv +++ /dev/null @@ -1,72 +0,0 @@ -99141656.79196046, 4.32298442228804e-19 -101205482.49601033, 2.271799723118581e-18 -104158328.1157883, 1.3235707139825912e-17 -106086409.64509214, 5.301534591106518e-17 -108048947.36178665, 1.9699353223856161e-16 -110044856.80228354, 6.143645439359398e-16 -113086508.37095246, 3.2619614796723604e-15 -115653226.80794486, 1.6043140454503596e-14 -116876346.36652254, 4.5861555613160747e-14 -118937953.36169156, 1.9233253766003405e-13 -121234170.5689567, 4.4606395891854315e-13 -123153703.32650946, 1.3513454513800764e-12 -125755756.89608395, 4.637958877190173e-12 -128411483.6710525, 1.489038739557029e-11 -131806550.50167172, 5.348627414390777e-11 -134244896.06857625, 1.9874339945953529e-10 -138152251.82483798, 7.379812503003672e-10 -142551823.72259143, 4.2988074841154985e-9 -146319538.99269778, 1.462638338418392e-8 -148637009.89640868, 4.5813862250395755e-8 -151776716.81472638, 1.5207811189751233e-7 -155795371.3452554, 6.986698417266102e-7 -160339953.8554685, 0.000003904232974658481 -165859387.89646813, 0.000012163631937061938 -168779445.69056767, 0.00004106555337481806 -174298701.69560546, 0.00019016559675151981 -178748614.1917998, 0.0005903319465959867 -183919379.34210336, 0.002179304871696868 -190562712.2511098, 0.011921516052413383 -196004642.7397286, 0.03995214279249117 -203590804.65992832, 0.1840592496931095 -209204685.89176884, 0.70517209272836 -213633518.31855777, 1.7905963855977054 -223293764.43036655, 10.331012951882514 -232310956.74744165, 43.62489080120873 -238941549.2249278, 132.60430857398137 -247293649.60355547, 400.89317243893794 -254348679.33528054, 1121.986036530282 -261738338.0940824, 2971.81449187606 -269350213.93577045, 9457.030895636239 -279347032.3760024, 31099.63608215993 -291216572.607934, 95636.90716128159 -303592765.9166178, 309194.5237683632 -316485884.59022546, 828567.4316012532 -329932837.5869688, 2516309.519595419 -343948508.8527656, 7268824.91218053 -360430558.45619416, 24636037.32573087 -379659278.36011004, 76853585.09507291 -401998572.14860314, 272065331.54399014 -427858624.0366213, 900641581.7982208 -457741131.6694967, 2728883831.4404507 -494820729.6332553, 9453675390.377441 -537676188.3463545, 30480068574.953228 -584238824.1429445, 93474927896.97137 -638130821.9282533, 286566527302.8782 -700608231.9712229, 833035083709.9401 -773198756.102467, 2446476055007.3643 -862195513.8615811, 7104837107420.825 -966429324.3160362, 20635547907238.863 -1083253069.335132, 55980633546301 -1214172615.7616086, 131887446352311.34 -1360879935.0654097, 262577147066744.62 -1525320097.8206513, 537237625834755.7 -1709605311.2883267, 999032950346908.2 -1916095671.9036298, 1513815611457111.8 -2147520500.42448, 2252486137725646 -2406878338.7359557, 3187977293167934.5 -2697549698.3736625, 4410504677518434 -3023305708.390134, 5857028128958770 -3388388270.302976, 7603023059047248 -3792381930.206634, 11091966672583598 -4256123956.9706087, 12597752882523148 diff --git a/test/test_fields.jl b/test/test_fields.jl index ff4b9fd2..f8dd6844 100644 --- a/test/test_fields.jl +++ b/test/test_fields.jl @@ -3,6 +3,7 @@ using Luna import FFTW import Statistics: mean, std import Random: MersenneTwister +import Luna: Hankel # note that most of the Fields.jl code is tested in many other modules @@ -405,7 +406,7 @@ end # test diversity of power fluctuations @test mean(std(Its[istart:iend,:], dims=2)[:,1]) > 10 end - +## @testset "Propagation" begin λ0 = 800e-9 τfwhm = 2.5e-15 @@ -536,8 +537,31 @@ end ϕs, Eωcomp = Fields.optcomp_taylor(Eωmirr, grid, λ0; order=3) @test isapprox(ϕs[3], -reflections*gdd, rtol=0.1) end -end + # check (back-)propagation for all gases with a large grid + λ0 = 800e-9 + τfwhm = 2.5e-15 + grid = Grid.RealGrid(1, λ0, (70e-9, 4e-6), 500e-15) + input = Fields.GaussField(λ0=λ0, τfwhm=τfwhm, energy=1e-6) + x = Array{Float64}(undef, length(grid.t)) + FT = FFTW.plan_rfft(x, 1) + Eω = input(grid, FT) + @testset "gas propgation: $g" for g in PhysData.gas + Eωgas = Fields.prop_material(Eω, grid, g, 10, λ0) + Et = FT \ Eωgas + gab = Maths.gabor(grid.t, Et, [-10e-15, 10e-15], 3e-15) + ω0 = Maths.moment(grid.ω, abs2.(gab)) + @test ω0[1] < ω0[2] + + Eωgas = Fields.prop_material(Eω, grid, g, -10, λ0) + Et = FT \ Eωgas + gab = Maths.gabor(grid.t, Et, [-10e-15, 10e-15], 3e-15) + ω0 = Maths.moment(grid.ω, abs2.(gab)) + @test ω0[1] > ω0[2] + end + +end +## @testset "Compression" begin # Short pulse with 100 fs^2 λ0 = 800e-9 diff --git a/test/test_ionisation.jl b/test/test_ionisation.jl index 69eff34c..5ac40a94 100644 --- a/test/test_ionisation.jl +++ b/test/test_ionisation.jl @@ -1,6 +1,7 @@ import Test: @test, @testset import Luna: Ionisation, Maths, PhysData, Tools import NumericalIntegration: integrate, SimpsonEven +import Luna.Modes: hquadrature @test Ionisation.ionrate_ADK(:He, 1e10) ≈ 1.2416371415312408e-18 @test Ionisation.ionrate_ADK(:He, 2e10) ≈ 1.0772390893742478 @@ -11,13 +12,13 @@ import NumericalIntegration: integrate, SimpsonEven E = collect(range(1e9, 1e11; length=32)) @test Ionisation.ionrate_ADK(:He, E) == Ionisation.ionrate_ADK(:He, -E) -@test isapprox(Ionisation.ionrate_PPT(:He, 800e-9, 1e10), 1.4130113877738475e-5, rtol=1e-3) -@test isapprox(Ionisation.ionrate_PPT(:He, 800e-9, 1.3e10), 0.04585332982943, rtol=1e-3) +@test isapprox(Ionisation.ionrate_PPT(:He, 800e-9, 1e10), 2*1.40432138471583e-5, rtol=1e-3) +@test isapprox(Ionisation.ionrate_PPT(:He, 800e-9, 1.3e10), 2*0.04517809797503506, rtol=1e-3) Emin = 1e9 Emax = 1e11 -N = 2^10 -E = collect(range(Emin, stop=Emax, length=N)) +N = 2^9 +E = collect(range(Emin, Emax, N)) rate = Ionisation.ionrate_PPT.(:He, 800e-9, E) ifun(E0) = E0 <= Emin ? 2 : E0 >= Emax ? N : @@ -74,73 +75,4 @@ ratefun!(outneg, -E) adk_avg_kw = Ionisation.ionrate_ADK.(gas, E0; cycle_average=true) @test all(isapprox.(adk_avg, adk_avg_kw; rtol=0.05)) end -## -# this_folder = dirname(@__FILE__) -# import CSV -# import PythonPlot: pyplot, pygui -# pygui(true) -# #### Ilkov et al -# k = collect(range(8, stop=12, length=200)) -# E = 10 .^ k -# ppt = Ionisation.ionrate_PPT(:He, 10.6e-6, E) -# ppt_cycavg = Ionisation.ionrate_PPT(:He, 10.6e-6, E, rcycle=false) -# adk = Ionisation.ionrate_ADK(:He, E) - -# dat = CSV.read(joinpath(this_folder, "Ilkov_PPT_He.csv")) -# dat = convert(Matrix, dat) # ionrate [1/s] vs field [V/cm] - -# pyplot.figure() -# pyplot.loglog(E, ppt, label="PPT") -# pyplot.loglog(E, ppt_cycavg, label="PPT cycle averaged") -# pyplot.loglog(E, adk, label="ADK") -# pyplot.loglog(dat[:, 1].*100, dat[:, 2], label="Ilkov et al. PPT") -# pyplot.xlim(1e9, 1e12) -# pyplot.ylim(1, 1e18) -# pyplot.legend() - -# ### Chang, Fundamentals of Attosecond Optics -# dat = CSV.read(joinpath(this_folder, "Chang_PPT.csv")) -# dat = convert(Matrix, dat) # ionrate [1/fs] vs intensity [1e14 W/cm^2] -# intensity = range(0.1, 25; length=1000) * 1e18 # W/m^2 -# E = Tools.intensity_to_field.(intensity) -# ppt = Ionisation.ionrate_PPT(:He, 390e-9, E) -# ppt_cycavg = Ionisation.ionrate_PPT(:He, 390e-9, E, rcycle=false) -# adk = Ionisation.ionrate_ADK(:He, E) - -# s = sortperm(dat[:, 1]) - -# pyplot.figure() -# pyplot.loglog(intensity*1e-18, ppt*1e-15, label="PPT") -# pyplot.loglog(intensity*1e-18, adk*1e-15, label="ADK") -# pyplot.loglog(intensity*1e-18, ppt_cycavg*1e-15, label="PPT cycle averaged") -# pyplot.loglog(dat[s, 1], dat[s, 2], label="Chang PPT") -# pyplot.ylim(1e-14, 1) -# pyplot.xlim(extrema(intensity.*1e-18)) -# pyplot.legend() -# pyplot.xlabel("Intensity (10\$^{14}\$ W/cm\$^2\$)") -# pyplot.ylabel("Ionisation rate (1/fs)") - -### Couairon -# Ip = 12.063 * PhysData.electron -# dat = CSV.read(joinpath(this_folder, "Couairon_PPT.csv")) -# dat = convert(Matrix, dat) # ionrate [1/s] vs intensity [W/cm^2] -# k = collect(range(12, 15, length=500)) -# intensity = 10 .^ k .* 1e4 # W/m^2 -# E = Tools.intensity_to_field.(intensity) -# ppt = Ionisation.ionrate_PPT(Ip, 800e-9, 1, 1, E) -# ppt_cycavg = Ionisation.ionrate_PPT(Ip, 800e-9, 1, 1, E; rcycle=false) -# adk = Ionisation.ionrate_ADK(Ip, E) - -# s = sortperm(dat[:, 1]) -# pyplot.figure() -# pyplot.loglog(intensity*1e-4, ppt, label="PPT") -# pyplot.loglog(intensity*1e-4, adk, label="ADK") -# pyplot.loglog(intensity*1e-4, ppt_cycavg, label="PPT cycle averaged") -# pyplot.loglog(dat[s, 1], dat[s, 2], label="Couairon PPT") -# pyplot.ylim(1, 1e17) -# pyplot.xlim(extrema(intensity.*1e-4)) -# pyplot.legend() -# pyplot.xlabel("Intensity (W/cm\$^2\$)") -# pyplot.ylabel("Ionisation rate (1/s)") -# pyplot.title("O\$_2\$ ionisation at 800 nm") \ No newline at end of file diff --git a/test/test_maths.jl b/test/test_maths.jl index 9d010868..75c84234 100644 --- a/test/test_maths.jl +++ b/test/test_maths.jl @@ -1,7 +1,7 @@ import Test: @test, @testset, @test_throws, @test_broken import Luna: Maths, Grid, Fields -import Dierckx -import HCubature: hquadrature +import Luna.Maths: Dierckx +import Luna.Modes: hquadrature import Random: seed! import FFTW @@ -210,7 +210,7 @@ end x = range(0.0, 2π, length=100) y = sin.(x) spl = Maths.BSpline(x, y) - @test all(abs.(spl.(x) .- y) .< 3e-16) + @test all(abs.(spl.(x) .- y) .< 3.5e-16) x2 = range(0.0, 2π, length=300) @test maximum(spl.(x2) - sin.(x2)) < 5e-8 # these use the actual spline derivative @@ -234,7 +234,7 @@ end @test abs(Maths.derivative(splc, 1.3, 1) - complex(cos(1.3), cos(1.3 + π/6))) < 2.5e-7 @test abs(Maths.derivative(splc, 1.3, 2) - complex(-sin(1.3), -sin(1.3 + π/6))) < 2.5e-3 # test Julia evaluation vs original Dierckx - @test all(spl.(x2) .== spl.rspl.(x2)) + @test all(spl.(x2) .≈ spl.rspl.(x2)) # test full spline Derivatives spl1 = Maths.differentiate_spline(spl, 1) @test maximum(abs.(cos.(x2) .- spl1.(x2))) < 5.1e-6 @@ -266,19 +266,19 @@ end @testset "randgauss" begin import Statistics: std, mean seed!(1234) - x = Maths.randgauss(1, 0.5, 1000000) + x = Maths.randgauss(1, 0.5, 10000000) @test isapprox(std(x), 0.5, rtol=1e-3) @test isapprox(mean(x), 1, rtol=1e-3) seed!(1234) - x = Maths.randgauss(10, 0.1, 1000000) + x = Maths.randgauss(10, 0.1, 10000000) @test isapprox(std(x), 0.1, rtol=1e-3) @test isapprox(mean(x), 10, rtol=1e-3) seed!(1234) - x = Maths.randgauss(-1, 0.5, 1000000) + x = Maths.randgauss(-1, 0.5, 10000000) @test isapprox(std(x), 0.5, rtol=1e-3) @test isapprox(mean(x), -1, rtol=1e-3) seed!(1234) - x = Maths.randgauss(1, 0.5, (1000, 1000)) + x = Maths.randgauss(1, 0.5, (10000, 10000)) @test isapprox(std(x), 0.5, rtol=1e-3) @test isapprox(mean(x), 1, rtol=1e-3) end diff --git a/test/test_processing.jl b/test/test_processing.jl index 23cfb2b0..7eea30ee 100644 --- a/test/test_processing.jl +++ b/test/test_processing.jl @@ -1,6 +1,7 @@ import Test: @test, @testset import FFTW -import Luna: Grid, Processing, Maths, Fields, settings, PhysData +using Luna +import Luna: settings import Luna.PhysData: wlfreq import NumericalIntegration: integrate @@ -275,3 +276,21 @@ end end end +@testset "peak power modal sum" begin + λ0 = 800e-9 + τfwhm = 10e-15 + pHE11 = 1e8 + pHE12 = 5e7 + + pulses = [ + Pulses.GaussPulse(;λ0, τfwhm, power=pHE11, mode=:HE11) + Pulses.GaussPulse(;λ0, τfwhm, power=pHE12, mode=:HE12) + ] + + out = prop_capillary(100e-6, 0.2, :He, 1e-3; λ0, pulses, λlims=(70e-9, 4e-6), trange=1e-12, modes=4) + + @test all(isapprox.(Processing.peakpower(out)[1:2, 1], [pHE11, pHE12]; rtol=1e-4)) + @test isapprox(Processing.peakpower(out; sumdims=2)[1], pHE11+pHE12; rtol=1e-4) + + @test isapprox(out["stats"]["peakpower_allmodes"][1], pHE11+pHE12; rtol=1e-4) +end \ No newline at end of file diff --git a/test/test_scans.jl b/test/test_scans.jl index b427aa63..c4583c5a 100644 --- a/test/test_scans.jl +++ b/test/test_scans.jl @@ -2,6 +2,7 @@ import Test: @test, @testset, @test_throws using Luna import HDF5 using Distributed +import Logging: with_logger, NullLogger @testset "Chunking" begin function contains_all_unique(chunks, x) @@ -142,7 +143,7 @@ rm.(files) end ## -if ~Sys.iswindows() +if ~("GITHUB_ACTIONS" in keys(ENV)) @testset "multi-process queue scan" begin ps = addprocs(2) @everywhere using Luna @@ -170,10 +171,11 @@ if ~Sys.iswindows() end rmprocs(ps) end - +## @testset "multi-process queue scan with error" begin ps = addprocs(2) @everywhere using Luna + @everywhere import Logging: with_logger, NullLogger # do it again but with one process giving an error scanname = "scantest_queue_multiproc_err" function worker_err() @@ -181,12 +183,14 @@ end scan = Scan(scanname, Scans.QueueExec(); energy=energies) idcs_run = Int[] runscan(scan) do scanidx, energy - prop_capillary(125e-6, 3, :He, 0.8; λ0=800e-9, τfwhm=10e-15, energy=energy, - trange=400e-15, λlims=(200e-9, 4e-6)) - if scanidx == 16 - error("This exception is expected as part of the test suite") + with_logger(NullLogger()) do + prop_capillary(125e-6, 3, :He, 0.8; λ0=800e-9, τfwhm=10e-15, energy=energy, + trange=400e-15, λlims=(200e-9, 4e-6)) + if scanidx == 16 + error("This exception is expected as part of the test suite") + end + push!(idcs_run, scanidx) end - push!(idcs_run, scanidx) end idcs_run end @@ -245,7 +249,7 @@ end @test length(readdir(td)) == 2length(energies) rm(td; recursive=true) end -end # if ~Sys.iswindows() +end # if ~("GITHUB_ACTIONS" in keys(ENV)) ## @testset "automatic ScanHDF5Output in prop_capillary scan" begin diff --git a/test/test_utils.jl b/test/test_utils.jl index c9c08437..edeccab2 100644 --- a/test/test_utils.jl +++ b/test/test_utils.jl @@ -1,5 +1,5 @@ import Test: @test, @testset, @test_throws -import Luna: Utils, @hlock +import Luna: Utils import HDF5 import Dates @@ -17,7 +17,7 @@ isfile(fpath) && rm(fpath) isdir(dirname(fpath)) || mkpath(dirname(fpath)) Utils.save_dict_h5(fpath, d) @test_throws ErrorException Utils.save_dict_h5(fpath, d, force=false) -@hlock HDF5.h5open(fpath) do file +HDF5.h5open(fpath) do file for k in ["float", "float[]", "string"] @test d[k] == read(file[k]) end