# SPDX-FileCopyrightText: 2026 John C. Travers and contributors
#
# SPDX-License-Identifier: GPL-3.0-or-later
"""Machine-readable metadata of every keyword a recipe can set (SEP 41, S9).
One :class:`Parameter` per keyword of a route's ``Simulation.from_*`` constructor, of
its ``prop_*`` function and of the execution settings: its forms (a keyword often takes
several, as ``pressure`` takes a number, a gradient or a profile), their units and
ranges, whether it needs the ``"none"`` spelling, the unit a front end shows it in,
whether the resolved recipe writes it back, the rule that resolves a ``None`` default,
and where it is documented. The function's signature stays the single source of the
default: :func:`parameters` reads it, and a test holds the table and the signature to
the same names.
A front end's tooltips and field validation come from this table; its labels and
wording are its own.
"""
import dataclasses
import inspect
from collections.abc import Callable, Mapping, Sequence
from dataclasses import dataclass
from types import MappingProxyType
from typing import TYPE_CHECKING, Final, Literal
from solprop.driver.defaults import (
default_integrator,
default_rtol,
default_scheme,
resolve_noise,
)
from solprop.exceptions import DriverError
from solprop.modes.slab import SlabMode
if TYPE_CHECKING:
from solprop.driver.simulation import Simulation
__all__ = [
"OBJECT_ROUTES",
"PER_WIDTH_KEYWORDS",
"REQUIRED",
"SETTINGS_KEYWORDS",
"ElementUnits",
"Form",
"FormKind",
"Group",
"Level",
"Parameter",
"Resolution",
"Resolver",
"parameters",
"per_unit_width",
"surface",
]
FormKind = Literal[
"number",
"integer",
"boolean",
"choice",
"string",
"pair",
"array",
"mapping",
"object",
"callable",
]
"""What a form of a keyword holds.
``"number"`` a real number or quantity string; ``"integer"`` an integer (a decimal
string for one beyond 64 bits); ``"boolean"``; ``"choice"`` one of fixed strings;
``"string"`` free text, such as a gas name; ``"pair"`` two numbers; ``"array"`` a
one-dimensional list of numbers or a data file; ``"mapping"`` a table keyed by name;
``"object"`` a registered type, written as a table with a ``type`` key; ``"callable"``
a Python function, which no recipe holds (the Python export is the way out).
"""
Group = Literal[
"geometry", "medium", "nonlinear", "input", "grid", "numerics", "output"
]
"""Where a front end shows a keyword."""
Level = Literal["basic", "advanced"]
"""Whether a newcomer sees a keyword, or only an expert."""
ElementUnits = Literal["phases", "betas", "z_pressure"]
"""Keywords whose elements carry different units (SEP 41 section 4).
``phases`` is ``[rad, s, s^2, ...]``; ``betas`` is ``s^n/m`` for order ``n``; the
``(z, p)`` form of ``pressure`` is two rows, positions in metres and pressures in bar.
"""
class _Required:
"""The default of a keyword that has none: the caller must give it."""
def __repr__(self) -> str:
"""Return the sentinel's name.
Returns
-------
str
``"REQUIRED"``.
"""
return "REQUIRED"
REQUIRED: Final = _Required()
"""The default :func:`parameters` records for a keyword the caller must give."""
OBJECT_ROUTES: Final = ("radial", "free", "planar")
"""The routes whose ``[build]`` keywords are objects: grids, medium, beam and settings.
Their ``Simulation.from_*`` takes the time grid, the transverse grid, the medium, the
responses and the beam positionally, and the stepping in a ``settings`` object; a
recipe writes each as a table under its keyword's name.
"""
SETTINGS_KEYWORDS: Final = ("absorber", "absorber_scheme")
"""Keywords of an object route's constructor that a recipe writes under ``settings``.
``prop_radial``, ``prop_free`` and ``prop_planar`` apply them to their settings; a
recipe holds the ``from_*`` form, so they are keywords of its ``settings`` table
(SEP 41 section 5.1), and the route's table does not list them.
"""
[docs]
@dataclass(frozen=True)
class Resolution:
"""The value a ``None`` default will take, and the rule that chose it.
Attributes
----------
value : object
The value the build will use.
rule : str
The rule, in a sentence a front end can show.
Examples
--------
>>> from solprop.recipe.metadata import Resolution
>>> Resolution(1e-6, "the capillary's default").value
1e-06
"""
value: object
rule: str
Resolver = Callable[[Mapping[str, object]], Resolution]
"""Given the values a recipe sets, return what a ``None`` default resolves to."""
[docs]
@dataclass(frozen=True)
class Parameter:
"""Everything a front end needs to show and check one keyword.
Attributes
----------
name : str
The keyword.
forms : tuple of Form
The forms its value may take; ``None`` is a form wherever the default is
``None`` or `none` is set.
group : str
A :data:`Group`.
level : str
A :data:`Level`.
summary : str
One line.
anchor : str
The manual section, as a path under ``docs/`` with an optional ``#anchor``.
display : str or None
The unit a front end shows and a canonical recipe writes, in ASCII
(``"um"``, ``"fs"``); ``None`` for no unit.
none : bool
Whether the keyword's default is not ``None`` yet it accepts ``None``, so a
recipe spells ``None`` as the string ``"none"`` (SEP 41 section 5.1).
resolvable : bool
Whether the resolved recipe writes the value that was used (SEP 41 section
5.2): only where writing it back gives the same record.
resolver : callable or None
For a ``None`` default resolved by a rule, the function that says what it
resolves to (:data:`Resolver`).
default : object
The function's default, filled in by :func:`parameters` from the signature;
:data:`REQUIRED` for a keyword the caller must give.
Examples
--------
>>> from solprop.recipe import metadata
>>> table = metadata.parameters("capillary")
>>> table["pressure"].display, table["pressure"].forms[0].unit
('bar', 'bar')
"""
name: str
forms: tuple[Form, ...]
group: Group
level: Level
summary: str
anchor: str
display: str | None = None
none: bool = False
resolvable: bool = False
resolver: Resolver | None = None
default: object = REQUIRED
# ------------------------------------------------------------------- the resolvers
def _precision(values: Mapping[str, object]) -> str:
"""Return the precision a recipe asks for.
Parameters
----------
values : collections.abc.Mapping
The recipe's values; on an object route the precision is a keyword of its
``settings`` table.
Returns
-------
str
``"double"`` unless the recipe sets ``"single"``.
"""
settings = values.get("settings")
if isinstance(settings, Mapping):
values = settings
return "single" if values.get("precision") == "single" else "double"
_SLAB_TYPES: Final = ("SlabMode", "ModeCollection.slab")
"""The registered types that make a waveguide's mode set a slab set."""
def _is_slab(value: object) -> bool:
"""Return whether a ``modes`` value, as a recipe or a script gives it, holds a slab.
Parameters
----------
value : object
A table with a ``type``, a list of them, or the mode objects themselves.
Returns
-------
bool
True when any element is a slab mode or a collection of them.
"""
if isinstance(value, Mapping):
return value.get("type") in _SLAB_TYPES
if isinstance(value, SlabMode):
return True
if isinstance(value, Sequence) and not isinstance(value, str):
return any(_is_slab(item) for item in value)
return False
PER_WIDTH_KEYWORDS: Final = ("energy", "peak_power")
"""The keywords that are per metre of y on a slab mode set (:func:`per_unit_width`)."""
[docs]
def per_unit_width(route: str, values: Mapping[str, object]) -> bool:
"""Return whether a recipe's pulse energy and peak power are per metre of y.
A slab mode set is uniform in y, a plane wave in the free direction, so on the
waveguide route with slab modes the :data:`PER_WIDTH_KEYWORDS` are read in J/m
and W/m though written, as everywhere, in J and W: the value is the same number,
and a front end shows the unit per metre.
Parameters
----------
route : str
The route.
values : collections.abc.Mapping
The recipe's ``[build]`` values, as it gives them (``modes`` as tables).
Returns
-------
bool
Whether the route is the waveguide's and its mode set a slab set.
Examples
--------
>>> from solprop.recipe.metadata import per_unit_width
>>> per_unit_width("waveguide", {"modes": {"type": "ModeCollection.slab"}})
True
>>> per_unit_width("waveguide", {"modes": [{"type": "RectMode"}]})
False
"""
return route == "waveguide" and _is_slab(values.get("modes"))
def _slab_note(route: str) -> str:
"""Return what a route's energy summary adds for slab sets.
Parameters
----------
route : str
The route.
Returns
-------
str
`` (per metre of y on a slab set)`` on the waveguide route, else nothing.
"""
return " (per metre of y on a slab set)" if route == "waveguide" else ""
def _row(route: str, values: Mapping[str, object]) -> str:
"""Return the defaults-table row a recipe reads.
Parameters
----------
route : str
The keyword route.
values : collections.abc.Mapping
The recipe's values.
Returns
-------
str
``"capillary tdse"`` for a capillary beside a TDSE response, and
``"waveguide slab"`` for a waveguide of slab modes; the route otherwise.
"""
if route == "capillary" and values.get("tdse") is not None:
return "capillary tdse"
if per_unit_width(route, values):
return "waveguide slab"
return route
def _rtol(route: str) -> Resolver:
"""Return the resolver of a route's ``rtol``.
Parameters
----------
route : str
The keyword route.
Returns
-------
callable
The resolver.
"""
def resolve(values: Mapping[str, object]) -> Resolution:
"""Return the route's default in the recipe's precision.
Parameters
----------
values : collections.abc.Mapping
The recipe's values.
Returns
-------
Resolution
The tolerance and its rule.
"""
row = _row(route, values)
precision = _precision(values)
return Resolution(
default_rtol(row, precision),
f"the {row} route's default in {precision} precision "
"(solprop.driver.defaults.ROUTE_DEFAULTS)",
)
return resolve
def _integrator(route: str) -> Resolver:
"""Return the resolver of a route's ``integrator``.
Parameters
----------
route : str
The keyword route.
Returns
-------
callable
The resolver.
"""
def resolve(values: Mapping[str, object]) -> Resolution:
"""Return the route's default method in the recipe's precision.
Parameters
----------
values : collections.abc.Mapping
The recipe's values.
Returns
-------
Resolution
The integrator and its rule.
"""
row = _row(route, values)
precision = _precision(values)
rule = (
"single precision steps with RungeKutta()"
if precision == "single"
else f"the {row} route's default"
)
return Resolution(default_integrator(row, precision), rule)
return resolve
def _noise(route: str) -> Resolver:
"""Return the resolver of a route's ``noise="default"``.
Parameters
----------
route : str
The keyword route.
Returns
-------
callable
The resolver.
"""
def resolve(values: Mapping[str, object]) -> Resolution:
"""Return the model the default draws, and why.
Parameters
----------
values : collections.abc.Mapping
The recipe's values.
Returns
-------
Resolution
The model (``None`` for none) and the reason.
"""
row = _row(route, values)
model, resolution = resolve_noise(row, "default", None, _precision(values))
per = "transverse column" if route in OBJECT_ROUTES else "mode"
return Resolution(
model, resolution.reason or f"one photon per {per} on the input"
)
return resolve
def _scheme(values: Mapping[str, object]) -> Resolution:
"""Return the collar scheme a keyword route takes when none is named.
Parameters
----------
values : collections.abc.Mapping
The recipe's values.
Returns
-------
Resolution
The scheme and its rule.
"""
del values
return Resolution(
default_scheme("gnlse", interpolate_planes=False),
"the in-stage collar on the guided routes (SEP 21)",
)
def _by_envelope(name: str) -> Resolver:
"""Return the resolver of a keyword that defaults to the opposite of ``envelope``.
Parameters
----------
name : str
``"thg"`` or ``"plasma"``.
Returns
-------
callable
The resolver.
"""
def resolve(values: Mapping[str, object]) -> Resolution:
"""Return on for a field grid, off for an envelope grid.
Parameters
----------
values : collections.abc.Mapping
The recipe's values.
Returns
-------
Resolution
The switch and its rule.
"""
envelope = bool(values.get("envelope", False))
return Resolution(
not envelope,
f"{name} is on for a field grid and off for an envelope grid",
)
return resolve
def _step_planes(values: Mapping[str, object]) -> Resolution:
"""Return the capillary's ``step_planes`` default.
Parameters
----------
values : collections.abc.Mapping
The recipe's values.
Returns
-------
Resolution
1 beside a TDSE response, else 0.
"""
tdse = values.get("tdse") is not None
return Resolution(
1 if tdse else 0,
"every step is saved beside a TDSE response, none otherwise",
)
def _requested_planes(values: Mapping[str, object]) -> Resolution:
"""Return the ``step_planes`` default of the GNLSE and the waveguide.
Parameters
----------
values : collections.abc.Mapping
The recipe's values.
Returns
-------
Resolution
0: the requested planes only.
"""
del values
return Resolution(0, "the requested planes only")
def _raman(values: Mapping[str, object]) -> Resolution:
"""Return the capillary's ``raman`` default, which depends on the species.
Parameters
----------
values : collections.abc.Mapping
The recipe's values.
Returns
-------
Resolution
The rule; the value is ``None`` here, because it is decided by building the
fill: on for a molecular gas whose Raman data are curated, off otherwise.
"""
del values
return Resolution(
None,
"on for a molecular gas with curated Raman data, beside the Kerr response; "
"off for an atomic gas",
)
def _pressure(values: Mapping[str, object]) -> Resolution:
"""Return the capillary's default pressure.
Parameters
----------
values : collections.abc.Mapping
The recipe's values.
Returns
-------
Resolution
One bar.
"""
del values
return Resolution(1.0, "one bar when no pressure is given")
def _length_fraction(fraction: float, text: str) -> Resolver:
"""Return the resolver of a step length that defaults to a fraction of the length.
Parameters
----------
fraction : float
The fraction of ``length``.
text : str
The rule.
Returns
-------
callable
The resolver.
"""
def resolve(values: Mapping[str, object]) -> Resolution:
"""Return the fraction of the recipe's length.
Parameters
----------
values : collections.abc.Mapping
The recipe's values.
Returns
-------
Resolution
The step, m, or ``None`` without a length, and the rule.
"""
length = values.get("length")
value = None if not isinstance(length, int | float) else fraction * length
return Resolution(value, text)
return resolve
# ------------------------------------------------------------------- the forms
_PROFILES: Final = ("Constant", "Linear", "PiecewiseLinear")
"""The registered profiles along z a keyword may take."""
_NONE: Final = Form("choice", choices=("none",))
"""The ``"none"`` spelling, as a form."""
_POSITIVE_LENGTH: Final = Form("number", unit="m", minimum=0.0, exclusive=True)
_LIMITS: Final = Form("pair", unit="m", minimum=0.0, exclusive=True, length=2)
_DURATION: Final = Form("number", unit="s", minimum=0.0, exclusive=True)
_ENERGY: Final = Form("number", unit="J", minimum=0.0, exclusive=True)
_POWER: Final = Form("number", unit="W", minimum=0.0, exclusive=True)
_BOOLEAN: Final = Form("boolean")
_PULSES: Final = Form(
"object",
types=("GaussianPulse", "SechPulse", "load_spectrum"),
)
_GRID: Final = Form(
"object", types=("EnvelopeGrid.from_limits", "FieldGrid.from_limits")
)
_STATS: Final = Form("object", types=("StatsSpec",))
_SCHEDULE: Final = Form("object", types=("FixedSchedule.uniform",))
_INTEGRATOR: Final = Form(
"object", types=("Multistep", "RungeKutta", "LowStorageRungeKutta")
)
_ABSORBER: Final = Form("object", types=("absorber",))
_CHECK: Final = Form("object", types=("BoundaryCheck",))
_MATERIALS: Final = Form("object", types=("MaterialSelection",))
_SCHEMES: Final = Form("choice", choices=("nonlinear", "split_step"))
_SAVE_PLANES: Final = Form("array", unit="m", minimum=0.0, exclusive=True)
_PHASES: Final = Form("array", elements="phases")
_SHAPES: Final = Form("choice", choices=("gauss", "sech"))
_PRECISIONS: Final = Form("choice", choices=("double", "single"))
_BACKENDS: Final = Form("string")
_NOISE: Final = Form("choice", choices=("default", "input", "modified"))
_SEED: Final = Form("integer", minimum=0)
_PULSE_PAGE: Final = "guide/setup/pulses.md"
_GRID_PAGE: Final = "guide/setup/grids.md#choose-the-window-and-wavelength-range"
_STEPPER_PAGE: Final = "guide/numerics/choosing-a-stepper.md#the-defaults"
_BOUNDARY_PAGE: Final = "guide/numerics/boundaries.md"
_NOISE_PAGE: Final = "guide/media/noise.md"
_OUTPUT_PAGE: Final = "guide/results/output.md"
_BACKEND_PAGE: Final = "guide/performance/backends.md"
_PRECISION_PAGE: Final = "guide/performance/single-precision.md"
_MATERIALS_PAGE: Final = "guide/media/material-selection.md"
_PLANES_PAGE: Final = "guide/numerics/solver.md"
def _shared(route: str) -> tuple[Parameter, ...]:
"""Return the keywords the keyword routes share, with the route's rules.
Parameters
----------
route : str
The route, one of :data:`solprop.recipe.core.ROUTES`.
Returns
-------
tuple of Parameter
The shared entries.
"""
return (
Parameter(
"length",
(_POSITIVE_LENGTH,),
"geometry",
"basic",
"Propagation length.",
"guide/setup/running.md#one-call",
display="m",
),
Parameter(
"wavelength",
(_POSITIVE_LENGTH,),
"input",
"basic",
"Reference wavelength: the grid's centre and the pulse's carrier.",
_PULSE_PAGE,
display="nm",
),
Parameter(
"wavelength_limits",
(_LIMITS,),
"grid",
"basic",
"Shortest and longest wavelength the grid resolves.",
_GRID_PAGE,
display="nm",
),
Parameter(
"trange",
(_DURATION,),
"grid",
"basic",
"Length of the time window.",
_GRID_PAGE,
display="fs",
),
Parameter(
"grid",
(_GRID,),
"grid",
"advanced",
"A grid built in full, in place of wavelength_limits and trange.",
"guide/setup/grids.md",
),
Parameter(
"fwhm",
(_DURATION,),
"input",
"basic",
"Full width at half maximum of the intensity.",
_PULSE_PAGE,
display="fs",
),
Parameter(
"width",
(_DURATION,),
"input",
"advanced",
"The shape's natural width, in place of fwhm.",
_PULSE_PAGE,
display="fs",
),
Parameter(
"shape",
(_SHAPES,),
"input",
"basic",
"Temporal shape of the pulse.",
_PULSE_PAGE,
),
Parameter(
"energy",
(_ENERGY,),
"input",
"basic",
f"Pulse energy; give it or peak_power{_slab_note(route)}.",
"guide/setup/pulses.md#energy-or-peak-power-never-both",
display="uJ",
),
Parameter(
"peak_power",
(_POWER,),
"input",
"basic",
f"Peak power; give it or energy{_slab_note(route)}.",
"guide/setup/pulses.md#energy-or-peak-power-never-both",
display="W",
),
Parameter(
"phases",
(_PHASES,),
"input",
"advanced",
"Spectral phase coefficients: carrier phase, delay, GDD, TOD and on.",
"guide/setup/pulses.md#signs-where-a-pulse-lands-and-which-way-it-chirps",
),
Parameter(
"pulses",
(_PULSES,),
"input",
"advanced",
"Several pulses, in place of the single-pulse keywords.",
"guide/setup/pulses.md#several-pulses-at-once",
),
Parameter(
"launch",
(Form("mapping"), Form("array")),
"input",
"advanced",
"The modes the pulse is launched into: a share (and a phase) per mode "
"label, or one such table per pulse.",
"guide/setup/pulses.md#launching-into-chosen-modes",
),
Parameter(
"noise",
(_NOISE, _NONE),
"input",
"advanced",
"Shot noise added to the input: a model, or none.",
_NOISE_PAGE,
none=True,
resolver=_noise(route),
),
Parameter(
"seed",
(_SEED,),
"input",
"advanced",
"Seed of the noise draw; none draws one and records it.",
"guide/media/noise.md#reproducibility",
),
Parameter(
"rtol",
(Form("number", minimum=0.0, exclusive=True),),
"numerics",
"advanced",
"Relative tolerance of the adaptive step.",
_STEPPER_PAGE,
resolvable=True,
resolver=_rtol(route),
),
Parameter(
"atol",
(Form("number", minimum=0.0),),
"numerics",
"advanced",
"Absolute tolerance of the adaptive step, in the square root of watts.",
_STEPPER_PAGE,
resolvable=True,
),
Parameter(
"dz_max",
(_POSITIVE_LENGTH,),
"numerics",
"advanced",
"Largest step the controller may take.",
_PLANES_PAGE,
display="mm",
resolvable=True,
resolver=_length_fraction(0.5, "half the length"),
),
Parameter(
"dz_init",
(_POSITIVE_LENGTH,),
"numerics",
"advanced",
"First step; none takes it from the start-up rule.",
_PLANES_PAGE,
display="mm",
resolver=_length_fraction(1e-4, "a ten-thousandth of the length"),
),
Parameter(
"schedule",
(_SCHEDULE,),
"numerics",
"advanced",
"A fixed schedule of steps to replay instead of adapting.",
"guide/numerics/fixed-schedules.md",
),
Parameter(
"integrator",
(_INTEGRATOR,),
"numerics",
"advanced",
"The method that steps the run.",
_STEPPER_PAGE,
resolvable=True,
resolver=_integrator(route),
),
Parameter(
"interpolate_planes",
(_BOOLEAN,),
"output",
"advanced",
"Observe the save planes inside steps instead of landing on them.",
_PLANES_PAGE,
),
Parameter(
"save_planes",
(_SAVE_PLANES,),
"output",
"basic",
"Positions to save the field at, after the entrance.",
_OUTPUT_PAGE,
display="mm",
resolvable=True,
),
Parameter(
"n_save",
(Form("integer", minimum=2),),
"output",
"basic",
"Number of evenly spaced planes to save, counting the entrance.",
_OUTPUT_PAGE,
),
Parameter(
"stats",
(_STATS,),
"output",
"advanced",
"Which statistics to record at every saved plane.",
"guide/results/processing.md",
resolvable=True,
),
Parameter(
"absorber",
(_ABSORBER, Form("boolean")),
"numerics",
"advanced",
"The absorbing collars; false switches the soft collars off.",
_BOUNDARY_PAGE,
),
Parameter(
"absorber_scheme",
(_SCHEMES,),
"numerics",
"advanced",
"How the temporal collar is applied.",
"guide/numerics/boundaries.md#the-two-temporal-schemes",
resolver=_scheme,
),
Parameter(
"boundary_check",
(_CHECK, _NONE),
"numerics",
"advanced",
"How the run watches its time window's edges, or none.",
"guide/numerics/boundaries.md#watching-the-windows-edges",
none=True,
),
Parameter(
"backend",
(_BACKENDS,),
"numerics",
"advanced",
"The backend that executes the run; auto picks the fastest installed.",
_BACKEND_PAGE,
),
Parameter(
"precision",
(_PRECISIONS,),
"numerics",
"advanced",
"The arithmetic the run is held in.",
_PRECISION_PAGE,
),
Parameter(
"materials",
(_MATERIALS,),
"medium",
"advanced",
"Which optmat models the run's materials take.",
_MATERIALS_PAGE,
),
Parameter(
"polarisation_taper",
(_BOOLEAN,),
"numerics",
"advanced",
"Taper the nonlinear polarisation in the spectral collar too.",
_BOUNDARY_PAGE,
),
)
def _gnlse() -> tuple[Parameter, ...]:
"""Return the GNLSE route's own keywords.
Returns
-------
tuple of Parameter
The entries not shared with the capillary.
"""
page = "guide/waveguides/gnlse.md"
return (
Parameter(
"gamma",
(Form("number", unit="W^-1/m", minimum=0.0),),
"nonlinear",
"basic",
"Nonlinear coefficient; none derives it from n2 and the fibre's area.",
page,
display="W^-1/m",
),
Parameter(
"betas",
(Form("array", elements="betas"),),
"medium",
"basic",
"Taylor coefficients of the propagation constant, from order zero.",
"guide/waveguides/gnlse.md#the-fibre-is-a-mode",
),
Parameter(
"beta",
(Form("callable", recipe=False),),
"medium",
"advanced",
"The propagation constant as a function, in place of betas.",
"guide/waveguides/gnlse.md#the-fibre-is-a-mode",
),
Parameter(
"fibre",
(Form("object", types=("Mode", "IndexTable"), recipe=False),),
"medium",
"advanced",
"A mode or a tabulated index as the fibre.",
"guide/setup/running.md#a-table-or-mode-as-the-gnlse-fibre",
),
Parameter(
"n2",
(Form("number", unit="m^2/W"), Form("string")),
"nonlinear",
"advanced",
"Nonlinear index, or a material name, with the fibre's area for gamma.",
page,
display="m^2/W",
),
Parameter(
"loss_db_per_m",
(Form("number", minimum=0.0),),
"medium",
"basic",
"Loss, in dB per metre.",
page,
),
Parameter(
"shock",
(_BOOLEAN,),
"nonlinear",
"advanced",
"Keep the optical shock term.",
"guide/waveguides/gnlse.md#the-shock-term",
),
Parameter(
"raman",
(_BOOLEAN,),
"nonlinear",
"basic",
"Add fused silica's Raman response.",
"guide/waveguides/gnlse.md#the-raman-fraction-is-not-silently-taken",
resolvable=True,
),
Parameter(
"raman_fraction",
(Form("number", minimum=0.0, maximum=1.0),),
"nonlinear",
"advanced",
"Fraction of the nonlinearity that is Raman; none takes the rule's.",
"guide/waveguides/gnlse.md#the-raman-fraction-is-not-silently-taken",
),
Parameter(
"step_planes",
(Form("integer", minimum=0),),
"output",
"advanced",
"Also save every k-th accepted step; none and 0 save the requested planes "
"only.",
_PLANES_PAGE,
resolvable=True,
resolver=_requested_planes,
),
)
def _capillary() -> tuple[Parameter, ...]:
"""Return the capillary route's own keywords.
Returns
-------
tuple of Parameter
The entries not shared with the GNLSE.
"""
page = "guide/waveguides/capillaries.md"
ionisation = "guide/media/ionisation.md"
return (
Parameter(
"radius",
(_POSITIVE_LENGTH, Form("object", types=_PROFILES)),
"geometry",
"basic",
"Core radius, or a radius profile along z.",
page,
display="um",
),
Parameter(
"gas",
(
Form("string"),
Form("mapping"),
Form(
"object",
types=("Mixture.fill", "Mixture.partial_pressures", "Mixture.air"),
),
),
"medium",
"basic",
"The fill: a species, a mapping of species to fractions, or a mixture.",
"guide/media/mixtures.md",
),
Parameter(
"pressure",
(
Form("number", unit="bar", minimum=0.0),
Form("pair", unit="bar", minimum=0.0, length=2),
Form("array", elements="z_pressure"),
Form(
"object",
types=(
"PressureGradient.two_point",
"PressureGradient.from_stages",
*_PROFILES,
),
),
),
"medium",
"basic",
"Fill pressure: a number, an end-to-end gradient, (z, p) pairs or a "
"profile.",
"guide/media/mixtures.md",
display="bar",
resolver=_pressure,
),
Parameter(
"pressure_floor",
(Form("number", unit="bar", minimum=0.0),),
"medium",
"advanced",
"Lowest pressure of a gradient's evacuated end.",
"guide/media/mixtures.md",
display="mbar",
),
Parameter(
"vacuum_end",
(Form("choice", choices=("choked", "floor")),),
"medium",
"advanced",
"How a gradient treats an end below its choked pressure.",
"guide/media/mixtures.md",
),
Parameter(
"envelope",
(_BOOLEAN,),
"grid",
"basic",
"Propagate the complex envelope instead of the real field.",
"guide/setup/grids.md#choose-a-field-or-an-envelope",
),
Parameter(
"modes",
(
Form("integer", minimum=1),
Form("object", types=("MarcatiliMode",)),
),
"geometry",
"basic",
"Number of HE1m modes to propagate, or the modes themselves.",
"guide/waveguides/mode-sets.md",
),
Parameter(
"mode_average",
(_BOOLEAN,),
"geometry",
"basic",
"Propagate one mode with its nonlinearity averaged over the profile.",
"guide/waveguides/mode-averaging.md",
),
Parameter(
"lattice",
(Form("object", types=("TubeLattice",)),),
"geometry",
"advanced",
"An antiresonant tube lattice around the core.",
"guide/waveguides/antiresonant.md",
),
Parameter(
"wall",
(Form("object", types=("CoherentWall",)),),
"geometry",
"advanced",
"A coherent wall model in place of the capillary's bulk cladding.",
"guide/waveguides/coherent-wall.md",
),
Parameter(
"taper_coupling",
(_BOOLEAN,),
"geometry",
"advanced",
"Couple the modes of a tapered core.",
"guide/waveguides/taper-coupling.md",
),
Parameter(
"model",
(Form("choice", choices=("full", "reduced")),),
"geometry",
"advanced",
"Which Marcatili model of the propagation constant.",
"guide/waveguides/capillary-model.md",
resolvable=True,
),
Parameter(
"loss",
(_BOOLEAN, Form("number"), Form("callable", recipe=False)),
"medium",
"basic",
"Include the capillary's loss: on or off, a scale, or a curve.",
"guide/waveguides/capillary-model.md",
resolvable=True,
),
Parameter(
"cladding",
(
Form("object", types=("GlassIndex",), recipe=False),
Form("number"),
),
"medium",
"advanced",
"Cladding index; none is fused silica.",
"guide/waveguides/capillary-model.md",
),
Parameter(
"susceptibility",
(Form("object", types=("GasSusceptibility",), recipe=False),),
"medium",
"advanced",
"A gas susceptibility in place of the fill's own.",
"guide/media/materials.md",
),
Parameter(
"temperature",
(
Form("number", unit="K", minimum=0.0, exclusive=True),
Form("object", types=_PROFILES),
),
"medium",
"advanced",
"Gas temperature, or a profile along z.",
"guide/media/mixtures.md",
display="K",
resolvable=True,
),
Parameter(
"kerr",
(_BOOLEAN,),
"nonlinear",
"basic",
"Include the Kerr response.",
"guide/media/nonlinear.md",
),
Parameter(
"thg",
(_BOOLEAN,),
"nonlinear",
"advanced",
"Keep third-harmonic generation in the Kerr response.",
"guide/media/nonlinear.md",
resolvable=True,
resolver=_by_envelope("thg"),
),
Parameter(
"plasma",
(
_BOOLEAN,
Form("object", types=("OptmatRate",)),
Form("mapping"),
),
"nonlinear",
"basic",
"Include photoionisation and the plasma: on or off, a rate, or per "
"species.",
ionisation,
resolvable=True,
resolver=_by_envelope("plasma"),
),
Parameter(
"preionisation",
(
Form("number", minimum=0.0, maximum=1.0),
Form("object", types=_PROFILES),
Form("mapping"),
),
"nonlinear",
"advanced",
"Ionisation fraction already present, or per species.",
ionisation,
),
Parameter(
"plasma_integration",
(Form("choice", choices=("band", "trapezoid")),),
"nonlinear",
"advanced",
"How the plasma current is integrated over time.",
"guide/media/field-plasma.md",
),
Parameter(
"raman",
(_BOOLEAN, Form("array"), Form("mapping")),
"nonlinear",
"basic",
"Include the molecular Raman response: on or off, or which species.",
"guide/media/raman.md",
resolvable=True,
resolver=_raman,
),
Parameter(
"n_r",
(Form("integer", minimum=1),),
"numerics",
"advanced",
"Radial quadrature nodes of a multimode run.",
"guide/waveguides/radial-quadrature.md",
),
Parameter(
"n_theta",
(Form("integer", minimum=1),),
"numerics",
"advanced",
"Azimuthal quadrature nodes of a multimode run.",
"guide/waveguides/radial-quadrature.md",
),
Parameter(
"radial_rule",
(
Form(
"choice",
choices=(
"gauss_kronrod",
"gauss",
"field_scale",
"field_scale_radau",
),
),
),
"numerics",
"advanced",
"The radial quadrature rule.",
"guide/waveguides/radial-quadrature.md",
),
Parameter(
"ionisation_field_range",
(Form("pair", minimum=0.0, exclusive=True, length=2),),
"numerics",
"advanced",
"Lowest and highest field, V/m, the ionisation-rate table covers.",
ionisation,
),
Parameter(
"step_planes",
(Form("integer", minimum=0),),
"output",
"advanced",
"Also save every k-th accepted step; none takes 1 beside a TDSE response, "
"else 0.",
_PLANES_PAGE,
resolvable=True,
resolver=_step_planes,
),
Parameter(
"node_budget",
(Form("integer", minimum=1),),
"numerics",
"advanced",
"Bytes of node buffers a response pass may hold.",
"guide/performance/resources.md",
),
Parameter(
"polarisation",
(Form("string"), Form("number")),
"input",
"advanced",
"Refused: the capillary route is scalar.",
"guide/setup/polarisation.md",
),
Parameter(
"tdse",
(Form("object", types=("TDSEGasResponse",), recipe=False),),
"nonlinear",
"advanced",
"An atomic TDSE response in place of the rate model.",
"guide/media/tdse.md",
),
Parameter(
"tdse_execution",
(Form("object", types=("TDSEExecution",), recipe=False),),
"numerics",
"advanced",
"Explicit runtime resources of a TDSE response.",
"guide/media/tdse.md",
),
)
_MODE_TYPES: Final = ("MarcatiliMode", "RectMode", "SlabMode", "ModeCollection.slab")
"""The registered supplied modes: geometry, cladding and loss, rebound to the fill."""
def _waveguide() -> tuple[Parameter, ...]:
"""Return the waveguide route's own keywords.
The fill, the responses and the transverse rule are the capillary's entries
(:func:`_route_entries`); these are the ones the waveguide has alone or reads
differently.
Returns
-------
tuple of Parameter
The entries not shared with the capillary.
"""
supplied = "guide/waveguides/supplied-guides.md"
provider = f"{supplied}#a-guide-instead-of-a-fill"
general = "guide/waveguides/general-modal.md"
beam = "guide/workflows/coupling.md#a-beam-into-a-general-modal-guide"
rule = "guide/waveguides/mode-sets.md#the-transverse-quadrature"
return (
Parameter(
"modes",
(
Form("object", types=_MODE_TYPES),
Form("object", types=("Mode",), recipe=False),
),
"geometry",
"basic",
"The supplied gas-filled modes, or a slab collection; empty beside a "
"provider.",
f"{supplied}#supplied-gas-filled-waveguides",
),
Parameter(
"provider",
(Form("object", types=("ModalGuide",), recipe=False),),
"geometry",
"advanced",
"A guide that solves its own fields, in place of modes and a fill.",
provider,
),
Parameter(
"channel",
(Form("integer", minimum=0), Form("string")),
"input",
"advanced",
"The channel a provider's pulse enters, by index or display label.",
provider,
),
Parameter(
"incident",
(Form("object", types=("SpatialPulse",)),),
"input",
"advanced",
"An incident beam coupled into a provider's channels.",
beam,
),
Parameter(
"coupling",
(Form("object", types=("CouplingModel",), recipe=False),),
"input",
"advanced",
"The model an incident beam is coupled under.",
beam,
),
Parameter(
"kerr_form",
(Form("choice", choices=("real", "analytic")),),
"nonlinear",
"advanced",
"The cubic a provider's material regions respond with.",
provider,
),
Parameter(
"observable_rtol",
(Form("number", minimum=0.0),),
"numerics",
"advanced",
"Accuracy of a provider run's observables; only the solver's own, none.",
f"{general}#declared-refused-recorded",
),
Parameter(
"observable_atol",
(Form("number", minimum=0.0),),
"numerics",
"advanced",
"Accuracy of a provider run's observables; only the solver's own, none.",
f"{general}#declared-refused-recorded",
),
Parameter(
"n_r",
(Form("integer", minimum=1),),
"numerics",
"advanced",
"Radial nodes, or the x-axis nodes of a Cartesian or slab rule.",
rule,
),
Parameter(
"n_theta",
(Form("integer", minimum=1),),
"numerics",
"advanced",
"Azimuthal nodes, or the y-axis nodes of a Cartesian rule.",
rule,
),
Parameter(
"polarisation",
(Form("string"), Form("number")),
"input",
"advanced",
"Refused: the waveguide route is scalar.",
"guide/setup/polarisation.md",
),
Parameter(
"step_planes",
(Form("integer", minimum=0),),
"output",
"advanced",
"Also save every k-th accepted step; none and 0 save the requested planes "
"only.",
_PLANES_PAGE,
resolvable=True,
resolver=_requested_planes,
),
)
_SPACES: Final[Mapping[str, tuple[str, ...]]] = {
"radial": ("RadialGrid",),
"free": ("FreeGrid", "LineGrid"),
"planar": ("PlanarGrid",),
}
"""The transverse grids each object route takes."""
_RESPONSES: Final = (
"KerrResponse",
"EnvelopePlasmaResponse",
"PlasmaResponse",
)
"""The registered responses an object route's ``responses`` may list."""
def _free_space(route: str) -> tuple[Parameter, ...]:
"""Return an object route's own keywords.
The noise, seed, material selection and boundary check are the shared entries
(:func:`_route_entries`); the absorber and its scheme are keywords of the
``settings`` table (:data:`SETTINGS_KEYWORDS`).
Parameters
----------
route : {'radial', 'free', 'planar'}
The route.
Returns
-------
tuple of Parameter
The entries not shared with the keyword routes.
"""
beams = "guide/free-space/beams.md"
planar = "guide/waveguides/planar.md#resolving-y"
space_page = planar if route == "planar" else f"{beams}#choosing-a-grid"
beam = (
Form("object", types=("PlanarPulse",))
if route == "planar"
else Form("object", types=("SpatialPulse",))
)
return (
Parameter(
"grid",
(_GRID,),
"grid",
"basic",
"The time grid: a field grid or an envelope grid.",
"guide/setup/grids.md",
),
Parameter(
"space",
(Form("object", types=_SPACES[route]),),
"geometry",
"basic",
"The transverse grid, with the room outside it for a collar."
if route != "planar"
else "The slab modes in x, with their fill, and the y axis.",
space_page,
),
Parameter(
"medium",
(Form("object", types=("SpatialMedium",)),),
"medium",
"basic",
"The bulk medium: a gas at a pressure, a constant index, or vacuum.",
f"{beams}#a-beam-in-argon",
),
Parameter(
"responses",
(Form("object", types=_RESPONSES),),
"nonlinear",
"basic",
"The nonlinear responses; none is linear propagation.",
f"{beams}#ionisation-plasma-and-filamentation",
),
Parameter(
"initial",
(beam, Form("array", recipe=False)),
"input",
"basic",
"The launched beam, or a built spectrum, which only Python carries.",
planar if route == "planar" else f"{beams}#a-beam-in-argon",
),
Parameter(
"settings",
(Form("object", types=("SimulationSettings",)),),
"numerics",
"basic",
"Stepping, saved planes, statistics, absorber and backend.",
"guide/free-space/model.md#construct-and-run-a-beam",
),
Parameter(
"frame",
(Form("object", types=("Frame",), recipe=False),),
"numerics",
"advanced",
"The moving frame; none follows the responses' choice.",
"guide/free-space/model.md#exact-longitudinal-operator",
),
Parameter(
"spatial_absorber",
(Form("object", types=("SpatialAbsorber",)), _BOOLEAN),
"numerics",
"advanced",
"The transverse boundary: a designed collar, or false for no ramp.",
"guide/free-space/model.md#transverse-boundaries",
),
)
_EXECUTION: Final = (
Parameter(
"fields",
(Form("choice", choices=("all", "planes", "none")),),
"output",
"advanced",
"Which saved planes keep their field.",
_OUTPUT_PAGE,
),
Parameter(
"field_planes",
(Form("array"),),
"output",
"advanced",
"Saved-plane indices that keep their field, with fields='planes'.",
_OUTPUT_PAGE,
),
Parameter(
"resume_history",
(_BOOLEAN,),
"output",
"advanced",
"Keep the Adams method's history at each plane, for an exact resume.",
"guide/results/output.md",
),
)
"""The ``[execution]`` table's keys: settings a run reads when it executes."""
def _route_entries(route: str) -> tuple[Parameter, ...]:
"""Return a route's entries, the shared ones overridden by the route's own.
The waveguide takes the capillary's entries for the fill, the responses and the
rule, under its own; an entry the route's constructor does not name is dropped by
:func:`parameters`.
Parameters
----------
route : str
The route, already checked by :func:`surface`.
Returns
-------
tuple of Parameter
The entries, without defaults, the first of each name winning.
"""
layers = {
"gnlse": (_gnlse(),),
"capillary": (_capillary(),),
"waveguide": (_waveguide(), _capillary()),
"radial": (_free_space("radial"),),
"free": (_free_space("free"),),
"planar": (_free_space("planar"),),
}[route]
entries: dict[str, Parameter] = {}
for layer in (*layers, _shared(route)):
for entry in layer:
entries.setdefault(entry.name, entry)
return tuple(entries.values())
[docs]
def surface(route: str) -> "Callable[..., Simulation]": # noqa: UP037 - run time
"""Return the constructor whose keywords a route's ``[build]`` table holds.
Parameters
----------
route : str
The route, one of :data:`solprop.recipe.core.ROUTES`.
Returns
-------
callable
The route's ``Simulation.from_*``: ``from_gnlse``, ``from_capillary``,
``from_waveguide``, ``from_radial``, ``from_free`` or ``from_planar``.
Raises
------
DriverError
If the route has no recipe yet.
Examples
--------
>>> from solprop.recipe.metadata import surface
>>> surface("gnlse").__name__
'from_gnlse'
"""
from solprop.driver.simulation import Simulation # noqa: PLC0415 - layer D
builders = {
"gnlse": Simulation.from_gnlse,
"capillary": Simulation.from_capillary,
"waveguide": Simulation.from_waveguide,
"radial": Simulation.from_radial,
"free": Simulation.from_free,
"planar": Simulation.from_planar,
}
try:
return builders[route]
except KeyError:
msg = (
f"no recipe surface for route {route!r}; the routes are "
f"{', '.join(builders)}"
)
raise DriverError(msg) from None
# ---------------------------------------------------------- the registered types
#
# A recipe's tables name registered types (solprop.recipe.registry.REGISTRY): a second
# pulse, a pressure gradient, a grid, a mode, the whole [build] of a free-space route.
# Each type's keywords get the entry a route keyword has, so that a front end builds
# every table's form from solprop's own description. A test holds every form's unit to
# the registry's, every keyword to the target's signature and every anchor to the
# manual.
def _number(
unit: str | None = None,
*,
minimum: float | None = None,
maximum: float | None = None,
exclusive: bool = False,
) -> Form:
"""Return a number's form.
Parameters
----------
unit : str or None, optional
Its unit.
minimum, maximum : float or None, optional
Its range.
exclusive : bool, optional
Whether the range's ends are excluded.
Returns
-------
Form
The form.
"""
return Form(
"number", unit=unit, minimum=minimum, maximum=maximum, exclusive=exclusive
)
def _integer(minimum: float | None = None, maximum: float | None = None) -> Form:
"""Return an integer's form.
Parameters
----------
minimum, maximum : float or None, optional
Its range, inclusive.
Returns
-------
Form
The form.
"""
return Form("integer", minimum=minimum, maximum=maximum)
def _choice(*choices: str) -> Form:
"""Return the form of one of fixed strings.
Parameters
----------
*choices : str
The strings.
Returns
-------
Form
The form.
"""
return Form("choice", choices=choices)
def _object(*types: str) -> Form:
"""Return the form of a registered type.
Parameters
----------
*types : str
The types it may name.
Returns
-------
Form
The form.
"""
return Form("object", types=types)
_POSITIVE: Final = _number(minimum=0.0, exclusive=True)
_FRACTION: Final = _number(minimum=0.0, maximum=1.0)
_LENGTH: Final = _number("m", minimum=0.0, exclusive=True)
_POSITION: Final = _number("m")
_TYPE_DURATION: Final = _number("s", minimum=0.0, exclusive=True)
_PRESSURE: Final = _number("bar", minimum=0.0)
_TEMPERATURE: Final = _number("K", minimum=0.0, exclusive=True)
_TYPE_BOOLEAN: Final = Form("boolean")
_STRING: Final = Form("string")
_MAPPING: Final = Form("mapping")
_TYPE_PROFILES: Final = ("Constant", "Linear", "PiecewiseLinear")
_PROFILE: Final = _object(*_TYPE_PROFILES)
_CALLABLE: Final = Form("callable", recipe=False)
_UNREGISTERED: Final = Form("object", recipe=False)
_TYPE_PULSES: Final = ("GaussianPulse", "SechPulse", "load_spectrum")
_LOSS: Final = (_TYPE_BOOLEAN, _number(minimum=0.0), _CALLABLE)
_CLADDING: Final = (_UNREGISTERED, Form("mapping"))
_GAS: Final = (_STRING,)
def _keyword(
name: str,
forms: tuple[Form, ...] | Form,
level: Level,
summary: str,
*,
display: str | None = None,
) -> tuple[str, tuple[Form, ...], Level, str, str | None]:
"""Return one keyword's row, its group and anchor left to its type.
Parameters
----------
name : str
The keyword.
forms : Form or tuple of Form
Its forms.
level : str
A :data:`~solprop.recipe.metadata.Level`.
summary : str
One line.
display : str or None, optional
The unit it is shown in.
Returns
-------
tuple
The row.
"""
return (
name,
forms if isinstance(forms, tuple) else (forms,),
level,
summary,
display,
)
Row = tuple[str, tuple[Form, ...], Level, str, str | None]
def _type(group: Group, anchor: str, *rows: Row) -> tuple[Parameter, ...]:
"""Return a type's entries, each with the type's group and anchor.
Parameters
----------
group : str
Where the type is shown.
anchor : str
Its manual section.
*rows : tuple
Its keywords (:func:`_keyword`).
Returns
-------
tuple of Parameter
The entries.
"""
return tuple(
Parameter(name, forms, group, level, summary, anchor, display=display)
for name, forms, level, summary, display in rows
)
# ------------------------------------------------------------------------ the types
_TYPE_PULSE_PAGE: Final = "guide/setup/pulses.md"
_PROFILE_PAGE: Final = "guide/waveguides/profiles.md"
_MIXTURE_PAGE: Final = "guide/media/mixtures.md"
_TYPE_GRID_PAGE: Final = "guide/setup/grids.md"
_TYPE_STEPPER_PAGE: Final = "guide/numerics/choosing-a-stepper.md"
_TYPE_BOUNDARY_PAGE: Final = "guide/numerics/boundaries.md"
_FREE_PAGE: Final = "guide/free-space/model.md"
_PLANAR_PAGE: Final = "guide/waveguides/planar.md"
_IONISATION_PAGE: Final = "guide/media/ionisation.md"
def _pulse(*extra: Row) -> tuple[Parameter, ...]:
"""Return a shaped pulse's entries: the shared keywords and its own.
Parameters
----------
*extra : tuple
Its own keywords' rows.
Returns
-------
tuple of Parameter
The entries.
"""
return _type(
"input",
_TYPE_PULSE_PAGE,
_keyword("wavelength", _LENGTH, "basic", "Centre wavelength.", display="nm"),
_keyword(
"energy",
_number("J", minimum=0.0, exclusive=True),
"basic",
"Pulse energy; give it or the peak power.",
display="uJ",
),
_keyword(
"peak_power",
_number("W", minimum=0.0, exclusive=True),
"basic",
"Peak power after the spectral phase; give it or the energy.",
display="GW",
),
_keyword(
"phase",
Form("array", elements="phases"),
"advanced",
"Taylor coefficients of the spectral phase about the centre wavelength.",
),
_keyword(
"t0",
_number("s"),
"advanced",
"Delay, positive for later arrival.",
display="fs",
),
*extra,
)
def _gradient(*first: Row) -> tuple[Parameter, ...]:
"""Return a pressure gradient's entries: its stages and the flow keywords.
Parameters
----------
*first : tuple
The rows that give its stages.
Returns
-------
tuple of Parameter
The entries.
"""
return _type(
"medium",
f"{_PROFILE_PAGE}#steady-flow-fills",
*first,
_keyword(
"floor",
_PRESSURE,
"advanced",
"Smallest pressure a stage end may take; the default is a fraction of the "
"highest.",
display="bar",
),
_keyword(
"radius",
(_LENGTH, _PROFILE),
"advanced",
"Core radius the flow follows; the default is the uniform bore.",
display="um",
),
_keyword(
"weight",
(_POSITIVE, _PROFILE),
"advanced",
"Flow weight multiplying the radius to the minus fourth power.",
),
)
def _fill_rows() -> tuple[Row, ...]:
"""Return the keywords a gas fill shares: its pressure path and the flow.
Returns
-------
tuple
The rows.
"""
return (
_keyword(
"floor",
_PRESSURE,
"advanced",
"Pressure-gradient floor; the default is the standard floor.",
display="bar",
),
_keyword(
"length",
_LENGTH,
"advanced",
"Length of the fill, needed beside a pair of end pressures.",
display="m",
),
_keyword(
"radius",
(_LENGTH, _PROFILE),
"advanced",
"Core radius the gas flows through, which makes end pressures a steady "
"flow.",
display="um",
),
_keyword(
"vacuum_end",
_STRING,
"advanced",
"How an outer end below its choked pressure is treated.",
),
_keyword(
"selection",
_object("MaterialSelection"),
"advanced",
"The material selection the species are read from optmat under.",
),
)
_PRESSURE_PATH: Final = (
_PRESSURE,
Form("pair", unit="bar", minimum=0.0, length=2),
_PROFILE,
_object("PressureGradient.two_point", "PressureGradient.from_stages"),
)
_TEMPERATURE_PATH: Final = (_TEMPERATURE, _PROFILE)
def _grid(*rows: Row) -> tuple[Parameter, ...]:
"""Return a time grid's entries: the window, the band and the sampling.
Parameters
----------
*rows : tuple
The rows between the sample spacing and the transform sizes.
Returns
-------
tuple of Parameter
The entries.
"""
return _type(
"grid",
f"{_TYPE_GRID_PAGE}#coarse-and-fine-sampling",
_keyword("length", _LENGTH, "basic", "Propagation distance.", display="m"),
_keyword(
"reference_wavelength",
_LENGTH,
"basic",
"Reference wavelength, usually the pump's.",
display="nm",
),
_keyword(
"wavelength_limits",
Form("pair", unit="m", minimum=0.0, exclusive=True, length=2),
"basic",
"Shortest and longest wavelength of the frequency window.",
display="nm",
),
_keyword(
"trange",
_TYPE_DURATION,
"basic",
"Extent of the time window.",
display="fs",
),
_keyword(
"dt",
_TYPE_DURATION,
"advanced",
"Fine sample spacing; the band's own where it is finer.",
display="fs",
),
*rows,
_keyword(
"fft_size",
_choice("auto", "pow2", "smooth"),
"advanced",
"How the transform sizes are rounded up.",
),
_keyword(
"collar_fraction",
_number(minimum=0.0, maximum=0.5),
"advanced",
"Width of each temporal collar as a fraction of the window.",
),
_keyword(
"collar_placement",
_choice("outside", "inside"),
"advanced",
"Whether the collars lie outside the requested window or inside it.",
),
)
def _hollow_rows(cladding: str) -> tuple[Row, ...]:
"""Return a hollow mode's loss and cladding keywords.
Parameters
----------
cladding : str
What the cladding is called.
Returns
-------
tuple
The rows.
"""
return (
_keyword(
"loss",
_LOSS,
"advanced",
"The model's loss, none, a scale of it, or an absolute loss.",
),
_keyword(
"cladding",
_CLADDING,
"advanced",
f"The {cladding}'s index; the default is fused silica.",
),
)
def _slab_fill(temperature: Form | tuple[Form, ...]) -> tuple[Row, ...]:
"""Return a slab channel's own fill, which the planar route reads.
Parameters
----------
temperature : Form or tuple of Form
The temperature's forms.
Returns
-------
tuple
The rows.
"""
return (
_keyword("gas", _GAS, "basic", "The filling gas; none for vacuum."),
_keyword(
"pressure",
(_PRESSURE, _PROFILE),
"basic",
"Gas pressure, constant or along z.",
display="bar",
),
_keyword(
"polarisation",
_choice("te", "tm"),
"advanced",
"TE (field along y, parallel to the plates) or TM.",
),
*_hollow_rows("plates"),
_keyword(
"temperature", temperature, "advanced", "Gas temperature.", display="K"
),
)
def _beam(*extra: Row) -> tuple[Parameter, ...]:
"""Return a transverse beam's entries.
Parameters
----------
*extra : tuple
Its own keywords' rows.
Returns
-------
tuple of Parameter
The entries.
"""
return _type(
"input",
f"{_TYPE_PULSE_PAGE}#transverse-beams",
_keyword(
"temporal",
_object("GaussianPulse", "SechPulse"),
"basic",
"The pulse in time; its energy or peak power is the beam's.",
),
_keyword(
"waist",
_LENGTH,
"basic",
"The 1/e^2 intensity radius.",
display="um",
),
_keyword("order", _integer(1), "advanced", "Super-Gaussian order, at least 1."),
_keyword(
"propz",
_POSITION,
"advanced",
"Distance the beam is propagated back from its waist at the launch.",
display="mm",
),
_keyword(
"jones",
Form("array"),
"advanced",
"Polarisation: one scalar component or the x and y components.",
),
_keyword(
"displacement",
Form("pair", unit="m", length=2),
"advanced",
"The beam centre's offset in x and y at the launch.",
display="um",
),
_keyword(
"tilt",
Form("pair", unit="rad", length=2),
"advanced",
"The beam's tilt towards x and y.",
display="mrad",
),
*extra,
)
def _plasma(*extra: Row) -> tuple[Parameter, ...]:
"""Return a plasma response's entries.
Parameters
----------
*extra : tuple
Its own keywords' rows.
Returns
-------
tuple of Parameter
The entries.
"""
return _type(
"nonlinear",
_IONISATION_PAGE,
_keyword("gas", _GAS, "basic", "The species ionised."),
_keyword(
"rate",
(_object("OptmatRate"), _UNREGISTERED),
"basic",
"The ionisation rate against the field amplitude.",
),
_keyword(
"preionisation",
(_FRACTION, _PROFILE),
"advanced",
"Fraction already ionised before the pulse, constant or along z.",
),
_keyword("species", _STRING, "advanced", "Component label in a mixture."),
*extra,
)
_ENTRIES: Final[dict[str, tuple[Parameter, ...]]] = {
"GaussianPulse": _pulse(
_keyword(
"fwhm",
_TYPE_DURATION,
"basic",
"Full width at half maximum of the power.",
display="fs",
),
_keyword("m", _integer(1), "advanced", "Super-Gaussian order, at least 1."),
),
"SechPulse": _pulse(
_keyword(
"fwhm",
_TYPE_DURATION,
"basic",
"Full width at half maximum of the power; give it or the natural width.",
display="fs",
),
_keyword(
"width",
_TYPE_DURATION,
"advanced",
"Natural width of sech^2(t/width); give it or the FWHM.",
display="fs",
),
),
"load_spectrum": _type(
"input",
f"{_TYPE_PULSE_PAGE}#measured-spectra-from-a-file",
_keyword(
"path",
Form("mapping"),
"basic",
"The spectrum's file, as a checked data-file reference.",
),
_keyword(
"energy",
_number("J", minimum=0.0, exclusive=True),
"basic",
"Pulse energy to scale to.",
display="uJ",
),
_keyword(
"axis",
_choice("frequency", "wavelength"),
"basic",
"What the axis column holds.",
),
_keyword(
"axis_unit",
_STRING,
"basic",
"Unit of the axis column; the default is the axis's SI unit.",
),
_keyword(
"columns",
Form("array"),
"advanced",
"Indices of the axis, power and phase columns.",
),
_keyword(
"phase_unit", _choice("rad", "deg"), "advanced", "Unit of the phase column."
),
_keyword(
"phase_convention",
_choice("optics", "fft"),
"advanced",
"Sign convention of the phase column.",
),
_keyword(
"wrapped",
_TYPE_BOOLEAN,
"advanced",
"Whether the phase is wrapped; the default decides from its range.",
),
_keyword(
"floor",
_number(minimum=0.0, maximum=1.0),
"advanced",
"Share of the peak power below which a sample is noise for the unwrapping.",
),
),
"PressureGradient.two_point": _gradient(
_keyword("length", _LENGTH, "basic", "Length of the fill.", display="m"),
_keyword(
"start_bar", _PRESSURE, "basic", "Pressure at the entrance.", display="bar"
),
_keyword("end_bar", _PRESSURE, "basic", "Pressure at the exit.", display="bar"),
),
"PressureGradient.from_stages": _gradient(
_keyword(
"positions",
Form("array", unit="m"),
"basic",
"Stage boundaries, strictly increasing.",
display="m",
),
_keyword(
"pressures",
Form("array", unit="bar", minimum=0.0),
"basic",
"Pressure at each boundary.",
display="bar",
),
),
"Constant": _type(
"medium",
f"{_PROFILE_PAGE}#the-paths",
_keyword("value", _number(), "basic", "The value, in the consumer's unit."),
_keyword(
"span",
Form("pair", unit="m", length=2),
"advanced",
"The interval the path is declared over; the default is any.",
display="m",
),
),
"Linear": _type(
"medium",
f"{_PROFILE_PAGE}#the-paths",
_keyword("start", _number(), "basic", "Value at the entrance."),
_keyword("end", _number(), "basic", "Value at the end of the section."),
_keyword(
"length", _LENGTH, "basic", "Length of the varying section.", display="m"
),
),
"PiecewiseLinear": _type(
"medium",
f"{_PROFILE_PAGE}#the-paths",
_keyword(
"positions",
Form("array", unit="m"),
"basic",
"Positions, strictly increasing.",
display="m",
),
_keyword("values", Form("array"), "basic", "The value at each position."),
),
"Mixture.fill": _type(
"medium",
_MIXTURE_PAGE,
_keyword(
"fractions",
_MAPPING,
"basic",
"Mole fraction of each species, summing to one.",
),
_keyword(
"pressure",
_PRESSURE_PATH,
"basic",
"Total pressure: constant, end pressures, positions and pressures, or a "
"profile.",
display="bar",
),
_keyword(
"temperature",
_TEMPERATURE_PATH,
"advanced",
"Temperature, constant or along z.",
display="K",
),
*_fill_rows(),
),
"Mixture.partial_pressures": _type(
"medium",
_MIXTURE_PAGE,
_keyword(
"pressures",
_MAPPING,
"basic",
"Partial pressure of each species in bar, constant or a profile.",
),
_keyword(
"temperature",
_TEMPERATURE_PATH,
"advanced",
"Temperature, constant or along z.",
display="K",
),
_keyword(
"selection",
_object("MaterialSelection"),
"advanced",
"The material selection the species are read from optmat under.",
),
),
"Mixture.air": _type(
"medium",
f"{_MIXTURE_PAGE}#air",
_keyword(
"pressure",
_PRESSURE_PATH,
"basic",
"Total pressure, as a fill's.",
display="bar",
),
_keyword(
"temperature",
_TEMPERATURE_PATH,
"advanced",
"Temperature, constant or along z.",
display="K",
),
_keyword(
"fractions",
_MAPPING,
"advanced",
"Mole fractions; the default is the selection's named composition.",
),
_keyword(
"argon",
_TYPE_BOOLEAN,
"advanced",
"Whether the default composition carries argon.",
),
*(row for row in _fill_rows() if row[0] != "floor"),
_keyword(
"floor",
_PRESSURE,
"advanced",
"Pressure-gradient floor; the default is the standard floor.",
display="bar",
),
),
"EnvelopeGrid.from_limits": _grid(
_keyword(
"thg",
_TYPE_BOOLEAN,
"advanced",
"Leave room for third-harmonic generation.",
),
_keyword(
"ffac",
_number(minimum=2.0),
"advanced",
"Fine-grid oversampling factor; 6 with third harmonics, else 2.",
),
),
"FieldGrid.from_limits": _grid(
_keyword(
"ffac",
_number(minimum=2.0),
"advanced",
"Fine-grid oversampling factor; 6 resolves a cubic response.",
),
),
"StatsSpec": _type(
"output",
"guide/results/statistics.md",
_keyword(
"names",
Form("array"),
"basic",
"The statistics evaluated at every saved plane.",
),
_keyword(
"energy_windows",
Form("array"),
"advanced",
"Spectral windows in rad/s, one (low, high) row each.",
),
_keyword(
"periodic",
Form("array"),
"advanced",
"The statistics also evaluated between planes.",
),
_keyword(
"period",
_integer(1),
"advanced",
"Accepted steps between the periodic evaluations.",
),
_keyword(
"apertures",
_UNREGISTERED,
"advanced",
"Far-field apertures, for the aperture spectra.",
),
_keyword(
"spectral_regions",
Form("array"),
"advanced",
"Transverse wavevector boxes in rad/m, one row each.",
),
),
"BoundaryCheck": _type(
"numerics",
f"{_TYPE_BOUNDARY_PAGE}#watching-the-windows-edges",
_keyword(
"guard_fraction",
_number(minimum=0.0, maximum=0.5, exclusive=True),
"advanced",
"Width of each edge's guard as a fraction of the window.",
),
_keyword(
"budget",
_POSITIVE,
"advanced",
"The field-error budget above which the check warns.",
),
_keyword(
"stop_factor",
_POSITIVE,
"advanced",
"How many budgets stop the run.",
),
_keyword(
"quiet_collar",
_TYPE_BOOLEAN,
"advanced",
"Leave the in-stage collar out while it is quiet, a speed-up.",
),
_keyword(
"transverse_budget",
_POSITIVE,
"advanced",
"The budget of a free-space run's transverse edge; the default is the "
"budget.",
),
),
"FixedSchedule.uniform": _type(
"numerics",
"guide/numerics/fixed-schedules.md#plain-fixed-steps",
_keyword("z_start", _POSITION, "basic", "Where the run starts.", display="m"),
_keyword("z_end", _POSITION, "basic", "Where it ends.", display="m"),
_keyword("dz", _LENGTH, "basic", "Largest step.", display="mm"),
_keyword(
"save_planes",
Form("array", unit="m"),
"advanced",
"Positions the run will save at.",
display="m",
),
_keyword(
"hard_kinks",
Form("array", unit="m"),
"advanced",
"Positions where the linear operator jumps or kinks.",
display="m",
),
),
"FixedSchedule": _type(
"numerics",
"guide/numerics/fixed-schedules.md",
_keyword(
"steps",
Form("array", unit="m", minimum=0.0, exclusive=True),
"basic",
"The step lengths.",
display="mm",
),
_keyword(
"z_start",
_POSITION,
"advanced",
"Position the first step starts from.",
display="m",
),
),
"Multistep": _type(
"numerics",
"guide/numerics/adams.md",
_keyword("order", _integer(1, 6), "advanced", "Highest predictor order."),
_keyword(
"variable_order",
_TYPE_BOOLEAN,
"advanced",
"Choose the order step by step.",
),
_keyword(
"max_calls",
_integer(1),
"advanced",
"Budget of source calls for the run; the default is none.",
),
_keyword(
"interval_cap",
_TYPE_BOOLEAN,
"advanced",
"Cap each step at its operator-table interval.",
),
_keyword(
"equal_approach",
_TYPE_BOOLEAN,
"advanced",
"Approach a forced landing in two equal steps.",
),
_keyword(
"max_growth",
_number(minimum=1.0, exclusive=True),
"advanced",
"Largest ratio of one step to the one before.",
),
_keyword(
"min_factor",
_number(minimum=0.0, maximum=1.0, exclusive=True),
"advanced",
"Smallest step factor after a rejection.",
),
_keyword(
"safety",
_number(minimum=0.0, maximum=1.0),
"advanced",
"Safety factor on a proposed step.",
),
_keyword(
"formulation",
_choice("interaction", "exponential"),
"advanced",
"How the source history is integrated.",
),
_keyword(
"carrier_frame",
_choice("off", "scalar"),
"advanced",
"The carrier phase removed from the history inside each step.",
),
),
"RungeKutta": (),
"LowStorageRungeKutta": _type(
"numerics",
f"{_TYPE_STEPPER_PAGE}#the-defaults",
_keyword("scheme", _choice("rk4ip"), "advanced", "The register scheme."),
),
"absorber": _type(
"numerics",
_TYPE_BOUNDARY_PAGE,
_keyword(
"boundary_N",
_POSITIVE,
"advanced",
"Absorber strength: how often the collar is applied over the run.",
),
_keyword(
"boundary_length",
_LENGTH,
"advanced",
"Reference length, in place of the strength.",
display="m",
),
_keyword(
"temporal",
_TYPE_BOOLEAN,
"advanced",
"Whether the collar absorbs in time; off is a spectral-only collar.",
),
_keyword(
"walkoff",
_TYPE_DURATION,
"advanced",
"Group-delay drift the temporal collar must stop, over the grid's length.",
display="fs",
),
),
"TubeLattice": _type(
"geometry",
"guide/waveguides/antiresonant.md",
_keyword(
"wall_thickness",
_LENGTH,
"basic",
"Wall thickness of a tube.",
display="nm",
),
_keyword("tube_radius", _LENGTH, "basic", "Outer tube radius.", display="um"),
_keyword("n_tubes", _integer(3), "basic", "Number of tubes, at least three."),
_keyword(
"n_terms",
_integer(1),
"advanced",
"Number of radial and azimuthal fit terms.",
),
),
"CoherentWall": _type(
"geometry",
"guide/waveguides/coherent-wall.md",
_keyword(
"condition_limit",
_number(minimum=1.0),
"advanced",
"Largest admitted condition number of one step's flow action.",
),
_keyword(
"validity_limit",
_POSITIVE,
"advanced",
"Largest admitted validity ratio, above which the block is refused.",
),
_keyword(
"sections",
_integer(1),
"advanced",
"Equal sections each operator-table interval is divided into.",
),
_keyword(
"impedance",
_choice("mean", "te_tm"),
"advanced",
"The impedance-averaged block or the coupled azimuthal one.",
),
_keyword(
"method",
_choice("frozen_residual", "cf4"),
"advanced",
"How a guide that varies along z is integrated.",
),
_keyword(
"growth_limit",
_number(minimum=1.0),
"advanced",
"Largest admitted internal growth of a CF4 section.",
),
_keyword(
"fallback",
_choice("frozen_residual"),
"advanced",
"What a section the CF4 guards reject becomes; the default refuses it.",
),
),
"MarcatiliMode": _type(
"geometry",
"guide/waveguides/capillary-model.md",
_keyword("radius", (_LENGTH, _PROFILE), "basic", "Core radius.", display="um"),
_keyword("kind", _choice("HE", "TE", "TM"), "basic", "The mode family."),
_keyword("n", _integer(), "basic", "Azimuthal index."),
_keyword("m", _integer(1), "basic", "Radial index."),
_keyword(
"phi",
_number("rad"),
"advanced",
"Rotation of the mode about the axis.",
display="rad",
),
_keyword(
"model",
_choice("full", "reduced"),
"advanced",
"Marcatili's full square root or its leading-order expansion.",
),
*_hollow_rows("cladding"),
),
"RectMode": _type(
"geometry",
"guide/waveguides/rectangular.md",
_keyword(
"half_width_x",
(_LENGTH, _PROFILE),
"basic",
"Half-width of the core in x.",
display="um",
),
_keyword(
"half_width_y",
(_LENGTH, _PROFILE),
"basic",
"Half-width of the core in y.",
display="um",
),
_keyword("n", _integer(1), "basic", "Mode index along y."),
_keyword("m", _integer(1), "basic", "Mode index along x."),
_keyword(
"polarisation",
_choice("x", "y"),
"basic",
"The transverse electric-field component.",
),
*_hollow_rows("wall"),
),
"SlabMode": _type(
"geometry",
_PLANAR_PAGE,
_keyword(
"half_gap",
(_LENGTH, _PROFILE),
"basic",
"Half the plate separation.",
display="um",
),
*_slab_fill((_TEMPERATURE, _PROFILE))[:2],
_keyword(
"order", _integer(1), "basic", "Mode order; odd orders are symmetric."
),
*_slab_fill((_TEMPERATURE, _PROFILE))[2:],
),
"ModeCollection.slab": _type(
"geometry",
_PLANAR_PAGE,
_keyword(
"half_gap",
(_LENGTH, _PROFILE),
"basic",
"Half the plate separation.",
display="um",
),
_keyword(
"orders",
Form("array", minimum=1.0),
"basic",
"The mode orders, without repeats, in state order.",
),
*_slab_fill(_TEMPERATURE),
),
"MaterialSelection": _type(
"medium",
"guide/media/material-selection.md",
_keyword(
"linear",
(_STRING, _UNREGISTERED),
"advanced",
"The linear dispersion model of a gas.",
),
_keyword(
"electronic",
_STRING,
"advanced",
"The electronic third-order policy.",
),
_keyword("raman", (_STRING, _UNREGISTERED), "advanced", "The Raman response."),
_keyword(
"ionisation",
(_STRING, _UNREGISTERED),
"advanced",
"The ionisation rate model.",
),
_keyword(
"carrier_wavelength",
_LENGTH,
"advanced",
"Carrier wavelength the carrier coefficients are evaluated at.",
display="nm",
),
_keyword(
"energy_loss",
_choice("peak-weighted"),
"advanced",
"The plasma energy-loss convention.",
),
_keyword(
"raman_shift",
_choice("reference-density"),
"advanced",
"Hold a density-dependent Raman shift at a reference density.",
),
),
"RadialGrid": _type(
"grid",
f"{_FREE_PAGE}",
_keyword("radius", _LENGTH, "basic", "The aperture modelled.", display="mm"),
_keyword("n_r", _integer(2), "basic", "Number of radial samples."),
_keyword(
"collar_fraction",
_number(minimum=0.0),
"advanced",
"Room outside the aperture as a fraction of it.",
),
),
"FreeGrid": _type(
"grid",
_FREE_PAGE,
_keyword("radius_x", _LENGTH, "basic", "Half-width in x.", display="mm"),
_keyword("n_x", _integer(2), "basic", "Samples in x."),
_keyword("radius_y", _LENGTH, "basic", "Half-width in y.", display="mm"),
_keyword("n_y", _integer(2), "basic", "Samples in y."),
_keyword(
"collar_fraction",
_number(minimum=0.0),
"advanced",
"Room outside each half-width as a fraction of it.",
),
),
"LineGrid": _type(
"grid",
_FREE_PAGE,
_keyword("radius_x", _LENGTH, "basic", "Half-width in x.", display="mm"),
_keyword("n_x", _integer(2), "basic", "Samples in x."),
_keyword(
"collar_fraction",
_number(minimum=0.0),
"advanced",
"Room outside the half-width as a fraction of it.",
),
),
"PlanarGrid": _type(
"grid",
_PLANAR_PAGE,
_keyword(
"modes",
_object("ModeCollection.slab", "SlabMode"),
"basic",
"The slab channels, in state order.",
),
_keyword("radius_y", _LENGTH, "basic", "Half-width in y.", display="mm"),
_keyword("n_y", _integer(2), "basic", "Samples in y."),
_keyword(
"collar_fraction",
_number(minimum=0.0),
"advanced",
"Room outside the half-width in y as a fraction of it.",
),
_keyword(
"n_x",
_integer(1),
"advanced",
"Nodes of the x rule; the default follows the orders.",
),
_keyword(
"x_rule",
_choice("gauss_kronrod", "gauss"),
"advanced",
"The x rule's family.",
),
),
"SpatialMedium": _type(
"medium",
_FREE_PAGE,
_keyword(
"material",
(
_STRING,
_number(),
_object("Mixture.fill", "Mixture.partial_pressures", "Mixture.air"),
_UNREGISTERED,
),
"basic",
"A gas name, a glass, a constant index or a mixture.",
),
_keyword(
"pressure",
(_PRESSURE, _PROFILE),
"basic",
"Gas pressure, constant or along z.",
display="bar",
),
_keyword(
"temperature",
_TEMPERATURE,
"advanced",
"Gas temperature; the default is room temperature.",
display="K",
),
),
"KerrResponse": _type(
"nonlinear",
"guide/media/nonlinear.md",
_keyword(
"chi3",
(_number(), _object("gamma3")),
"basic",
"Third-order susceptibility per unit density.",
),
_keyword(
"thg",
_TYPE_BOOLEAN,
"advanced",
"Whether the response radiates the third harmonic.",
),
_keyword("species", _STRING, "advanced", "Component label in a mixture."),
),
"gamma3": _type(
"nonlinear",
"guide/media/material-selection.md",
_keyword("gas", _GAS, "basic", "A built-in gas other than air."),
_keyword(
"wavelength",
_LENGTH,
"basic",
"Carrier wavelength the coefficient is taken at.",
display="nm",
),
),
"EnvelopePlasmaResponse": _plasma(),
"PlasmaResponse": _plasma(
_keyword(
"integration",
_choice("band", "trapezoid"),
"advanced",
"The running-integral rule of the electron density.",
),
),
"OptmatRate": _type(
"nonlinear",
_IONISATION_PAGE,
_keyword("source", _GAS, "basic", "The species optmat curates a rate for."),
_keyword(
"wavelength",
_LENGTH,
"basic",
"Carrier wavelength the rate is taken at.",
display="nm",
),
_keyword(
"model",
_STRING,
"advanced",
"The rate model; the default is optmat's recommended one.",
),
_keyword(
"reference_field",
_POSITIVE,
"advanced",
"Field amplitude in V/m the energy per event is taken at.",
),
_keyword(
"field_range",
Form("pair", minimum=0.0, length=2),
"advanced",
"Field amplitudes in V/m the rate is tabulated over.",
),
_keyword(
"negligible_rate",
_number("s^-1", minimum=0.0),
"advanced",
"Rate below which the rate is zero.",
),
_keyword(
"maximum",
_number("s^-1", minimum=0.0, exclusive=True),
"advanced",
"Ceiling on the rate.",
),
),
"SpatialPulse": _beam(),
"PlanarPulse": _beam(
_keyword(
"waist_x",
_LENGTH,
"basic",
"The 1/e field radius in x.",
display="um",
),
_keyword(
"offset_x",
_POSITION,
"advanced",
"The beam centre's offset from the mid-plane in x.",
display="um",
),
),
"SimulationSettings": _type(
"numerics",
"guide/setup/running.md",
_keyword(
"controller",
_object("PIController"),
"advanced",
"The step-size controller: tolerances, safety and gains.",
),
_keyword(
"save_planes",
(Form("array", unit="m", minimum=0.0, exclusive=True), _integer(1)),
"basic",
"Positions to save at, or how many equally spaced planes.",
display="m",
),
_keyword("dz_max", _LENGTH, "advanced", "Largest step.", display="mm"),
_keyword("dz_init", _LENGTH, "advanced", "First step.", display="mm"),
_keyword(
"dz_min",
_LENGTH,
"advanced",
"Smallest step; the default follows the run length.",
display="mm",
),
_keyword("stretch", _POSITIVE, "advanced", "Landing stretch factor."),
_keyword(
"interval_cap",
_TYPE_BOOLEAN,
"advanced",
"Cap each step at its operator-table interval.",
),
_keyword(
"interpolate_planes",
_TYPE_BOOLEAN,
"advanced",
"Observe the save planes inside accepted steps.",
),
_keyword(
"step_planes",
_integer(0),
"advanced",
"Also save the end of every k-th accepted step.",
),
_keyword(
"schedule",
_object("FixedSchedule.uniform", "FixedSchedule"),
"advanced",
"A fixed schedule to replay instead of stepping adaptively.",
),
_keyword(
"propagator_mode",
_choice("auto", "tabulate", "recompute"),
"advanced",
"Whether the backend tabulates the stage propagators.",
),
_keyword(
"shock",
_TYPE_BOOLEAN,
"advanced",
"Keep the optical shock term.",
),
_keyword(
"absorber",
_object("absorber"),
"advanced",
"The temporal and spectral collar.",
),
_keyword(
"absorber_scheme",
_choice("split_step", "nonlinear"),
"advanced",
"How the temporal collar is applied.",
),
_keyword(
"ionisation_field_range",
Form("pair", minimum=0.0, length=2),
"advanced",
"Field amplitudes in V/m the ionisation-rate table covers.",
),
_keyword(
"node_budget",
_integer(1),
"advanced",
"Bytes of node buffers a response pass may hold.",
),
_keyword(
"stats",
_object("StatsSpec", "default_spec"),
"advanced",
"The statistics collected at every saved plane.",
),
_keyword(
"polarisation_taper",
_TYPE_BOOLEAN,
"advanced",
"Whether the spectral collar also tapers the nonlinear polarisation.",
),
_keyword(
"backend",
_STRING,
"advanced",
"The backend that executes the run.",
),
_keyword(
"integrator",
_object("Multistep", "RungeKutta", "LowStorageRungeKutta"),
"advanced",
"The method that steps the run.",
),
_keyword(
"precision",
_choice("double", "single"),
"advanced",
"The arithmetic the run is held in.",
),
_keyword(
"evanescent_rate",
_number("m^-1", minimum=0.0, exclusive=True),
"advanced",
"Cap on the evanescent field attenuation.",
),
_keyword(
"spatial_absorber",
(_object("SpatialAbsorber"), _TYPE_BOOLEAN),
"advanced",
"The transverse boundary; false for none.",
),
_keyword(
"boundary_check",
_object("BoundaryCheck"),
"advanced",
"How the run watches its time window's edges.",
),
),
"PIController": _type(
"numerics",
"guide/numerics/solver.md",
_keyword("rtol", _POSITIVE, "basic", "Relative tolerance."),
_keyword(
"atol", _number(minimum=0.0), "basic", "Absolute tolerance, in state units."
),
_keyword(
"safety",
_number(minimum=0.0, maximum=1.0),
"advanced",
"Factor applied to every proposal.",
),
_keyword(
"max_factor",
_number(minimum=1.0, exclusive=True),
"advanced",
"Largest growth per step.",
),
_keyword(
"min_factor",
_number(minimum=0.0, maximum=1.0, exclusive=True),
"advanced",
"Smallest shrink per step.",
),
_keyword("beta1", _POSITIVE, "advanced", "Proportional gain."),
_keyword("beta2", _number(), "advanced", "Integral gain."),
_keyword(
"reject_free",
_TYPE_BOOLEAN,
"advanced",
"Never reject a step whose error is finite.",
),
_keyword(
"region",
_choice("active", "band"),
"advanced",
"The spectral bins the error norm scores.",
),
),
"SpatialAbsorber": _type(
"numerics",
f"{_TYPE_BOUNDARY_PAGE}#the-transverse-boundary",
_keyword(
"kappa",
_number("rad/m", minimum=0.0, exclusive=True),
"advanced",
"The protected band's edge in transverse wavenumber.",
),
_keyword(
"angle",
_number("rad", minimum=0.0, exclusive=True),
"advanced",
"The protected band as an angle in vacuum.",
display="mrad",
),
_keyword(
"discard_above",
_number("rad/m", minimum=0.0, exclusive=True),
"advanced",
"Wavenumber above which no physical light is declared.",
),
_keyword(
"attenuation",
_POSITIVE,
"advanced",
"The collar's attenuation at the band's edge, in field nepers.",
),
_keyword(
"safety_fraction",
_number(minimum=0.0, maximum=1.0, exclusive=True),
"advanced",
"The safety ramp's width as a fraction of each axis's largest wavenumber.",
),
_keyword(
"real_space",
_TYPE_BOOLEAN,
"advanced",
"Whether a real-space collar is asked for.",
),
),
"default_spec": _type(
"output",
"guide/results/statistics.md",
_keyword(
"kind",
_choice("mode_averaged", "multimode", "radial", "free", "slab"),
"basic",
"Which transform the run uses.",
),
_keyword(
"with_profiles",
_TYPE_BOOLEAN,
"advanced",
"Also record the gas density, pressure and core radius.",
),
_keyword(
"with_zdw",
_TYPE_BOOLEAN,
"advanced",
"Also record each mode's zero-dispersion wavelength.",
),
),
}
_TYPES: Final[Mapping[str, tuple[Parameter, ...]]] = MappingProxyType(_ENTRIES)
"""Each registered type's keyword entries, by the ``type`` a recipe writes (S9 for the
tables): the units are the registry's, the group and anchor the type's. The defaults are
left to :func:`parameters`, which reads them from the target's signature;
``RungeKutta`` has no keyword a recipe gives.
"""
[docs]
def parameters(route: str) -> Mapping[str, Parameter]:
"""Return the metadata of a route's build keywords, with their defaults.
Parameters
----------
route : str
A route of :data:`solprop.recipe.core.ROUTES`, ``"execution"`` for the
``[execution]`` table's keys, or a registered type's name
(:data:`solprop.recipe.registry.REGISTRY`) for the keywords of its table.
Returns
-------
collections.abc.Mapping
:class:`Parameter` by keyword, in the constructor's order, each with the
default its signature declares (``REQUIRED`` where it has none). An object
route's table leaves out :data:`SETTINGS_KEYWORDS`, which a recipe writes
under ``settings``; a type's leaves out what it receives as its build context
and the keywords a recipe may not give
(:attr:`~solprop.recipe.registry.Factory.omitted`).
Examples
--------
>>> from solprop.recipe.metadata import parameters
>>> table = parameters("gnlse")
>>> table["rtol"].default is None, table["rtol"].resolver({}).value
(True, 1e-07)
>>> parameters("execution")["fields"].default
'all'
>>> pulse = parameters("GaussianPulse")
>>> pulse["fwhm"].display, pulse["m"].default
('fs', 1)
"""
if route in _TYPES:
return _type_parameters(route, _TYPES[route])
if route == "execution":
from solprop.driver.simulation import ( # noqa: PLC0415 - layer D
SimulationSettings,
)
fields = {field.name: field for field in dataclasses.fields(SimulationSettings)}
out = {}
for entry in _EXECUTION:
field = fields.get(entry.name)
default = (
None
if field is None or field.default is dataclasses.MISSING
else field.default
)
out[entry.name] = dataclasses.replace(entry, default=default)
return MappingProxyType(out)
signature = inspect.signature(surface(route))
entries = {entry.name: entry for entry in _route_entries(route)}
skipped = SETTINGS_KEYWORDS if route in OBJECT_ROUTES else ()
# Every keyword of the constructor has an entry, as the metadata tests check.
ordered = {
name: dataclasses.replace(
entries[name],
default=REQUIRED
if parameter.default is inspect.Parameter.empty
else parameter.default,
)
for name, parameter in signature.parameters.items()
if name not in skipped
}
return MappingProxyType(ordered)
def _type_parameters(
name: str, entries: Sequence[Parameter]
) -> Mapping[str, Parameter]:
"""Return a registered type's keyword metadata, with its target's defaults.
Parameters
----------
name : str
The type, as a recipe names it.
entries : collections.abc.Sequence of Parameter
Its entries (:data:`_TYPES`).
Returns
-------
collections.abc.Mapping
:class:`Parameter` by keyword, in the target's order.
"""
from solprop.recipe.registry import REGISTRY # noqa: PLC0415 - a cycle
factory = REGISTRY[name]
by_name = {entry.name: entry for entry in entries}
signature = inspect.signature(factory.target)
target: object = factory.target
fields = (
{field.name: field for field in dataclasses.fields(target)}
if isinstance(target, type) and dataclasses.is_dataclass(target)
else {}
)
keywords = [
parameter
for parameter in signature.parameters.values()
if parameter.kind not in (parameter.VAR_POSITIONAL, parameter.VAR_KEYWORD)
and parameter.name not in factory.omitted
][len(factory.context) :]
out = {}
for parameter in keywords:
field = fields.get(parameter.name)
if field is not None and field.default_factory is not dataclasses.MISSING:
default = field.default_factory()
elif parameter.default is inspect.Parameter.empty:
default = REQUIRED
else:
default = parameter.default
# Every keyword of the target has an entry, as the metadata tests check.
out[parameter.name] = dataclasses.replace(
by_name[parameter.name], default=default
)
return MappingProxyType(out)