diff --git a/CHANGELOG.md b/CHANGELOG.md index e2e7fe99..88b5e778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,8 @@ The first public release. It contains: the generalised nonlinear Schrödinger equation (GNLSE), for the real electric field or a complex envelope, in waveguides and in free space. - **Waveguides.** - - Gas-filled hollow capillaries, mode-averaged or with resolved modes. Tapers, + - Gas-filled hollow capillaries, with resolved modes (the default) or mode-averaged + (`mode_average=True` on `prop_capillary` and `prop_waveguide`). Tapers, pressure gradients and gas flow are supported, as is a coherent wall that couples the modes. - Antiresonant hollow-core fibres (the Vincetti model). diff --git a/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.codeobj.json b/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.codeobj.json index 9b940b36..e230b663 100644 --- a/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.codeobj.json +++ b/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.codeobj.json @@ -89,50 +89,6 @@ "name": "int" } ], - "Simulation.from_capillary": [ - { - "is_class": true, - "is_explicit": false, - "module": "solprop.driver.simulation", - "module_short": "solprop.driver", - "name": "Simulation.from_capillary" - }, - { - "is_class": true, - "is_explicit": false, - "module": "solprop.driver", - "module_short": "solprop.driver", - "name": "Simulation.from_capillary" - }, - { - "is_class": true, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Simulation.from_capillary" - }, - { - "is_class": true, - "is_explicit": false, - "module": "solprop._display", - "module_short": "solprop._display", - "name": "Displayed.from_capillary" - }, - { - "is_class": true, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Displayed.from_capillary" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver.Simulation", - "module_short": "solprop.driver.Simulation", - "name": "from_capillary" - } - ], "ax": [ { "is_class": false, @@ -1009,22 +965,6 @@ "name": "ndarray" } ], - "default_spec": [ - { - "is_class": false, - "is_explicit": false, - "module": "builtins", - "module_short": "builtins", - "name": "function" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop.stats", - "module_short": "solprop.stats", - "name": "default_spec" - } - ], "density": [ { "is_class": false, @@ -1535,7 +1475,7 @@ "name": "subplots" } ], - "replace": [ + "prop_capillary": [ { "is_class": false, "is_explicit": false, @@ -1546,9 +1486,9 @@ { "is_class": false, "is_explicit": false, - "module": "dataclasses", - "module_short": "dataclasses", - "name": "replace" + "module": "solprop.driver", + "module_short": "solprop.driver", + "name": "prop_capillary" } ], "rin": [ @@ -1767,140 +1707,6 @@ "name": "ndarray" } ], - "simulation": [ - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver.simulation", - "module_short": "solprop.driver", - "name": "Simulation" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver", - "module_short": "solprop.driver", - "name": "Simulation" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Simulation" - } - ], - "simulation.run": [ - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver.simulation", - "module_short": "solprop.driver", - "name": "Simulation.run" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver", - "module_short": "solprop.driver", - "name": "Simulation.run" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Simulation.run" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop._display", - "module_short": "solprop._display", - "name": "Displayed.run" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Displayed.run" - } - ], - "simulation.settings": [ - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver.simulation", - "module_short": "solprop.driver", - "name": "Simulation.settings" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver", - "module_short": "solprop.driver", - "name": "Simulation.settings" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Simulation.settings" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop._display", - "module_short": "solprop._display", - "name": "Displayed.settings" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Displayed.settings" - } - ], - "simulation.translate": [ - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver.simulation", - "module_short": "solprop.driver", - "name": "Simulation.translate" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver", - "module_short": "solprop.driver", - "name": "Simulation.translate" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Simulation.translate" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop._display", - "module_short": "solprop._display", - "name": "Displayed.translate" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Displayed.translate" - } - ], "spectra": [ { "is_class": false, diff --git a/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.ipynb b/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.ipynb index 35d23d57..e22b826d 100644 --- a/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.ipynb +++ b/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## The ensemble\nEach shot is the same run with a different realisation of the quantum shot noise:\none photon per active spectral bin, with a random phase, added to the input spectrum\n(``noise=\"input\"``), drawn from the generator seeded by ``seed``.\nClearing the transverse quadrature selects mode averaging. Merely setting\n``modes=1`` would retain a resolved single-mode calculation. The assertions check\nboth the translated problem and the saved metadata so the approximation is\nrecorded with the result.\n\n" + "## The ensemble\nEach shot is the same run with a different realisation of the quantum shot noise:\none photon per active spectral bin, with a random phase, added to the input spectrum\n(``noise=\"input\"``), drawn from the generator seeded by ``seed``.\n``mode_average=True`` selects mode averaging; ``modes=1`` alone, the default, would\nretain a resolved single-mode calculation. The assertions check the saved metadata\nso the approximation is recorded with the result.\n\n" ] }, { @@ -22,7 +22,7 @@ }, "outputs": [], "source": [ - "import time\nimport warnings\nfrom dataclasses import replace\n\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nfrom solprop.constants import C\nfrom solprop.driver import Simulation\nfrom solprop.exceptions import MaterialRangeWarning\nfrom solprop.processing import g12_of, get_spectral_density\nfrom solprop.stats import default_spec\n\nSHOTS = 8\n\"\"\"The size of the ensemble.\"\"\"\n\nstarted = time.perf_counter()\nruns = []\nwith warnings.catch_warnings():\n warnings.simplefilter(\"ignore\", MaterialRangeWarning)\n for seed in range(SHOTS):\n simulation = Simulation.from_capillary(\n 16e-6,\n 0.4,\n \"Ar\",\n 40.0,\n lambda0=1030e-9,\n fwhm=400e-15,\n energy=5e-6,\n loss=False,\n plasma=True,\n raman=False,\n trange=4e-12,\n wavelength_limits=(170e-9, 4e-6),\n noise=\"input\",\n seed=seed,\n n_save=101,\n )\n simulation.settings = replace(\n simulation.settings,\n quadrature=None,\n components=None,\n stats=default_spec(\"mode_averaged\"),\n )\n assert simulation.translate().flags.mode_averaged\n run = simulation.run(progress=\"none\")\n assert run.metadata.simulation_kind == \"mode_averaged\"\n assert run.metadata.quadrature is None\n runs.append(run)\n print(f\"seed {seed}: {run.metadata.simulation_kind}, z = {run['z'][-1]:.2f} m\")" + "import time\nimport warnings\n\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nfrom solprop.constants import C\nfrom solprop.driver import prop_capillary\nfrom solprop.exceptions import MaterialRangeWarning\nfrom solprop.processing import g12_of, get_spectral_density\n\nSHOTS = 8\n\"\"\"The size of the ensemble.\"\"\"\n\nstarted = time.perf_counter()\nruns = []\nwith warnings.catch_warnings():\n warnings.simplefilter(\"ignore\", MaterialRangeWarning)\n for seed in range(SHOTS):\n run = prop_capillary(\n 16e-6,\n 0.4,\n \"Ar\",\n 40.0,\n lambda0=1030e-9,\n fwhm=400e-15,\n energy=5e-6,\n loss=False,\n plasma=True,\n raman=False,\n trange=4e-12,\n wavelength_limits=(170e-9, 4e-6),\n noise=\"input\",\n seed=seed,\n n_save=101,\n mode_average=True,\n progress=\"none\",\n )\n assert run.metadata.simulation_kind == \"mode_averaged\"\n assert run.metadata.quadrature is None\n runs.append(run)\n print(f\"seed {seed}: {run.metadata.simulation_kind}, z = {run['z'][-1]:.2f} m\")" ] }, { diff --git a/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.py b/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.py index 42ff3545..c32e78cf 100644 --- a/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.py +++ b/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.py @@ -53,23 +53,20 @@ # Each shot is the same run with a different realisation of the quantum shot noise: # one photon per active spectral bin, with a random phase, added to the input spectrum # (``noise="input"``), drawn from the generator seeded by ``seed``. -# Clearing the transverse quadrature selects mode averaging. Merely setting -# ``modes=1`` would retain a resolved single-mode calculation. The assertions check -# both the translated problem and the saved metadata so the approximation is -# recorded with the result. +# ``mode_average=True`` selects mode averaging; ``modes=1`` alone, the default, would +# retain a resolved single-mode calculation. The assertions check the saved metadata +# so the approximation is recorded with the result. import time import warnings -from dataclasses import replace import matplotlib.pyplot as plt import numpy as np from solprop.constants import C -from solprop.driver import Simulation +from solprop.driver import prop_capillary from solprop.exceptions import MaterialRangeWarning from solprop.processing import g12_of, get_spectral_density -from solprop.stats import default_spec SHOTS = 8 """The size of the ensemble.""" @@ -79,7 +76,7 @@ with warnings.catch_warnings(): warnings.simplefilter("ignore", MaterialRangeWarning) for seed in range(SHOTS): - simulation = Simulation.from_capillary( + run = prop_capillary( 16e-6, 0.4, "Ar", @@ -95,15 +92,9 @@ noise="input", seed=seed, n_save=101, + mode_average=True, + progress="none", ) - simulation.settings = replace( - simulation.settings, - quadrature=None, - components=None, - stats=default_spec("mode_averaged"), - ) - assert simulation.translate().flags.mode_averaged - run = simulation.run(progress="none") assert run.metadata.simulation_kind == "mode_averaged" assert run.metadata.quadrature is None runs.append(run) diff --git a/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.py.md5 b/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.py.md5 index 71ef4ba7..8b82baae 100644 --- a/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.py.md5 +++ b/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.py.md5 @@ -1 +1 @@ -814fb0daa5794e3950a30c26755b6a26 \ No newline at end of file +4bfd22e913423f4b067115cb97c2aca8 \ No newline at end of file diff --git a/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.rst b/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.rst index 4dc2dbc2..31f33b3a 100644 --- a/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.rst +++ b/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.rst @@ -61,35 +61,32 @@ for the noise convention and estimator see :doc:`/guide/media/noise` and :doc:`/introduction/lessons/plot_01_rdw` follows a shorter pulse whose broadening is organised by self-compression and dispersive-wave emission. -.. GENERATED FROM PYTHON SOURCE LINES 51-60 +.. GENERATED FROM PYTHON SOURCE LINES 51-59 The ensemble ------------ Each shot is the same run with a different realisation of the quantum shot noise: one photon per active spectral bin, with a random phase, added to the input spectrum (``noise="input"``), drawn from the generator seeded by ``seed``. -Clearing the transverse quadrature selects mode averaging. Merely setting -``modes=1`` would retain a resolved single-mode calculation. The assertions check -both the translated problem and the saved metadata so the approximation is -recorded with the result. +``mode_average=True`` selects mode averaging; ``modes=1`` alone, the default, would +retain a resolved single-mode calculation. The assertions check the saved metadata +so the approximation is recorded with the result. -.. GENERATED FROM PYTHON SOURCE LINES 60-112 +.. GENERATED FROM PYTHON SOURCE LINES 59-103 .. code-block:: Python import time import warnings - from dataclasses import replace import matplotlib.pyplot as plt import numpy as np from solprop.constants import C - from solprop.driver import Simulation + from solprop.driver import prop_capillary from solprop.exceptions import MaterialRangeWarning from solprop.processing import g12_of, get_spectral_density - from solprop.stats import default_spec SHOTS = 8 """The size of the ensemble.""" @@ -99,7 +96,7 @@ recorded with the result. with warnings.catch_warnings(): warnings.simplefilter("ignore", MaterialRangeWarning) for seed in range(SHOTS): - simulation = Simulation.from_capillary( + run = prop_capillary( 16e-6, 0.4, "Ar", @@ -115,15 +112,9 @@ recorded with the result. noise="input", seed=seed, n_save=101, + mode_average=True, + progress="none", ) - simulation.settings = replace( - simulation.settings, - quadrature=None, - components=None, - stats=default_spec("mode_averaged"), - ) - assert simulation.translate().flags.mode_averaged - run = simulation.run(progress="none") assert run.metadata.simulation_kind == "mode_averaged" assert run.metadata.quadrature is None runs.append(run) @@ -149,13 +140,13 @@ recorded with the result. -.. GENERATED FROM PYTHON SOURCE LINES 113-116 +.. GENERATED FROM PYTHON SOURCE LINES 104-107 The averaged spectrum along the fibre ------------------------------------- The spectral energy density averaged over the ensemble, in decibels below its peak. -.. GENERATED FROM PYTHON SOURCE LINES 116-145 +.. GENERATED FROM PYTHON SOURCE LINES 107-136 .. code-block:: Python @@ -200,7 +191,7 @@ The spectral energy density averaged over the ensemble, in decibels below its pe -.. GENERATED FROM PYTHON SOURCE LINES 146-153 +.. GENERATED FROM PYTHON SOURCE LINES 137-144 Noise and coherence at the exit ------------------------------- @@ -210,7 +201,7 @@ its mean, shot to shot; and the first-order coherence :math:`|g_{12}^{(1)}(\lambda)|` of the ensemble's complex spectra (0 for independent fields, 1 for identical ones). -.. GENERATED FROM PYTHON SOURCE LINES 153-192 +.. GENERATED FROM PYTHON SOURCE LINES 144-183 .. code-block:: Python @@ -265,7 +256,7 @@ independent fields, 1 for identical ones). -.. GENERATED FROM PYTHON SOURCE LINES 193-205 +.. GENERATED FROM PYTHON SOURCE LINES 184-196 In this 0.4 m calculation, broadening becomes pronounced around 0.2 m on the displayed 40 dB scale. The exit spectrum spans the visible and near infrared, @@ -280,7 +271,7 @@ Repeat with more seeds before using these curves as quantitative ensemble statistics. Check temporal sampling and band limits independently of the propagation tolerance; see :doc:`/guide/setup/grids`. -.. GENERATED FROM PYTHON SOURCE LINES 205-210 +.. GENERATED FROM PYTHON SOURCE LINES 196-201 .. code-block:: Python @@ -297,7 +288,7 @@ propagation tolerance; see :doc:`/guide/setup/grids`. .. code-block:: none - 620 s for 8 shots; backend native + 716 s for 8 shots; backend native @@ -305,7 +296,7 @@ propagation tolerance; see :doc:`/guide/setup/grids`. .. rst-class:: sphx-glr-timing - **Total running time of the script:** (10 minutes 19.772 seconds) + **Total running time of the script:** (11 minutes 56.193 seconds) .. _sphx_glr_download_auto_examples_supercontinuum_plot_03_modulational_instability_supercontinuum.py: diff --git a/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.zip b/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.zip index 41f0227a..a1f514f2 100644 Binary files a/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.zip and b/docs/auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.zip differ diff --git a/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.codeobj.json b/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.codeobj.json index 833f710d..f69366f0 100644 --- a/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.codeobj.json +++ b/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.codeobj.json @@ -64,50 +64,6 @@ "name": "MaterialRangeWarning" } ], - "Simulation.from_capillary": [ - { - "is_class": true, - "is_explicit": false, - "module": "solprop.driver.simulation", - "module_short": "solprop.driver", - "name": "Simulation.from_capillary" - }, - { - "is_class": true, - "is_explicit": false, - "module": "solprop.driver", - "module_short": "solprop.driver", - "name": "Simulation.from_capillary" - }, - { - "is_class": true, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Simulation.from_capillary" - }, - { - "is_class": true, - "is_explicit": false, - "module": "solprop._display", - "module_short": "solprop._display", - "name": "Displayed.from_capillary" - }, - { - "is_class": true, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Displayed.from_capillary" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver.Simulation", - "module_short": "solprop.driver.Simulation", - "name": "from_capillary" - } - ], "ax": [ { "is_class": false, @@ -189,22 +145,6 @@ "name": "Artist.set_title" } ], - "default_spec": [ - { - "is_class": false, - "is_explicit": false, - "module": "builtins", - "module_short": "builtins", - "name": "function" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop.stats", - "module_short": "solprop.stats", - "name": "default_spec" - } - ], "fig": [ { "is_class": false, @@ -306,7 +246,7 @@ "name": "prop_2d" } ], - "replace": [ + "prop_capillary": [ { "is_class": false, "is_explicit": false, @@ -317,9 +257,9 @@ { "is_class": false, "is_explicit": false, - "module": "dataclasses", - "module_short": "dataclasses", - "name": "replace" + "module": "solprop.driver", + "module_short": "solprop.driver", + "name": "prop_capillary" } ], "run": [ @@ -540,140 +480,6 @@ "name": "ABC.metadata" } ], - "simulation": [ - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver.simulation", - "module_short": "solprop.driver", - "name": "Simulation" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver", - "module_short": "solprop.driver", - "name": "Simulation" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Simulation" - } - ], - "simulation.run": [ - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver.simulation", - "module_short": "solprop.driver", - "name": "Simulation.run" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver", - "module_short": "solprop.driver", - "name": "Simulation.run" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Simulation.run" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop._display", - "module_short": "solprop._display", - "name": "Displayed.run" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Displayed.run" - } - ], - "simulation.settings": [ - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver.simulation", - "module_short": "solprop.driver", - "name": "Simulation.settings" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver", - "module_short": "solprop.driver", - "name": "Simulation.settings" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Simulation.settings" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop._display", - "module_short": "solprop._display", - "name": "Displayed.settings" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Displayed.settings" - } - ], - "simulation.translate": [ - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver.simulation", - "module_short": "solprop.driver", - "name": "Simulation.translate" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop.driver", - "module_short": "solprop.driver", - "name": "Simulation.translate" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Simulation.translate" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop._display", - "module_short": "solprop._display", - "name": "Displayed.translate" - }, - { - "is_class": false, - "is_explicit": false, - "module": "solprop", - "module_short": "solprop", - "name": "Displayed.translate" - } - ], "spec_1d": [ { "is_class": false, diff --git a/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.ipynb b/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.ipynb index 794ebfe5..e8354384 100644 --- a/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.ipynb +++ b/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Set up and run the model\n``Simulation.from_capillary`` assembles the material and pulse choices. We then\nremove its transverse quadrature and request the mode-averaged statistics before\ntranslating or running. ``modes=1`` on its own would select a resolved single\nmode, which is a different nonlinear approximation.\n\n" + "## Set up and run the model\n``prop_capillary`` assembles the material and pulse choices, and\n``mode_average=True`` selects mode averaging. ``modes=1`` on its own, the default,\nwould select a resolved single mode, which is a different nonlinear approximation.\n\n" ] }, { @@ -22,7 +22,7 @@ }, "outputs": [], "source": [ - "import time\nimport warnings\nfrom dataclasses import replace\n\nimport matplotlib.pyplot as plt\n\nfrom solprop.driver import Simulation\nfrom solprop.exceptions import MaterialRangeWarning\nfrom solprop.plotting import prop_2d, spec_1d\nfrom solprop.stats import default_spec\n\nstarted = time.perf_counter()\nwith warnings.catch_warnings():\n warnings.simplefilter(\"ignore\", MaterialRangeWarning)\n simulation = Simulation.from_capillary(\n 16e-6,\n 0.4,\n \"H2\",\n 40.0,\n lambda0=515e-9,\n fwhm=400e-15,\n energy=10e-6,\n loss=False,\n plasma=True,\n raman=True,\n trange=4e-12,\n wavelength_limits=(170e-9, 4e-6),\n noise=\"input\",\n seed=0,\n n_save=201,\n )\n simulation.settings = replace(\n simulation.settings,\n quadrature=None,\n components=None,\n stats=default_spec(\"mode_averaged\"),\n )\n assert simulation.translate().flags.mode_averaged\n run = simulation.run(progress=\"none\")\nassert run.metadata.simulation_kind == \"mode_averaged\"\nassert run.metadata.quadrature is None\nprint(f\"{run.metadata.simulation_kind}, z = {run['z'][-1]:.2f} m; seed 0\")" + "import time\nimport warnings\n\nimport matplotlib.pyplot as plt\n\nfrom solprop.driver import prop_capillary\nfrom solprop.exceptions import MaterialRangeWarning\nfrom solprop.plotting import prop_2d, spec_1d\n\nstarted = time.perf_counter()\nwith warnings.catch_warnings():\n warnings.simplefilter(\"ignore\", MaterialRangeWarning)\n run = prop_capillary(\n 16e-6,\n 0.4,\n \"H2\",\n 40.0,\n lambda0=515e-9,\n fwhm=400e-15,\n energy=10e-6,\n loss=False,\n plasma=True,\n raman=True,\n trange=4e-12,\n wavelength_limits=(170e-9, 4e-6),\n noise=\"input\",\n seed=0,\n n_save=201,\n mode_average=True,\n progress=\"none\",\n )\nassert run.metadata.simulation_kind == \"mode_averaged\"\nassert run.metadata.quadrature is None\nprint(f\"{run.metadata.simulation_kind}, z = {run['z'][-1]:.2f} m; seed 0\")" ] }, { diff --git a/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.py b/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.py index 58d51cc9..05346086 100644 --- a/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.py +++ b/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.py @@ -47,26 +47,23 @@ # %% # Set up and run the model # ------------------------ -# ``Simulation.from_capillary`` assembles the material and pulse choices. We then -# remove its transverse quadrature and request the mode-averaged statistics before -# translating or running. ``modes=1`` on its own would select a resolved single -# mode, which is a different nonlinear approximation. +# ``prop_capillary`` assembles the material and pulse choices, and +# ``mode_average=True`` selects mode averaging. ``modes=1`` on its own, the default, +# would select a resolved single mode, which is a different nonlinear approximation. import time import warnings -from dataclasses import replace import matplotlib.pyplot as plt -from solprop.driver import Simulation +from solprop.driver import prop_capillary from solprop.exceptions import MaterialRangeWarning from solprop.plotting import prop_2d, spec_1d -from solprop.stats import default_spec started = time.perf_counter() with warnings.catch_warnings(): warnings.simplefilter("ignore", MaterialRangeWarning) - simulation = Simulation.from_capillary( + run = prop_capillary( 16e-6, 0.4, "H2", @@ -82,15 +79,9 @@ noise="input", seed=0, n_save=201, + mode_average=True, + progress="none", ) - simulation.settings = replace( - simulation.settings, - quadrature=None, - components=None, - stats=default_spec("mode_averaged"), - ) - assert simulation.translate().flags.mode_averaged - run = simulation.run(progress="none") assert run.metadata.simulation_kind == "mode_averaged" assert run.metadata.quadrature is None print(f"{run.metadata.simulation_kind}, z = {run['z'][-1]:.2f} m; seed 0") diff --git a/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.py.md5 b/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.py.md5 index a61fe9b4..dbdbc1a2 100644 --- a/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.py.md5 +++ b/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.py.md5 @@ -1 +1 @@ -f05bd3790bff5912bcdffdcdbfbaa383 \ No newline at end of file +c8c1e620bfe7c3f0b549aa9ec0829db5 \ No newline at end of file diff --git a/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.rst b/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.rst index 41ef2e23..5c296f42 100644 --- a/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.rst +++ b/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.rst @@ -58,35 +58,32 @@ nitrogen-filled hollow-core anti-resonant fiber `_; its gas and pump conditions differ from this hydrogen calculation. -.. GENERATED FROM PYTHON SOURCE LINES 48-54 +.. GENERATED FROM PYTHON SOURCE LINES 48-53 Set up and run the model ------------------------ -``Simulation.from_capillary`` assembles the material and pulse choices. We then -remove its transverse quadrature and request the mode-averaged statistics before -translating or running. ``modes=1`` on its own would select a resolved single -mode, which is a different nonlinear approximation. +``prop_capillary`` assembles the material and pulse choices, and +``mode_average=True`` selects mode averaging. ``modes=1`` on its own, the default, +would select a resolved single mode, which is a different nonlinear approximation. -.. GENERATED FROM PYTHON SOURCE LINES 54-98 +.. GENERATED FROM PYTHON SOURCE LINES 53-89 .. code-block:: Python import time import warnings - from dataclasses import replace import matplotlib.pyplot as plt - from solprop.driver import Simulation + from solprop.driver import prop_capillary from solprop.exceptions import MaterialRangeWarning from solprop.plotting import prop_2d, spec_1d - from solprop.stats import default_spec started = time.perf_counter() with warnings.catch_warnings(): warnings.simplefilter("ignore", MaterialRangeWarning) - simulation = Simulation.from_capillary( + run = prop_capillary( 16e-6, 0.4, "H2", @@ -102,15 +99,9 @@ mode, which is a different nonlinear approximation. noise="input", seed=0, n_save=201, + mode_average=True, + progress="none", ) - simulation.settings = replace( - simulation.settings, - quadrature=None, - components=None, - stats=default_spec("mode_averaged"), - ) - assert simulation.translate().flags.mode_averaged - run = simulation.run(progress="none") assert run.metadata.simulation_kind == "mode_averaged" assert run.metadata.quadrature is None print(f"{run.metadata.simulation_kind}, z = {run['z'][-1]:.2f} m; seed 0") @@ -128,12 +119,12 @@ mode, which is a different nonlinear approximation. -.. GENERATED FROM PYTHON SOURCE LINES 99-101 +.. GENERATED FROM PYTHON SOURCE LINES 90-92 Spectral and temporal evolution ------------------------------- -.. GENERATED FROM PYTHON SOURCE LINES 101-115 +.. GENERATED FROM PYTHON SOURCE LINES 92-106 .. code-block:: Python @@ -163,14 +154,14 @@ Spectral and temporal evolution -.. GENERATED FROM PYTHON SOURCE LINES 116-120 +.. GENERATED FROM PYTHON SOURCE LINES 107-111 The comb and the continuum -------------------------- The four positions lie within the 0.4 m calculation. Compare peak locations as well as their broadening: a logarithmic plot can make weak features prominent. -.. GENERATED FROM PYTHON SOURCE LINES 120-129 +.. GENERATED FROM PYTHON SOURCE LINES 111-120 .. code-block:: Python @@ -201,7 +192,7 @@ well as their broadening: a logarithmic plot can make weak features prominent. -.. GENERATED FROM PYTHON SOURCE LINES 130-141 +.. GENERATED FROM PYTHON SOURCE LINES 121-132 The early slices retain conspicuous discrete peaks. By 0.4 m, broadened components overlap across much of the visible and near infrared, with weaker @@ -215,7 +206,7 @@ A quantitative noise study needs repeated independent seeds, as in the MI ensemble. For sampling and material checks see :doc:`/guide/setup/grids` and :doc:`/guide/media/materials`. -.. GENERATED FROM PYTHON SOURCE LINES 141-143 +.. GENERATED FROM PYTHON SOURCE LINES 132-134 .. code-block:: Python @@ -229,7 +220,7 @@ ensemble. For sampling and material checks see :doc:`/guide/setup/grids` and .. code-block:: none - 202 s; backend native + 152 s; backend native @@ -237,7 +228,7 @@ ensemble. For sampling and material checks see :doc:`/guide/setup/grids` and .. rst-class:: sphx-glr-timing - **Total running time of the script:** (3 minutes 22.033 seconds) + **Total running time of the script:** (2 minutes 32.458 seconds) .. _sphx_glr_download_auto_examples_supercontinuum_plot_04_raman_comb_supercontinuum.py: diff --git a/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.zip b/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.zip index d18dbba7..0ad68e14 100644 Binary files a/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.zip and b/docs/auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.zip differ diff --git a/docs/examples/supercontinuum/plot_03_modulational_instability_supercontinuum.py b/docs/examples/supercontinuum/plot_03_modulational_instability_supercontinuum.py index 42ff3545..c32e78cf 100644 --- a/docs/examples/supercontinuum/plot_03_modulational_instability_supercontinuum.py +++ b/docs/examples/supercontinuum/plot_03_modulational_instability_supercontinuum.py @@ -53,23 +53,20 @@ # Each shot is the same run with a different realisation of the quantum shot noise: # one photon per active spectral bin, with a random phase, added to the input spectrum # (``noise="input"``), drawn from the generator seeded by ``seed``. -# Clearing the transverse quadrature selects mode averaging. Merely setting -# ``modes=1`` would retain a resolved single-mode calculation. The assertions check -# both the translated problem and the saved metadata so the approximation is -# recorded with the result. +# ``mode_average=True`` selects mode averaging; ``modes=1`` alone, the default, would +# retain a resolved single-mode calculation. The assertions check the saved metadata +# so the approximation is recorded with the result. import time import warnings -from dataclasses import replace import matplotlib.pyplot as plt import numpy as np from solprop.constants import C -from solprop.driver import Simulation +from solprop.driver import prop_capillary from solprop.exceptions import MaterialRangeWarning from solprop.processing import g12_of, get_spectral_density -from solprop.stats import default_spec SHOTS = 8 """The size of the ensemble.""" @@ -79,7 +76,7 @@ with warnings.catch_warnings(): warnings.simplefilter("ignore", MaterialRangeWarning) for seed in range(SHOTS): - simulation = Simulation.from_capillary( + run = prop_capillary( 16e-6, 0.4, "Ar", @@ -95,15 +92,9 @@ noise="input", seed=seed, n_save=101, + mode_average=True, + progress="none", ) - simulation.settings = replace( - simulation.settings, - quadrature=None, - components=None, - stats=default_spec("mode_averaged"), - ) - assert simulation.translate().flags.mode_averaged - run = simulation.run(progress="none") assert run.metadata.simulation_kind == "mode_averaged" assert run.metadata.quadrature is None runs.append(run) diff --git a/docs/examples/supercontinuum/plot_04_raman_comb_supercontinuum.py b/docs/examples/supercontinuum/plot_04_raman_comb_supercontinuum.py index 58d51cc9..05346086 100644 --- a/docs/examples/supercontinuum/plot_04_raman_comb_supercontinuum.py +++ b/docs/examples/supercontinuum/plot_04_raman_comb_supercontinuum.py @@ -47,26 +47,23 @@ # %% # Set up and run the model # ------------------------ -# ``Simulation.from_capillary`` assembles the material and pulse choices. We then -# remove its transverse quadrature and request the mode-averaged statistics before -# translating or running. ``modes=1`` on its own would select a resolved single -# mode, which is a different nonlinear approximation. +# ``prop_capillary`` assembles the material and pulse choices, and +# ``mode_average=True`` selects mode averaging. ``modes=1`` on its own, the default, +# would select a resolved single mode, which is a different nonlinear approximation. import time import warnings -from dataclasses import replace import matplotlib.pyplot as plt -from solprop.driver import Simulation +from solprop.driver import prop_capillary from solprop.exceptions import MaterialRangeWarning from solprop.plotting import prop_2d, spec_1d -from solprop.stats import default_spec started = time.perf_counter() with warnings.catch_warnings(): warnings.simplefilter("ignore", MaterialRangeWarning) - simulation = Simulation.from_capillary( + run = prop_capillary( 16e-6, 0.4, "H2", @@ -82,15 +79,9 @@ noise="input", seed=0, n_save=201, + mode_average=True, + progress="none", ) - simulation.settings = replace( - simulation.settings, - quadrature=None, - components=None, - stats=default_spec("mode_averaged"), - ) - assert simulation.translate().flags.mode_averaged - run = simulation.run(progress="none") assert run.metadata.simulation_kind == "mode_averaged" assert run.metadata.quadrature is None print(f"{run.metadata.simulation_kind}, z = {run['z'][-1]:.2f} m; seed 0") diff --git a/docs/guide/setup/model-selection.md b/docs/guide/setup/model-selection.md index c16b324c..1b5d5854 100644 --- a/docs/guide/setup/model-selection.md +++ b/docs/guide/setup/model-selection.md @@ -28,6 +28,7 @@ stepper, the output files and the backends; they differ in what a *mode* is. | route | entry point | a mode is | use it for | |---|---|---|---| | mode-averaged GNLSE | `prop_gnlse` | an effective area and a dispersion, or a declared $\gamma$ | a single-mode fibre or a single mode of anything, fast; an envelope | +| mode-averaged gas-filled mode | `prop_capillary(..., mode_average=True)`, `prop_waveguide(..., mode_average=True)` | one gas-filled mode's effective area and dispersion, with the gas's density, Kerr, Raman and plasma | a quick single-mode capillary run, or a comparison with Luna.jl's default | | capillary / supplied gas-filled modes | `prop_capillary`, `prop_waveguide(modes, ..., gas=...)` | a transverse scalar profile of a gas-filled hollow guide on a shared quadrature | gas-filled capillaries, antiresonant fibres, rectangular guides, hollow planar (slab) guides per unit width, tabulated modes | | planar guide resolved in y | `prop_planar(grid, PlanarGrid(modes, radius_y, n_y), ...)` | a slab mode in x on every sample of a periodic y axis | planar compressors with a beam of finite height: self-focusing and modulational instability in y; energies in J | | coherent wall | `prop_capillary(..., wall=CoherentWall(...))` | the capillary's `HE_1m` channels (or one azimuthal order's two families) coupled through the glass | multimode capillaries whose higher modes carry energy, straight, tapered or in a pressure gradient | @@ -57,8 +58,8 @@ figures in their chapters ([solid-core fibres](../waveguides/solid-fibres.md), expansion, boundary and limits, are on [spatial decompositions](spatial-decompositions.md). -**Mode-averaged** (`prop_gnlse`, or a single-mode `Simulation` with no transverse -quadrature): the mode is +**Mode-averaged** (`prop_gnlse`, or `prop_capillary(..., mode_average=True)` for a +gas-filled mode): the mode is replaced by a uniform disc of the same effective area. ```{figure} ../figures/mode_averaged.svg diff --git a/docs/guide/setup/spatial-decompositions.md b/docs/guide/setup/spatial-decompositions.md index ff1fd179..67ae2c41 100644 --- a/docs/guide/setup/spatial-decompositions.md +++ b/docs/guide/setup/spatial-decompositions.md @@ -46,7 +46,7 @@ How each is reached, and where it runs: | decomposition | amplitude, energy | entry point | backends | |---|---|---|---| -| [mode-averaged](#mode-averaged-one-field-and-one-area) | √W, J | `prop_gnlse`, `Simulation.from_gnlse` | all three | +| [mode-averaged](#mode-averaged-one-field-and-one-area) | √W, J | `prop_gnlse`, `Simulation.from_gnlse`; `prop_capillary(mode_average=True)` | all three | | [capillary, radial](#capillary-modes-on-a-radial-rule) | √W, J | `prop_capillary(modes=N)`, `Simulation.from_capillary` | all three | | [capillary, full 2-D](#capillary-modes-on-the-full-cross-section) | √W, J | `prop_waveguide(modes, ..., gas=...)`, `Simulation.from_waveguide` | all three | | [coherent wall](#the-capillary-wall-two-ways) | √W, J | `prop_capillary(..., wall=CoherentWall())` | all three (CF4 on `numpy_ref` only) | @@ -124,9 +124,11 @@ rate saw. **Entry points.** `prop_gnlse` and `Simulation.from_gnlse`, with $\beta(\omega)$ and $\gamma$ or $n_2$ declared, or `fibre=` a mode or a table ([the GNLSE](../waveguides/gnlse.md), [running a simulation](running.md)). A `Simulation` whose settings carry no quadrature -builds the one-node transform for any single mode. **`prop_capillary(modes=1)` is not -mode-averaged** in solprop: it resolves its one `HE_11` channel on the radial rule -below, so its plasma sees the field on each ring. +builds the one-node transform for any single mode, and `mode_average=True` selects it on +the gas-filled routes (`prop_capillary`, `prop_waveguide` and their `Simulation` +constructors). **`prop_capillary(modes=1)` is not mode-averaged** in solprop unless +asked: it resolves its one `HE_11` channel on the radial rule below, so its plasma sees +the field on each ring. ## Capillary modes on a radial rule diff --git a/docs/guide/waveguides/mode-averaging.md b/docs/guide/waveguides/mode-averaging.md index 2fc3f28a..1a868dff 100644 --- a/docs/guide/waveguides/mode-averaging.md +++ b/docs/guide/waveguides/mode-averaging.md @@ -3,13 +3,13 @@ Mode averaging follows one temporal amplitude while replacing the transverse nonlinear response by an effective-area model. Use it when one guided mode carries the pulse and a resolved response across the core is unnecessary. It is -the spatial description used by the [GNLSE](gnlse.md) and can also be selected -for a single capillary mode. +the spatial description used by the [GNLSE](gnlse.md), and +`prop_capillary(..., mode_average=True)` selects it for a single capillary mode. A **resolved single-mode** calculation is different: it retains one propagated amplitude but evaluates the response at multiple points across that mode. -`prop_capillary(modes=1)` selects this resolved calculation. The distinction -matters for ionisation and other strongly intensity-dependent responses. +`prop_capillary(modes=1)`, the default, selects this resolved calculation. The +distinction matters for ionisation and other strongly intensity-dependent responses. The [spatial introduction](../../introduction/spatial-description.md) compares both descriptions with multimode and free-space propagation. @@ -40,17 +40,15 @@ another guided mode. Use a [multimode capillary](capillaries.md) or ## Select it explicitly -The GNLSE entry point selects mode averaging automatically. For a capillary, -construct a `Simulation` and remove the transverse quadrature. The statistics -must also be selected for the mode-averaged representation: +The GNLSE entry point selects mode averaging automatically. For a gas-filled +capillary or a supplied gas-filled mode, pass `mode_average=True` to +`prop_capillary` or `prop_waveguide`, or to `Simulation.from_capillary` or +`Simulation.from_waveguide`: ```python -from dataclasses import replace +from solprop.driver import prop_capillary -from solprop.driver import Simulation -from solprop.stats import default_spec - -simulation = Simulation.from_capillary( +output = prop_capillary( 125e-6, 1e-5, # a short configuration check, in metres "Ar", @@ -60,23 +58,25 @@ simulation = Simulation.from_capillary( trange=200e-15, fwhm=30e-15, energy=1e-9, - modes=1, plasma=False, raman=False, backend="numpy_ref", + mode_average=True, ) -simulation.settings = replace( - simulation.settings, - quadrature=None, - components=None, - stats=default_spec("mode_averaged"), -) -assert simulation.translate().flags.mode_averaged +assert output.metadata.simulation_kind == "mode_averaged" ``` +The run carries one mode, so `modes` greater than one is refused, as are `n_r`, +`n_theta` and `radial_rule`, which shape a transverse rule the run does not have. +The default statistics are the capillary's own, plasma and species included, less +the quadrature estimate. Everything else (a tapered bore, a pressure gradient, a +tube lattice, an envelope grid) runs as it does resolved. Luna.jl makes the +opposite default choice: its `prop_capillary` is mode-averaged unless it is given +a mode count ([coming from Luna.jl](../workflows/luna.md)). + The [MI ensemble](../../auto_examples/supercontinuum/plot_03_modulational_instability_supercontinuum.rst) and [comb-to-continuum example](../../auto_examples/supercontinuum/plot_04_raman_comb_supercontinuum.rst) -use this configuration at full length, retaining plasma and, for hydrogen, Raman. +use this switch at full length, retaining plasma and, for hydrogen, Raman. Their saved metadata identifies `simulation_kind="mode_averaged"` and records no transverse quadrature. Those examples illustrate the chosen approximation; they do not establish agreement with a spatially resolved plasma calculation. diff --git a/docs/guide/workflows/luna.md b/docs/guide/workflows/luna.md index 8fba07a6..8d2e8373 100644 --- a/docs/guide/workflows/luna.md +++ b/docs/guide/workflows/luna.md @@ -23,7 +23,7 @@ keywords in Python spelling and every quantity in SI units (pressures stay in ba |---|---| | `λ0`, `τfwhm`, `τw`, `ϕ`, `λlims`, `saveN`, `shotnoise` | `lambda0`, `fwhm`, `width`, `phases`, `wavelength_limits`, `n_save`, `noise="input"` with a `seed` | | `pulses=[Pulses.GaussPulse(...), ...]` | `pulses=[GaussianPulse(...), ...]` ({class}`~solprop.pulses.PulseSum`) | -| `modes=:HE11`, `modes=4` | `modes=1`, `modes=4`, or a list of mode objects | +| `modes=:HE11` (mode-averaged), `modes=1`, `modes=4` | `mode_average=True`, `modes=1`, `modes=4`, or a list of mode objects; solprop's default is the resolved `modes=1` | | `Plotting.prop_2D`, `time_1D`, `spec_1D` | `prop_2d`, `time_1d`, `spec_1d` | | `Plotting.stats`, `Plotting.energy` | `stats`, `energy` (`plot_stats`, `plot_energy` in `solprop.sol`) | | `Processing.getEt`, `getEω`, `getIω` | `get_field`, `get_spectrum`, `get_spectral_density` | diff --git a/docs/introduction/spatial-description.md b/docs/introduction/spatial-description.md index 31e93454..be641c5e 100644 --- a/docs/introduction/spatial-description.md +++ b/docs/introduction/spatial-description.md @@ -22,7 +22,7 @@ depends strongly on the local electric-field amplitude. A **resolved modal** calculation follows one or more guided modes and evaluates the nonlinear response across their combined transverse field. Even one resolved mode differs from mode averaging: `prop_capillary(modes=1)` resolves its profile -on a transverse quadrature. Adding modes allows their amplitudes and phases to +on a transverse quadrature, and `mode_average=True` replaces it by the effective area. Adding modes allows their amplitudes and phases to evolve and exchange energy. ```{figure} ../guide/figures/capillary_radial.svg diff --git a/src/solprop/driver/interface.py b/src/solprop/driver/interface.py index df0600af..6c0bd88a 100644 --- a/src/solprop/driver/interface.py +++ b/src/solprop/driver/interface.py @@ -714,6 +714,7 @@ def prop_capillary( # noqa: PLR0913 - the interface's keyword surface phases: Sequence[float] | np.ndarray = (), pulses: Sequence[Pulse] | None = None, modes: int | Sequence[Mode] = 1, + mode_average: bool = False, lattice: TubeLattice | None = None, wall: CoherentWall | None = None, taper_coupling: bool = True, @@ -773,9 +774,10 @@ def prop_capillary( # noqa: PLR0913 - the interface's keyword surface What differs from :func:`prop_gnlse` is the physics, not the machinery: a **field** grid rather than an envelope one, a set of modes on a shared transverse quadrature - rather than one mode-averaged effective area, a gas whose **density varies along - z** and carries both the dispersion and the nonlinearity, and a plasma response - whose ionisation rate is tabulated once over the fields the run will reach. + rather than one mode-averaged effective area (unless `mode_average` asks for that + area), a gas whose **density varies along z** and carries both the dispersion and + the nonlinearity, and a plasma response whose ionisation rate is tabulated once + over the fields the run will reach. The step control is reached through `rtol`, `atol`, `dz_max` and `dz_init`, and the default step caps are relative to the capillary length rather than absolute. @@ -851,6 +853,14 @@ def prop_capillary( # noqa: PLR0913 - the interface's keyword surface with their own tables. A supplied set carries its own geometry: `radius` must agree with every mode's core radius along the run, or the call is refused by name. + mode_average : bool, optional + Propagate the one mode **mode-averaged**, as a uniform disc of its effective + area, instead of resolving it on the transverse rule, as Luna.jl's default + does (``docs/guide/waveguides/mode-averaging.md``). The plasma then sees the + area-mean field, below the on-axis one (``HE_11``'s axis intensity is 1.768 + times ``P/A_eff``), and ``peak_intensity`` stays on the axis. ``False``, the + default, resolves the mode. Refused beside more than one mode and beside + `n_r`, `n_theta` or a `radial_rule`. lattice : TubeLattice or None, optional Tube geometry for an integer mode count, selecting Vincetti modes. Refused with supplied modes, which carry their own geometry, and with @@ -1114,6 +1124,7 @@ def prop_capillary( # noqa: PLR0913 - the interface's keyword surface phases=phases, pulses=pulses, modes=modes, + mode_average=mode_average, lattice=lattice, wall=wall, taper_coupling=taper_coupling, @@ -1212,6 +1223,7 @@ def prop_waveguide( # noqa: PLR0913 - the interface's keyword surface preionisation: float | ZProfile | Mapping[str, float | ZProfile] | None = None, plasma_integration: IntegrationPolicy | None = None, raman: bool | Sequence[str] | Mapping[str, bool | MolecularOptions] | None = None, + mode_average: bool = False, n_r: int | None = None, n_theta: int | None = None, radial_rule: RadialRule = "gauss_kronrod", @@ -1402,6 +1414,15 @@ def prop_waveguide( # noqa: PLR0913 - the interface's keyword surface available models of a mixture and is refused by name for a single gas without data; explicit labels or :class:`~solprop.physdata.raman_types.MolecularOptions` select components and terms, and a named unavailable model fails explicitly. + mode_average : bool, optional + Propagate the one mode **mode-averaged**, as a uniform disc of its effective + area, instead of resolving it on the transverse rule, as Luna.jl's default + does (``docs/guide/waveguides/mode-averaging.md``). The plasma then sees the + area-mean field, below the on-axis one (``HE_11``'s axis intensity is 1.768 + times ``P/A_eff``), and ``peak_intensity`` stays on the axis. ``False``, the + default, resolves the mode. Refused beside more than one mode and beside + `n_r`, `n_theta` or a `radial_rule`. + Refused beside `provider`. n_r : int or None, optional Radial nodes of the fixed rule, or the x-axis nodes of a Cartesian product or slab rule. ``None`` takes @@ -1540,6 +1561,7 @@ def prop_waveguide( # noqa: PLR0913 - the interface's keyword surface preionisation=preionisation, plasma_integration=plasma_integration, raman=raman, + mode_average=mode_average, n_r=n_r, n_theta=n_theta, radial_rule=radial_rule, diff --git a/src/solprop/driver/simulation.py b/src/solprop/driver/simulation.py index 29a82e0e..6f07ca44 100644 --- a/src/solprop/driver/simulation.py +++ b/src/solprop/driver/simulation.py @@ -3625,6 +3625,7 @@ def from_capillary( # noqa: PLR0913 - the interface's keyword surface phases: Sequence[float] | np.ndarray = (), pulses: Sequence[Pulse] | None = None, modes: int | Sequence[Mode] = 1, + mode_average: bool = False, lattice: TubeLattice | None = None, wall: CoherentWall | None = None, taper_coupling: bool = True, @@ -3763,6 +3764,14 @@ def from_capillary( # noqa: PLR0913 - the interface's keyword surface and a `model`, `loss` or `cladding` declared away from its default must be every mode's own, or the call is refused by name rather than the argument ignored. + mode_average : bool, optional + Propagate the one mode **mode-averaged**, as a uniform disc of its + effective area, instead of resolving it on the radial rule + (``docs/guide/waveguides/mode-averaging.md``). The plasma then sees the + area-mean field, below the on-axis one (``HE_11``'s axis intensity is + 1.768 times ``P/A_eff``), and ``peak_intensity`` stays on the axis. + ``False``, the default, resolves the mode. Refused beside more than one + mode and beside `n_r`, `n_theta` or a `radial_rule`. lattice : TubeLattice or None, optional Tube geometry for an integer mode count, selecting Vincetti modes. Refused with supplied modes, which carry their own geometry, and with @@ -3917,7 +3926,8 @@ def from_capillary( # noqa: PLR0913 - the interface's keyword surface stats : solprop.stats.StatsSpec or None, optional Which statistics to record; ``None`` takes :func:`solprop.stats.default_spec` for a multimode run with the profiles, - plus every statistic only a backend can compute. + or for a mode-averaged one under `mode_average`, plus every statistic + only a backend can compute. node_budget : int, optional Bytes of node-chunk buffers the response pass may hold. backend : str, optional @@ -4000,6 +4010,7 @@ def from_capillary( # noqa: PLR0913 - the interface's keyword surface phases=phases, pulses=pulses, modes=modes, + mode_average=mode_average, lattice=lattice, wall=wall, taper_coupling=taper_coupling, @@ -4086,6 +4097,7 @@ def from_waveguide( # noqa: PLR0913 - the interface's keyword surface | Sequence[str] | Mapping[str, bool | MolecularOptions] | None = None, + mode_average: bool = False, n_r: int | None = None, n_theta: int | None = None, radial_rule: RadialRule = "gauss_kronrod", @@ -4327,6 +4339,15 @@ def from_waveguide( # noqa: PLR0913 - the interface's keyword surface without data; explicit labels or :class:`~solprop.physdata.raman_types.MolecularOptions` select components and terms, and a named unavailable model fails explicitly. + mode_average : bool, optional + Propagate the one mode **mode-averaged**, as a uniform disc of its + effective area, instead of resolving it on the transverse rule + (``docs/guide/waveguides/mode-averaging.md``). The plasma then sees the + area-mean field, below the on-axis one (``HE_11``'s axis intensity is + 1.768 times ``P/A_eff``), and ``peak_intensity`` stays on the axis. + ``False``, the default, resolves the mode. Refused beside more than one + mode and beside `n_r`, `n_theta` or a `radial_rule`. + Refused beside `provider`. n_r : int or None, optional Radial nodes of the fixed rule, or the x-axis nodes of a Cartesian product or slab rule. ``None`` takes :func:`default_radial_order` of @@ -4379,7 +4400,8 @@ def from_waveguide( # noqa: PLR0913 - the interface's keyword surface stats : solprop.stats.StatsSpec or None, optional Which statistics to record; ``None`` takes :func:`solprop.stats.default_spec` for a multimode run with the profiles, - plus every statistic only a backend can compute. + or for a mode-averaged one under `mode_average`, plus every statistic + only a backend can compute. node_budget : int, optional Bytes of node-chunk buffers the response pass may hold. backend : str, optional @@ -4464,6 +4486,7 @@ def from_waveguide( # noqa: PLR0913 - the interface's keyword surface preionisation=preionisation, plasma_integration=plasma_integration, raman=raman, + mode_average=mode_average, n_r=n_r, n_theta=n_theta, radial_rule=radial_rule, @@ -4514,6 +4537,7 @@ def from_waveguide( # noqa: PLR0913 - the interface's keyword surface return cls._from_hollow( radius=None, modes=modes, + mode_average=mode_average, waveguide=True, length=length, gas=gas, @@ -4850,6 +4874,7 @@ def _from_hollow( # noqa: PLR0913 - the interface's keyword surface phases: Sequence[float] | np.ndarray = (), pulses: Sequence[Pulse] | None = None, modes: int | Sequence[Mode] = 1, + mode_average: bool = False, lattice: TubeLattice | None = None, wall: CoherentWall | None = None, taper_coupling: bool = True, @@ -4987,6 +5012,14 @@ def _from_hollow( # noqa: PLR0913 - the interface's keyword surface for orthonormality. The own HE_1m set is synthesised along x; a supplied set resolves the transverse components its modes need -- E_x and E_y for TE_0m, TM_0m, EH, higher HE or rotated modes -- with or without a coherent wall. + mode_average : bool, optional + Propagate the one mode **mode-averaged**: a uniform disc of the mode's + effective area instead of a transverse rule + (``docs/guide/waveguides/mode-averaging.md``). The plasma then sees the + area-mean field, below the on-axis one (``HE_11``'s axis intensity is + 1.768 times ``P/A_eff``), while ``peak_intensity`` stays on the axis. + ``False``, the default, resolves the mode on the radial rule. Refused + beside more than one mode and beside `n_r`, `n_theta` or a `radial_rule`. lattice : TubeLattice or None, optional Tube geometry for an integer mode count, selecting Vincetti modes. Refused with supplied modes, which carry their own geometry, and with @@ -5138,7 +5171,8 @@ def _from_hollow( # noqa: PLR0913 - the interface's keyword surface stats : solprop.stats.StatsSpec or None, optional Which statistics to record; ``None`` takes :func:`solprop.stats.default_spec` for a multimode run with the profiles, - plus every statistic only a backend can compute. + or for a mode-averaged one under `mode_average`, plus every statistic + only a backend can compute. node_budget : int, optional Bytes of node-chunk buffers the response pass may hold. backend : str, optional @@ -5283,7 +5317,11 @@ def _from_hollow( # noqa: PLR0913 - the interface's keyword surface if isinstance(modes, int) else cladding, ) - rule = _capillary_quadrature(mode_set, n_r, n_theta, radial_rule=radial_rule) + rule = ( + _no_quadrature(mode_set, n_r, n_theta, radial_rule=radial_rule) + if mode_average + else _capillary_quadrature(mode_set, n_r, n_theta, radial_rule=radial_rule) + ) profiles = mixture_profiles(mixture) if waveguide: for mode in mode_set: @@ -5400,7 +5438,9 @@ def _from_hollow( # noqa: PLR0913 - the interface's keyword surface DEFAULT_ABSORBER_SCHEME if absorber_scheme is None else absorber_scheme ), quadrature=rule, - components=_hollow_components(supplied=not isinstance(modes, int)), + components=None + if rule is None + else _hollow_components(supplied=not isinstance(modes, int)), ionisation_field_range=field_range, node_budget=node_budget, stats=( @@ -5412,14 +5452,20 @@ def _from_hollow( # noqa: PLR0913 - the interface's keyword surface rule=rule, ) if waveguide - else _capillary_stats(with_plasma=ionising, mixture=mixture), + else _capillary_stats( + with_plasma=ionising, mixture=mixture, averaged=rule is None + ), responses, ) if stats is None else stats ) if atomic is None or stats is not None - else _tdse_stats(_capillary_stats(with_plasma=False, mixture=mixture)), + else _tdse_stats( + _capillary_stats( + with_plasma=False, mixture=mixture, averaged=rule is None + ) + ), backend=backend, precision=precision, tdse_execution=None if atomic is None else atomic.execution, @@ -8172,6 +8218,60 @@ def _capillary_quadrature( ) +def _no_quadrature( + modes: ModeCollection, + n_r: int | None, + n_theta: int | None, + *, + radial_rule: RadialRule, +) -> None: + """Check a mode-averaged hollow run and return its absent transverse rule. + + A mode-averaged run carries one mode as a uniform disc of its effective area, the + one-node transform (:func:`solprop.translate.modal.mode_averaged_transform`), so it + has no rule for the keywords that shape one to act on. + + Parameters + ---------- + modes : solprop.modes.collection.ModeCollection + The mode set, which must hold exactly one mode. + n_r : int or None + Radial nodes, which must not be given. + n_theta : int or None + Azimuthal nodes, which must not be given. + radial_rule : str + The radial rule family, which must be the default. + + Raises + ------ + DriverError + If the set has more than one mode or a rule keyword is given. + """ + if len(modes) != 1: + msg = ( + f"mode_average=True needs exactly one mode, and the set has {len(modes)}: " + "energy moving between modes is what a mode-averaged run cannot carry " + "(docs/guide/waveguides/mode-averaging.md)" + ) + raise DriverError(msg) + given = [ + name + for name, value, unset in ( + ("n_r", n_r, None), + ("n_theta", n_theta, None), + ("radial_rule", radial_rule, "gauss_kronrod"), + ) + if value != unset + ] + if given: + msg = ( + f"{', '.join(given)} given beside mode_average=True: they shape a " + "transverse rule, and a mode-averaged run has none, its mode being a " + "uniform disc of the effective area" + ) + raise DriverError(msg) + + def _capillary_responses( # noqa: PLR0913 - the switches of the responses, one each gas: Gas | str, material: Species, @@ -8630,7 +8730,9 @@ def _warn_tdse_cost(quadrature: TransverseQuadrature | None) -> None: ) -def _capillary_stats(*, with_plasma: bool, mixture: Mixture) -> StatsSpec: +def _capillary_stats( + *, with_plasma: bool, mixture: Mixture, averaged: bool = False +) -> StatsSpec: """Return the statistics a capillary run records unless told otherwise. The multimode defaults with the z-profiles, plus every statistic only a backend can @@ -8645,6 +8747,10 @@ def _capillary_stats(*, with_plasma: bool, mixture: Mixture) -> StatsSpec: mixture : Mixture Ordered populations whose species statistics are included. Independent density paths have no total-pressure statistic. + averaged : bool, optional + Whether the run is mode-averaged: the mode-averaged defaults then replace the + multimode ones, and the quadrature estimate, which has no rule to read, is + left out. Returns ------- @@ -8654,9 +8760,12 @@ def _capillary_stats(*, with_plasma: bool, mixture: Mixture) -> StatsSpec: wanted = [ name for name in BACKEND_STATISTICS - if with_plasma or name not in PLASMA_STATISTICS + if (with_plasma or name not in PLASMA_STATISTICS) + and not (averaged and name == "quadrature_error") ] - base = default_spec("multimode", with_profiles=True) + base = default_spec( + "mode_averaged" if averaged else "multimode", with_profiles=True + ) wanted.extend( ( "species_ionisation", @@ -8686,6 +8795,7 @@ def _capillary_stats(*, with_plasma: bool, mixture: Mixture) -> StatsSpec: "preionisation": None, "plasma_integration": None, "raman": None, + "mode_average": False, "n_r": None, "n_theta": None, "radial_rule": "gauss_kronrod", @@ -8722,6 +8832,8 @@ def _capillary_stats(*, with_plasma: bool, mixture: Mixture) -> StatsSpec: "plasma_integration": "a general guide carries no plasma column to integrate", "raman": "a guide's delayed response is its material regions' own, the silica " "tensor Raman model", + "mode_average": "a general guide's channels are resolved on its own node rule; " + "mode averaging is the gas-filled route's", "n_r": "the transverse rule is the guide's own node rule, declared when the " "guide was built", "n_theta": "the transverse rule is the guide's own node rule, declared when " @@ -9191,7 +9303,7 @@ def _capillary_input_peak( field0: Complex[np.ndarray, "n_modes n_pol n_omega"], modes: ModeCollection, grid: TimeGrid, - rule: TransverseQuadrature, + rule: TransverseQuadrature | None, ) -> tuple[float, float]: """Return the input's peak field, which sets the ionisation table's range. @@ -9212,8 +9324,9 @@ def _capillary_input_peak( The mode set, whose profiles are evaluated on the axis and at the nodes. grid : solprop.grid.TimeGrid The grid the state is stored on. - rule : solprop.modes.quadrature.TransverseQuadrature - The run's transverse quadrature. + rule : solprop.modes.quadrature.TransverseQuadrature or None + The run's transverse quadrature; ``None`` for a mode-averaged run, whose + table the axis alone bounds: the area-mean field it samples lies below it. Returns ------- @@ -9225,7 +9338,9 @@ def _capillary_input_peak( limits = modes.dimlimits()[0] peak_power = 0.0 circular = 0.0 - for where in (on_axis_rule(limits), rule): + for where in ( + (on_axis_rule(limits),) if rule is None else (on_axis_rule(limits), rule) + ): matrices = mode_matrix(modes.modes, where, 0.0, components=("x", "y")) rows = matrices.synthesis.reshape(matrices.n_nodes, matrices.n_pol, -1) # One node at a time: the field at every node and time together would be @@ -9258,14 +9373,14 @@ class _InputPeak: The mode set. grid : solprop.grid.TimeGrid The grid the state is stored on. - rule : solprop.modes.quadrature.TransverseQuadrature - The run's transverse quadrature. + rule : solprop.modes.quadrature.TransverseQuadrature or None + The run's transverse quadrature; ``None`` for a mode-averaged run. """ field0: Complex[np.ndarray, "n_modes n_pol n_omega"] modes: ModeCollection grid: TimeGrid - rule: TransverseQuadrature + rule: TransverseQuadrature | None def evaluate(self) -> tuple[float, float]: """Return the peak field in V/m and its circular share. @@ -9644,7 +9759,7 @@ def _hollow_stats( with_plasma: bool, mixture: Mixture, profiles: Mapping[str, ZProfile], - rule: TransverseQuadrature, + rule: TransverseQuadrature | None, ) -> StatsSpec: """Keep capillary statistics where the supplied geometry defines them. @@ -9656,18 +9771,20 @@ def _hollow_stats( Ordered material populations. profiles : mapping of str to ZProfile Available physical paths. - rule : solprop.modes.quadrature.TransverseQuadrature + rule : solprop.modes.quadrature.TransverseQuadrature or None The run's fixed transverse rule, whose embedded coarse rule the - quadrature alarm needs. + quadrature alarm needs; ``None`` for a mode-averaged run. Returns ------- StatsSpec Statistics with unsupported geometry paths omitted. """ - base = _capillary_stats(with_plasma=with_plasma, mixture=mixture) + base = _capillary_stats( + with_plasma=with_plasma, mixture=mixture, averaged=rule is None + ) dropped = {name for name in ("core_radius",) if name not in profiles} - if not rule.kronrod: + if rule is not None and not rule.kronrod: # Without an embedded coarse rule the estimate is NaN at every plane, so # the statistic is dropped rather than recorded empty. A Cartesian # product rule has no Kronrod extension at all (quadrature_rule stores diff --git a/tests/driver/test_general_modal.py b/tests/driver/test_general_modal.py index f29f72a5..02f3f0bb 100644 --- a/tests/driver/test_general_modal.py +++ b/tests/driver/test_general_modal.py @@ -203,6 +203,7 @@ def test_a_provider_builds_a_simulation_that_translates_to_the_general_route( "preionisation": 0.1, "plasma_integration": "band", "raman": True, + "mode_average": True, "n_r": 16, "n_theta": 9, "radial_rule": "gauss", diff --git a/tests/driver/test_mode_average.py b/tests/driver/test_mode_average.py new file mode 100644 index 00000000..ef179f82 --- /dev/null +++ b/tests/driver/test_mode_average.py @@ -0,0 +1,149 @@ +# SPDX-FileCopyrightText: 2026 John C. Travers and contributors +# +# SPDX-License-Identifier: GPL-3.0-or-later + +"""`mode_average=True` runs a hollow guide's one mode mode-averaged. + +The switch is the one-node transform of the multimode pipeline, the route a +`Simulation` with no transverse quadrature already took: the capillary's run under it +is bitwise the run with the quadrature removed by hand. What it adds is the capillary's +own statistics, plasma and species included, and refusals for the keywords that shape +a transverse rule it does not have. +""" + +from dataclasses import replace + +import numpy as np +import pytest + +from solprop.driver import Simulation, prop_capillary, prop_waveguide +from solprop.exceptions import DriverError +from solprop.modes import MarcatiliMode, TubeLattice +from solprop.profiles import Linear +from solprop.stats import default_spec + +RADIUS = 100e-6 +"""Core radius, m.""" + +LENGTH = 5e-3 +"""Capillary length, m: a few adaptive steps.""" + +# The helium index is extrapolated beyond its fitted range at the grid's ends; that +# is the material model, not the switch under test. +pytestmark = pytest.mark.filterwarnings( + "ignore::solprop.exceptions.MaterialRangeWarning" +) + + +def _keywords(**overrides): + """Return a short, ionising helium capillary's keywords.""" + keywords = { + "lambda0": 800e-9, + "wavelength_limits": (150e-9, 4e-6), + "trange": 200e-15, + "fwhm": 30e-15, + "energy": 200e-6, + "n_save": 2, + "backend": "numpy_ref", + "progress": "none", + } + return keywords | overrides + + +@pytest.mark.medium +def test_the_switch_is_the_run_without_the_quadrature(): + """The keyword's run is bitwise the hand-built averaged run, with more stats.""" + keywords = _keywords() + progress = keywords.pop("progress") + by_hand = Simulation.from_capillary(RADIUS, LENGTH, "He", 2.0, **keywords) + by_hand.settings = replace( + by_hand.settings, + quadrature=None, + components=None, + stats=default_spec("mode_averaged"), + ) + expected = by_hand.run(progress=progress) + output = prop_capillary(RADIUS, LENGTH, "He", 2.0, **_keywords(), mode_average=True) + assert output.metadata is not None + assert output.metadata.simulation_kind == "mode_averaged" + assert output.metadata.quadrature is None + np.testing.assert_array_equal( + np.asarray(output["field"]), np.asarray(expected["field"]) + ) + names = set(output.stat_names) + assert { + "electron_density", + "peak_ionisation_rate", + "peak_intensity", + "species_electron_density", + "density", + "pressure", + "core_radius", + } <= names + assert "quadrature_error" not in names + assert "peak_power_total" not in names + + +def test_the_default_stays_resolved(): + """Without the switch, one mode is resolved on the radial rule.""" + keywords = _keywords() + keywords.pop("progress") + simulation = Simulation.from_capillary(RADIUS, LENGTH, "He", 2.0, **keywords) + assert simulation.settings.quadrature is not None + assert not simulation.translate().flags.mode_averaged + + +@pytest.mark.medium +@pytest.mark.parametrize( + ("radius", "gas", "extra"), + [ + (Linear(RADIUS, 0.8 * RADIUS, LENGTH), "He", {}), + (RADIUS, "He", {"envelope": True}), + ( + 15e-6, + "Ar", + { + "lattice": TubeLattice(0.7e-6, 5e-6, 6), + "energy": 1e-6, + "wavelength_limits": (300e-9, 3e-6), + }, + ), + ], + ids=["taper", "envelope", "antiresonant"], +) +def test_the_switch_runs_the_capillary_variants(radius, gas, extra): + """A tapered bore, an envelope grid and a tube lattice run mode-averaged.""" + output = prop_capillary( + radius, LENGTH, gas, 2.0, **_keywords(**extra), mode_average=True + ) + assert output.metadata is not None + assert output.metadata.simulation_kind == "mode_averaged" + + +@pytest.mark.medium +def test_the_switch_runs_a_supplied_gas_mode(): + """`prop_waveguide` takes the switch for its supplied mode.""" + output = prop_waveguide( + [MarcatiliMode(RADIUS)], LENGTH, "He", 2.0, **_keywords(), mode_average=True + ) + assert output.metadata is not None + assert output.metadata.simulation_kind == "mode_averaged" + + +@pytest.mark.parametrize( + ("given", "match"), + [ + ({"modes": 2}, "needs exactly one mode"), + ({"n_r": 8}, "n_r given beside mode_average=True"), + ({"n_theta": 4}, "n_theta given beside mode_average=True"), + ({"radial_rule": "gauss"}, "radial_rule given beside mode_average=True"), + ], +) +def test_the_switch_refuses_what_needs_a_rule(given, match): + """More than one mode, or a keyword that shapes the rule, is refused by name.""" + keywords = _keywords() + keywords.pop("progress") + with pytest.raises(DriverError, match=match): + Simulation.from_capillary( + RADIUS, LENGTH, "He", 2.0, **keywords, **given, mode_average=True + )