diff --git a/AGENTS.md b/AGENTS.md index 8dafb711e..3a1b1f192 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -161,13 +161,15 @@ validity range is cited to its primary source; say what you verified and what yo as doctests ⚙. A frozen dataclass documents its fields under `Attributes`. A public property has a one-line docstring naming the quantity and unit, with `# numpydoc ignore=RT01` on the `def` line. -- **Private code** — any name with a single-underscore component, which numpydoc skips ⚙; - a nested function's name starts with an underscore, so it is private too — has no - `Parameters`, `Returns`, `Yields`, `Raises` or `Examples` sections (`poe size-check` - caps those left ⚙): one summary line, then only what +- **Private code** — any name with a single-underscore component, which numpydoc skips ⚙ + — has no `Parameters`, `Returns`, `Yields`, `Raises` or `Examples` sections + (`poe size-check` caps those left ⚙): one summary line, then only what the signature and annotations don't say — a unit, a valid range, a condition, a convention, or why it exists. A function whose name and signature say everything needs - no docstring; a non-obvious inner function gets a one-line comment. + no docstring; a non-obvious inner function gets a one-line comment. A nested function + is private. numpydoc still checks one without a leading underscore, so a nested + function that takes parameters or returns a value is named with a leading underscore + unless it is returned to callers. - Comments explain **why**, never **what**: the physics (or the page with the derivation), the numerics (why this quadrature, cutoff or tolerance; where precision is lost), sign, unit and normalisation choices, references (DOI, equation), and anything a reader would diff --git a/docs/dev/tooling.md b/docs/dev/tooling.md index 2810941f6..fa46bc269 100644 --- a/docs/dev/tooling.md +++ b/docs/dev/tooling.md @@ -186,8 +186,8 @@ files and functions; `noqa` comments by rule; private names imported across pack `__all__` lines; cloned code (windows of 50 tokens seen twice, with names renamed and as written) with the file pairs that share most; and ruff's complexity counts. A name is private by exactly the rule numpydoc's validation excludes (a single-underscore component -of `module.qualname`), so the report and `poe docstrings` agree; a nested function is private -when its name starts with an underscore, as `AGENTS.md` asks. +of `module.qualname`), so the report and `poe docstrings` agree; both count a nested +function as private only when its name starts with an underscore. `poe size-check`, in `poe check` and CI's static job, holds `src/solprop` to the ratchets in `tools/size_budget.toml`: the number of private docstrings with numpydoc sections, a diff --git a/native/src/solprop_native/__init__.py b/native/src/solprop_native/__init__.py index 5ec1553af..a5da943a5 100644 --- a/native/src/solprop_native/__init__.py +++ b/native/src/solprop_native/__init__.py @@ -875,16 +875,6 @@ def _source_digest(root: Path) -> str: ``CMakeLists.txt`` and each C++ source and header under ``src`` and ``include``, the paths relative to `root` and sorted bytewise, and the digest is the SHA-256 of the record. - - Parameters - ---------- - root - The ``native`` directory of a checkout. - - Returns - ------- - str - The digest in lowercase hexadecimal. """ paths = ["CMakeLists.txt"] for folder in ("src", "include"): @@ -905,11 +895,8 @@ def _source_digest(root: Path) -> str: def _checkout_digest() -> str | None: """Return this editable checkout's source digest, computed once per process. - Returns - ------- - str or None - The digest, or ``None`` for an installed wheel, which has no sources beside it - and which solprop's metadata pins to its own version. + - Returns: the digest, or ``None`` for an installed wheel, which has no sources + beside it and which solprop's metadata pins to its own version. """ root = Path(__file__).parents[2] return _source_digest(root) if (root / "CMakeLists.txt").is_file() else None diff --git a/native/src/solprop_native/_allocation.py b/native/src/solprop_native/_allocation.py index 90cc5e22f..f6456a15b 100644 --- a/native/src/solprop_native/_allocation.py +++ b/native/src/solprop_native/_allocation.py @@ -74,13 +74,7 @@ def record(self) -> dict[str, object]: @classmethod def read(cls, data: Mapping[str, object]) -> AllocationClass: - """Read a class written by :meth:`record`. - - Raises - ------ - ValueError - When `data` is not a class. - """ + """Read a class written by :meth:`record`.""" try: kinds = data["kinds"] quota = data.get("quota") diff --git a/native/src/solprop_native/_budget.py b/native/src/solprop_native/_budget.py index 7c388db96..0adbbdec4 100644 --- a/native/src/solprop_native/_budget.py +++ b/native/src/solprop_native/_budget.py @@ -98,18 +98,9 @@ def _read_cgroup_cpus(root: Path) -> int | None: def cores_in_masks(masks: Sequence[int], cpus: Sequence[int]) -> int: """Count the cores, given as CPU bitmasks, that hold any of ``cpus``. - Parameters - ---------- - masks : sequence of int - One bitmask per physical core: bit ``n`` set when CPU ``n`` is one of its - hardware threads. - cpus : sequence of int - The CPU indices the process may run on. - - Returns - ------- - int - The number of cores holding at least one of ``cpus``. + - ``masks``: one bitmask per physical core: bit ``n`` set when CPU ``n`` is one of + its hardware threads. + - Returns: the number of cores holding at least one of ``cpus``. """ wanted = sum(1 << cpu for cpu in cpus) return sum(1 for mask in masks if mask & wanted) @@ -118,17 +109,8 @@ def cores_in_masks(masks: Sequence[int], cpus: Sequence[int]) -> int: def _linux_cores(cpus: Sequence[int], root: Path) -> int | None: """Count the distinct (package, core) pairs of ``cpus`` in a sysfs CPU tree. - Parameters - ---------- - cpus : sequence of int - The CPU indices the process may run on. - root : Path - The sysfs CPU directory, ``/sys/devices/system/cpu`` on a running system. - - Returns - ------- - int or None - The number of physical cores, or ``None`` if any CPU's topology is unreadable. + - Returns: the number of physical cores, or ``None`` if any CPU's topology is + unreadable. """ pairs: set[tuple[str, str]] = set() for cpu in cpus: @@ -148,15 +130,7 @@ def _linux_cores(cpus: Sequence[int], root: Path) -> int | None: def _windows_cores(cpus: Sequence[int]) -> int | None: # pragma: no cover - Windows """Count the physical cores holding ``cpus`` from Windows' processor table. - Parameters - ---------- - cpus : sequence of int - The CPU indices the process may run on (processor group 0). - - Returns - ------- - int or None - The number of physical cores, or ``None`` if the table cannot be read. + - Returns: the number of physical cores, or ``None`` if the table cannot be read. """ import ctypes @@ -198,18 +172,8 @@ def physical_cores( reads each CPU's package and core from ``root``; Windows asks the kernel. macOS on Apple silicon has one thread per core, so the CPUs are the cores. - Parameters - ---------- - cpus : sequence of int - The CPU indices the process may run on. - root : Path, optional - The sysfs CPU directory, read on Linux. - - Returns - ------- - int or None - The number of physical cores, or ``None`` on other platforms or when the - topology cannot be read. + - Returns: the number of physical cores, or ``None`` on other platforms or when the + topology cannot be read. """ if sys.platform.startswith("linux"): return _linux_cores(cpus, root) diff --git a/native/src/solprop_native/_counters.py b/native/src/solprop_native/_counters.py index 17229703c..ac4d584f0 100644 --- a/native/src/solprop_native/_counters.py +++ b/native/src/solprop_native/_counters.py @@ -151,19 +151,11 @@ def march( ) -> None: """Count one native march call from its returned rows. - Parameters - ---------- - kind : str - The march, ``adams_march`` or ``rk_march``. - reason : str - The call's ``march::Reason``, by name. - rows : numpy.ndarray - The call's attempt rows, the accepted flag in column 3. - due : dict - Accepted steps from the call's start to each piece of the driver's work, - by name, as the call's ``host_step`` bound was formed. - read_at : float or None - The transverse edge's next read, if the call was bounded by one. + - ``kind``: the march, ``adams_march`` or ``rk_march``. + - ``rows``: the call's attempt rows, the accepted flag in column 3. + - ``due``: accepted steps from the call's start to each piece of the driver's + work, by name, as the call's ``host_step`` bound was formed. + - ``read_at``: the transverse edge's next read, if the call was bounded by one. """ accepted = int(np.count_nonzero(rows[:, 3])) if rows.size else 0 self.march_calls[kind] += 1 @@ -191,25 +183,8 @@ def session_batch( ) -> None: """Count one batch a native session returned from ``advance``. - Parameters - ---------- - event : str - Its event's kind: ``observation``, ``snapshot``, ``warning``, - ``terminal``, or ``progress:`` and its cause. - attempts : int - The attempts its chunk carries. - accepted : int - The accepted steps among them. - rows : int, optional - The periodic rows its chunk carries. - evaluations : int, optional - The statistics evaluations it carries: its rows and its plane statistics. - plane : bool, optional - Whether it is an observation after the launch. - march : str, optional - The session's march, for its native march calls. - calls : int, optional - The native march calls the ``advance`` made. + - ``event``: its event's kind: ``observation``, ``snapshot``, ``warning``, + ``terminal``, or ``progress:`` and its cause. """ self.returns[f"session_{event}"] += 1 self.march_attempts += attempts diff --git a/native/src/solprop_native/_cpu.py b/native/src/solprop_native/_cpu.py index 3adb62ab0..647da1c95 100644 --- a/native/src/solprop_native/_cpu.py +++ b/native/src/solprop_native/_cpu.py @@ -192,13 +192,6 @@ class CpuBaselineError(ImportError): """The processor is below the CPU baseline this build was compiled for. - Parameters - ---------- - baseline - The build's recorded CPU baseline, for example ``"x86-64-v3"``. - missing - The required features the processor lacks. - Attributes ---------- baseline : str @@ -221,12 +214,8 @@ def __init__(self, baseline: str, missing: Iterable[str]) -> None: class CpuFeaturesUnknownError(ImportError): """This processor's features could not be established, so the build isn't loaded. - Parameters - ---------- - baseline - The build's recorded CPU baseline, for example ``"x86-64-v3"``. - variants - The builds that :data:`VARIANT_SETTING` may name; empty for a single build. + - ``variants``: the builds that :data:`VARIANT_SETTING` may name; empty for a single + build. Attributes ---------- @@ -253,18 +242,8 @@ def __init__(self, baseline: str, variants: Iterable[str] = ()) -> None: def missing_features(baseline: str, flags: Iterable[str]) -> list[str]: """Return the features ``baseline`` requires that ``flags`` lacks, sorted. - Parameters - ---------- - baseline - The recorded CPU baseline, for example ``"x86-64-v3"``. - flags - The processor's feature names as ``/proc/cpuinfo`` spells them. - - Returns - ------- - list[str] - Missing features; empty when all are present or the baseline is not one this - module checks. + - Returns: missing features; empty when all are present or the baseline is not one + this module checks. """ required = REQUIRED_FEATURES.get(baseline) if required is None: @@ -275,17 +254,8 @@ def missing_features(baseline: str, flags: Iterable[str]) -> list[str]: def cpuinfo_flags(cpuinfo: str, key: str = "flags") -> frozenset[str] | None: """Return the first processor's feature names from ``/proc/cpuinfo`` text. - Parameters - ---------- - cpuinfo - The file's contents. - key - The line naming them: ``"flags"`` on x86, ``"Features"`` on Arm. - - Returns - ------- - frozenset[str] | None - The names, or ``None`` when the text has no such line. + - ``key``: the line naming them: ``"flags"`` on x86, ``"Features"`` on Arm. + - Returns: the names, or ``None`` when the text has no such line. """ for line in cpuinfo.splitlines(): name, separator, value = line.partition(":") @@ -297,17 +267,8 @@ def cpuinfo_flags(cpuinfo: str, key: str = "flags") -> frozenset[str] | None: def windows_features(is_present: Callable[[int], object]) -> frozenset[str]: """Return the x86-64 features Windows reports, with those they stand for. - Parameters - ---------- - is_present - ``kernel32.IsProcessorFeaturePresent``, or a stand-in: takes a ``PF_*`` - constant and returns a true value when the feature is present. - - Returns - ------- - frozenset[str] - The features in Linux's spelling, including those Windows does not report but - a reported one stands for (module docstring). + - ``is_present``: ``kernel32.IsProcessorFeaturePresent``, or a stand-in: takes a + ``PF_*`` constant and returns a true value when the feature is present. """ present = {name for name, constant in _PF_FEATURES.items() if is_present(constant)} for marker, implied in _WINDOWS_IMPLIED.items(): @@ -317,18 +278,7 @@ def windows_features(is_present: Callable[[int], object]) -> frozenset[str]: def windows_arm_features(is_present: Callable[[int], object]) -> frozenset[str]: - """Return what Windows on Arm reports of the features this module knows. - - Parameters - ---------- - is_present - ``IsProcessorFeaturePresent``, or a stand-in for tests. - - Returns - ------- - frozenset[str] - The features in Linux's spelling. - """ + """Return what Windows on Arm reports of the features this module knows.""" return frozenset( name for name, constant in _PF_ARM_FEATURES.items() if is_present(constant) ) @@ -337,11 +287,8 @@ def windows_arm_features(is_present: Callable[[int], object]) -> frozenset[str]: def numpy_features() -> frozenset[str] | None: """Return NumPy's view of the x86-64 features, or ``None`` if it gives none. - Returns - ------- - frozenset[str] | None - The features in Linux's spelling; ``None`` when NumPy no longer has the private - name, or lacks one of the features asked for. + - Returns: the features in Linux's spelling; ``None`` when NumPy no longer has the + private name, or lacks one of the features asked for. """ try: from numpy._core._multiarray_umath import ( @@ -355,13 +302,7 @@ def numpy_features() -> frozenset[str] | None: def kernel32_feature_query() -> Callable[[int], object]: - """Return ``kernel32.IsProcessorFeaturePresent``; Windows only. - - Returns - ------- - collections.abc.Callable - The function, typed to take a ``DWORD`` and return a ``BOOL``. - """ + """Return ``kernel32.IsProcessorFeaturePresent``; Windows only.""" import ctypes # only a Windows check asks kernel32 kernel32 = ctypes.WinDLL("kernel32") # pyright: ignore[reportAttributeAccessIssue, reportUnknownMemberType, reportUnknownVariableType] @@ -408,26 +349,10 @@ def baseline_shortfall( ) -> CpuBaselineError | CpuFeaturesUnknownError | None: """Return the refusal a processor below this build's baseline earns, or ``None``. - Parameters - ---------- - package_dir - The installed package directory, holding the baseline record. - platform - ``sys.platform`` (the default), or a stand-in for tests. - cpuinfo - The processor description read on Linux. - feature_query - Returns Windows's ``IsProcessorFeaturePresent``; a stand-in for tests. - numpy_query - Returns NumPy's view of the features; a stand-in for tests. - - Returns - ------- - CpuBaselineError | CpuFeaturesUnknownError | None - The error naming the missing features, or the error saying they couldn't be - established; ``None`` when the processor meets the baseline, this module doesn't - check the baseline, or :data:`VARIANT_SETTING` has named a build of an unknown - processor. + - Returns: the error naming the missing features, or the error saying they couldn't + be established; ``None`` when the processor meets the baseline, this module + doesn't check the baseline, or :data:`VARIANT_SETTING` has named a build of an + unknown processor. """ try: baseline = (package_dir / BASELINE_RECORD).read_text(encoding="utf-8").strip() @@ -452,22 +377,7 @@ def baseline_shortfall( def check_cpu_baseline(package_dir: Path, cpuinfo: Path = CPUINFO) -> None: - """Refuse to continue on a processor below this build's CPU baseline. - - Parameters - ---------- - package_dir - The installed package directory, holding the baseline record. - cpuinfo - The processor description read on Linux, ``/proc/cpuinfo``. - - Raises - ------ - CpuBaselineError - If the processor lacks a feature of the recorded baseline. - CpuFeaturesUnknownError - If nothing could establish the processor's features. - """ + """Refuse to continue on a processor below this build's CPU baseline.""" error = baseline_shortfall(package_dir, cpuinfo=cpuinfo) if error is not None: raise error @@ -476,15 +386,7 @@ def check_cpu_baseline(package_dir: Path, cpuinfo: Path = CPUINFO) -> None: def read_variants(package_dir: Path) -> tuple[tuple[str, str], ...]: """Return a wheel's builds, best first, as ``(baseline, module stem)`` pairs. - Parameters - ---------- - package_dir - The installed package directory. - - Returns - ------- - tuple[tuple[str, str], ...] - The builds; empty when the package holds only its one extension. + - Returns: the builds; empty when the package holds only its one extension. """ try: text = (package_dir / VARIANTS_RECORD).read_text(encoding="utf-8") @@ -505,33 +407,9 @@ def select_variant( ) -> str: """Return the module stem of the build to load on this processor. - Parameters - ---------- - package_dir - The installed package directory. - requested - A baseline to run instead, which must be listed and no better than the one - chosen; the default reads ``SOLPROP_NATIVE_CPU_VARIANT``. - platform - ``sys.platform`` (the default), or a stand-in for tests. - cpuinfo - The processor description read on Linux. - feature_query - Returns Windows's ``IsProcessorFeaturePresent``; a stand-in for tests. - numpy_query - Returns NumPy's view of the features; a stand-in for tests. - - Returns - ------- - str - The stem; ``"_core"`` for a package with one extension. - - Raises - ------ - ValueError - If ``requested`` names no listed build, or one better than this processor runs. - CpuFeaturesUnknownError - If nothing could establish the processor's features and no build is named. + - ``requested``: a baseline to run instead, which must be listed and no better than + the one chosen; the default reads ``SOLPROP_NATIVE_CPU_VARIANT``. + - Returns: the stem; ``"_core"`` for a package with one extension. """ variants = read_variants(package_dir) request = os.environ.get(VARIANT_SETTING, "") if requested is None else requested diff --git a/native/src/solprop_native/_execution.py b/native/src/solprop_native/_execution.py index 3335000fe..056332eba 100644 --- a/native/src/solprop_native/_execution.py +++ b/native/src/solprop_native/_execution.py @@ -172,13 +172,7 @@ def native(self) -> dict[str, int | bool]: @classmethod def from_native(cls, values: Mapping[str, object]) -> ExecutionRecord: - """Read a record written by :meth:`native` or read back from a state. - - Raises - ------ - ValueError - When a field is missing, unknown or out of range. - """ + """Read a record written by :meth:`native` or read back from a state.""" names = {field.name for field in dataclasses.fields(cls)} if set(values) != names: missing = sorted(names - set(values)) @@ -193,12 +187,6 @@ def updated(self, values: Mapping[str, object]) -> ExecutionRecord: """Return the record with `values`' fields replaced, validated. A profile row holds only the settings it measured; the rest stay as they are. - - Raises - ------ - ValueError - When `values` names a field the record does not hold, or a value is out - of range. """ names = {field.name for field in dataclasses.fields(self)} unknown = sorted(set(values) - names) @@ -249,30 +237,9 @@ def builtin( ``SOLPROP_NATIVE_THREADED_FFT_MIN_SAMPLES``, replaces the FFT start and is never divided. - Parameters - ---------- - system : str - ``"Darwin"``, ``"Linux"``, ``"Windows"`` or another :func:`platform.system` - name. - spin_milliseconds : int - LLVM's block time inside native calls, zero when the runtime does not spin - there. - width : int - The runtime's thread count. - fft_threshold : int or None - The user's FFT start, or ``None``. - - Returns - ------- - ExecutionRecord - The record a workspace of this process takes without a profile. - - Examples - -------- - >>> builtin("Linux").stage_threshold - 4096 - >>> builtin("Darwin", spin_milliseconds=1, width=6).stage_threshold - 512 + - ``spin_milliseconds``: LLVM's block time inside native calls, zero when the + runtime does not spin there. + - ``fft_threshold``: the user's FFT start, or ``None``. """ apple = system == "Darwin" # The stage boundary is platform-dependent. At 8192, just under the calibrated diff --git a/native/src/solprop_native/_helpers.py b/native/src/solprop_native/_helpers.py index f5a594415..4fb62d852 100644 --- a/native/src/solprop_native/_helpers.py +++ b/native/src/solprop_native/_helpers.py @@ -30,20 +30,9 @@ def _basis( ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: """Return the Lagrange basis on `points`: its coefficients, or its integrals. - Parameters - ---------- - points - The distinct nodes, float64, C-contiguous, at most 32 of them. - upper - The integral's upper limit. - integrate - Whether to return the integrals rather than the coefficients. - - Returns - ------- - tuple - The coefficients, shape ``(n, n)`` (zero when integrating), and the integrals, - shape ``(n,)`` (unset when not). + - ``points``: the distinct nodes, float64, C-contiguous, at most 32 of them. + - Returns: the coefficients, shape ``(n, n)`` (zero when integrating), and the + integrals, shape ``(n,)`` (unset when not). """ count = points.shape[0] rows = np.empty((count, count)) @@ -65,23 +54,11 @@ def _tables( ]: """Return an exponential Adams trial's coefficient tables, formed in one call. - Parameters - ---------- - nodes - The history's normalised positions, newest first, float64, C-contiguous. - order - The step's order. - orders - The orders scored, the step's own first. - compensated - Whether the trial carries the transported compensation. - - Returns - ------- - tuple - solprop's ``trial_tables``: the predictor, corrector and factored defect - rows, then the compensation's spans, weights and defect rows, empty when not - compensated. + - ``nodes``: the history's normalised positions, newest first, float64, + C-contiguous. + - Returns: solprop's ``trial_tables``: the predictor, corrector and factored defect + rows, then the compensation's spans, weights and defect rows, empty when not + compensated. """ width = 1 + max(orders) count = len(orders) diff --git a/native/src/solprop_native/_host_blas.py b/native/src/solprop_native/_host_blas.py index 2626207a5..88d65b6e8 100644 --- a/native/src/solprop_native/_host_blas.py +++ b/native/src/solprop_native/_host_blas.py @@ -82,24 +82,11 @@ def hold( ) -> str: """Take the host BLAS pools for one more backend; return what applies. - Parameters - ---------- - own_library : str - This package's BLAS library, which keeps its own fitted width. - budget : int - The process's thread budget, which ``SOLPROP_BLAS_THREADS`` stays within. - controller : threadpoolctl.ThreadpoolController, optional - The library walk; a test passes its own. - acquire : callable or None, optional - An explicit host-lease hook supplied by solprop's backend adapter, so - construction and backend lifetimes share their pool ownership. Standalone - use keeps the local threadpoolctl limit and imports no solprop code. - - Returns - ------- - str - The libraries limited and their width, ``"none"`` when there is no other BLAS, - or ``"unavailable"`` without threadpoolctl. + - ``acquire``: an explicit host-lease hook supplied by solprop's backend adapter, + so construction and backend lifetimes share their pool ownership. Standalone use + keeps the local threadpoolctl limit and imports no solprop code. + - Returns: the libraries limited and their width, ``"none"`` when there is no other + BLAS, or ``"unavailable"`` without threadpoolctl. """ global _holders, _limiter, _reached with _LOCK: diff --git a/native/src/solprop_native/_load.py b/native/src/solprop_native/_load.py index 557620850..5339b228c 100644 --- a/native/src/solprop_native/_load.py +++ b/native/src/solprop_native/_load.py @@ -102,10 +102,7 @@ class Loaded: def native_noise(self) -> ComplexArray: """Return the realisation the native state adds at every stage, flat. - Returns - ------- - numpy.ndarray - The ``modified`` model's realisation, or an empty array. + - Returns: the ``modified`` model's realisation, or an empty array. """ if self.noise is None: return _EMPTY @@ -123,32 +120,11 @@ def load( ) -> Loaded: """Return a start's state, noise and position as the native state takes them. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The run's problem. - stepper : solprop.translate.problem.StepperSettings - Its stepping policy, for a replay's cursor. - field : numpy.ndarray - The state, shape ``(n_modes, n_pol, n_omega)``, of any strides. - noise : solprop.noise.NoiseRealisation or None - The realisation to carry, or ``None`` for the problem's own. - z : float - Where the state belongs, m. - resume : bool - Whether `field` came back from a saved plane of this run, and so never takes - the ``input`` draw. - - Returns - ------- - Loaded - The load. - - Raises - ------ - solprop.exceptions.BackendError - If the field does not match the problem's shape, `z` lies outside the operator - table, or a schedule is replayed and `z` is not one of its edges. + - ``field``: the state, shape ``(n_modes, n_pol, n_omega)``, of any strides. + - ``noise``: the realisation to carry, or ``None`` for the problem's own. + - ``z``: where the state belongs, m. + - ``resume``: `field` came back from a saved plane of this run, and so never takes + the ``input`` draw. """ values = np.ascontiguousarray(field, dtype=np.complex128) if values.shape != problem.shape: @@ -187,22 +163,8 @@ def load( def schedule_index(stepper: StepperSettings, z: float) -> int: """Return how many steps of a replayed schedule a position has consumed. - Parameters - ---------- - stepper : solprop.translate.problem.StepperSettings - The stepping policy. - z : float - The position, m. - - Returns - ------- - int - The index of the schedule's edge at `z`, and 0 without a schedule. - - Raises - ------ - solprop.exceptions.BackendError - If a schedule is replayed and `z` is not one of its edges. + - ``z``: the position, m. + - Returns: the index of the schedule's edge at `z`, and 0 without a schedule. """ schedule = stepper.schedule if schedule is None: @@ -263,13 +225,6 @@ class StageCache: stage, and :meth:`bundles` reads every scalar table once over all of them; the bundles equal :meth:`scalars`'s bit for bit and are kept, by anchor and position, until the next hoist. - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The run's problem. - helpers : solprop._helpers.Helpers - The backend's compiled Clenshaw and basis helpers. """ def __init__(self, problem: PropagationProblem, helpers: Helpers) -> None: @@ -282,10 +237,7 @@ def __init__(self, problem: PropagationProblem, helpers: Helpers) -> None: def hoist(self, z0: float) -> None: """Evaluate the anchor every stage of a step at `z0` subtracts; drop the kept. - Parameters - ---------- - z0 : float - The step's anchor, m. + - ``z0``: the step's anchor, m. """ self._anchor = anchor(self._problem, z0, helpers=self._helpers) self._kept.clear() @@ -295,17 +247,8 @@ def bundles( ) -> tuple[StageScalars, ...]: """Return the scalars of several stages of one step, taken together. - Parameters - ---------- - z0 : float - The step's anchor, m. - positions : sequence of float - The stages' positions, m. - - Returns - ------- - tuple of solprop.translate.stage.StageScalars - One bundle per position, in order. + - ``z0``: the step's anchor, m. + - ``positions``: the stages' positions, m. """ kept = self._kept missing = [z1 for z1 in dict.fromkeys(positions) if (z0, z1) not in kept] @@ -320,17 +263,8 @@ def bundles( def scalars(self, z0: float, z1: float) -> StageScalars: """Return one stage's scalars, a kept bundle where one was taken. - Parameters - ---------- - z0 : float - The step's anchor, m. - z1 : float - The stage's position, m. - - Returns - ------- - solprop.translate.stage.StageScalars - The weights and the scalar path values. + - ``z0``: the step's anchor, m. + - ``z1``: the stage's position, m. """ kept = self._kept.get((z0, z1)) if kept is not None: @@ -347,17 +281,7 @@ def collar_rests(problem: PropagationProblem, stepper: StepperSettings) -> bool: route, a real-space collar sharing the term, single precision, and the Cartesian, line and planar routes keep the collar in every stage (SEP 21 D15). - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The run's problem. - stepper : solprop.translate.problem.StepperSettings - Its stepping policy. - - Returns - ------- - bool - True when the term may be left out while the collar is quiet. + - Returns: True when the term may be left out while the collar is quiet. """ responses = problem.responses geometry = problem.transform.geometry @@ -386,19 +310,7 @@ def propagator_mode( or any on a device build, per element: there an extra field read costs more than the exponential (SEP 24 section 4.3). - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The run's problem. - stepper : solprop.translate.problem.StepperSettings - Its stepping policy. - device : bool - Whether the build executes on a device. - - Returns - ------- - str - ``"tabulate"`` or ``"recompute"``. + - Returns: ``"tabulate"`` or ``"recompute"``. """ if not isinstance(stepper.integrator, LowStorageRungeKutta): return "recompute" diff --git a/native/src/solprop_native/_probe.py b/native/src/solprop_native/_probe.py index 89d55a433..b82fd4e0f 100644 --- a/native/src/solprop_native/_probe.py +++ b/native/src/solprop_native/_probe.py @@ -31,17 +31,7 @@ class NativeProbe: - """The native backend's :class:`~solprop.backends.protocol.TimingProbe`. - - Parameters - ---------- - backend : solprop_native.backend.NativeBackend - The backend, loaded from the probe's start. - loaded : solprop_native._load.Loaded - That load, which a readback loads again. - noise : solprop.noise.NoiseRealisation or None - The realisation the start carried, loaded again with the kept state. - """ + """The native backend's :class:`~solprop.backends.protocol.TimingProbe`.""" def __init__( self, @@ -58,13 +48,9 @@ def __init__( def trial(self, z: float | None = None, dz: float | None = None) -> None: """Take one trial step and keep its end state for :meth:`field`. - Parameters - ---------- - z : float or None, optional - Where the trial starts, m; the loaded position by default. - dz : float or None, optional - Its length, m, positive; the problem's ``dz_init`` by default, or the - scheduled step at the loaded position for the low-storage march. + - ``z``: where the trial starts, m; the loaded position by default. + - ``dz``: its length, m, positive; the problem's ``dz_init`` by default, or the + scheduled step at the loaded position for the low-storage march. """ backend = self._backend start = self._loaded.z if z is None else float(z) @@ -92,17 +78,8 @@ def field(self) -> npt.NDArray[np.complex128]: state is kept first and loaded again after, exactly, so that the probe still commits nothing. - Returns - ------- - numpy.ndarray - An owning copy, internal units and the FFT convention, shape - ``(n_modes, n_pol, n_omega)``. - - Raises - ------ - solprop.exceptions.BackendError - With no trial since the last readback, or for the low-storage march, which - has no trial state. + - Returns: an owning copy, internal units and the FFT convention, shape + ``(n_modes, n_pol, n_omega)``. """ backend = self._backend if backend._resident_low_storage is not None: # pyright: ignore[reportPrivateUsage] diff --git a/native/src/solprop_native/_profile.py b/native/src/solprop_native/_profile.py index d26d7b5bd..25048a5b9 100644 --- a/native/src/solprop_native/_profile.py +++ b/native/src/solprop_native/_profile.py @@ -227,25 +227,9 @@ def descriptor( settings and the machine and build. Equal descriptors reuse; anything else falls back to the machine profile. - Parameters - ---------- - shape : sequence of int - The state's shape. - grid_kind : str - ``"field"`` or ``"envelope"``. - integrator : str - The integrator's name. - sizing : Mapping[str, object] - The workspace's ``execution_sizing``; only its keys are read. - process : Process - The process settings. - fingerprint : str - The machine and build's store key. - - Returns - ------- - str - 24 hexadecimal digits. + - ``grid_kind``: ``"field"`` or ``"envelope"``. + - ``sizing``: the workspace's ``execution_sizing``; only its keys are read. + - Returns: 24 hexadecimal digits. """ text = json.dumps( { @@ -276,28 +260,10 @@ def choose( Under ``SOLPROP_CALIBRATION=off`` nothing is read. An unreadable store or a malformed row leaves the built-in table, with one :class:`StoreWarning`. - Parameters - ---------- - environ : Mapping[str, str] - The environment. - packages : Sequence[Path] - The ``solprop_native`` package's directories, for the build's identity. - stem : str - The chosen extension's stem. - cpus : Sequence[int] - The process's affinity mask; empty for the whole machine. - quota : int or None - Its CPU quota. - topology : Topology or None - The machine's, read when ``None``. - searched : Sequence[Store] or None - The stores to search, the user's and ``SOLPROP_CALIBRATION_PATH``'s when - ``None``. - - Returns - ------- - Choice - What was found. + - ``cpus``: the process's affinity mask; empty for the whole machine. + - ``topology``: the machine's, read when ``None``. + - ``searched``: the stores to search, the user's and ``SOLPROP_CALIBRATION_PATH``'s + when ``None``. """ chosen = mode(environ) if chosen == "off": diff --git a/native/src/solprop_native/_statistics.py b/native/src/solprop_native/_statistics.py index d023b4dd7..97487265b 100644 --- a/native/src/solprop_native/_statistics.py +++ b/native/src/solprop_native/_statistics.py @@ -290,16 +290,8 @@ def translated(error: BaseException) -> BaseException: """Return the reference's exception for a native statistics refusal. - Parameters - ---------- - error : BaseException - What the native evaluation raised, or a failure's message as an exception. - - Returns - ------- - BaseException - The :class:`~solprop.exceptions.SolpropError` the reference raises for the - same condition, or `error` itself when the refusal is not one of them. + - Returns: the :class:`~solprop.exceptions.SolpropError` the reference raises for + the same condition, or `error` itself when the refusal is not one of them. """ message = str(error) for marker, kind in _TRANSLATED: @@ -311,15 +303,7 @@ def translated(error: BaseException) -> BaseException: def _route(problem: PropagationProblem) -> str: """Return the route a problem's statistics read. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem. - - Returns - ------- - str - ``"general"``, ``"spatial"`` or ``"modal"``. + - Returns: ``"general"``, ``"spatial"`` or ``"modal"``. """ if problem.transform.modal_tables is not None: return "general" @@ -331,15 +315,7 @@ def _route(problem: PropagationProblem) -> str: def _metric_varies(problem: PropagationProblem) -> bool: """Return whether a basis's metric varies inside one of its intervals. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem. - - Returns - ------- - bool - True when the dense metric has terms beyond degree zero. + - Returns: True when the dense metric has terms beyond degree zero. """ metric = problem.linear.modal_metric if metric is None or metric.kind == "identity" or metric.dense is None: @@ -348,22 +324,7 @@ def _metric_varies(problem: PropagationProblem) -> bool: def uncovered(problem: PropagationProblem, names: Sequence[str]) -> tuple[str, ...]: - """Return the names the native plan does not evaluate on this problem's route. - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem. - names : sequence of str - Requested statistics. - - Returns - ------- - tuple of str - The names the plan leaves, in request order: a statistic of another route - and, on a basis whose metric varies inside an interval, its metric - statistics (:func:`metric_refusal`). - """ + """Return the names the native plan does not evaluate on this problem's route.""" route = _route(problem) admitted = _ADMITTED[route] if route == "general" and _metric_varies(problem): @@ -401,18 +362,7 @@ class StatisticsLowering: ) def subset(self, names: Sequence[str]) -> StatisticsLowering: - """Return the plan of some of the names, from the same lowered inputs. - - Parameters - ---------- - names : sequence of str - Outputs of this lowering. - - Returns - ------- - StatisticsLowering - Their plan, reading the same tables. - """ + """Return the plan of some of the names, from the same lowered inputs.""" names = tuple(names) values = dict(self.values, outputs=[STATISTIC_IDS[name] for name in names]) return StatisticsLowering( @@ -426,20 +376,7 @@ def subset(self, names: Sequence[str]) -> StatisticsLowering: def unpack(self, packed: np.ndarray) -> dict[str, np.ndarray]: """Name the packed values and give each its catalogue shape. - Parameters - ---------- - packed : numpy.ndarray - The native evaluation, in :attr:`names` order. - - Returns - ------- - dict - The outputs by name, without a batch axis; arithmetic-free. - - Raises - ------ - solprop.exceptions.BackendError - If the evaluation's size is not what the names take. + - Returns: the outputs by name, without a batch axis; arithmetic-free. """ values = np.asarray(packed, dtype=np.float64).reshape(-1) out: dict[str, np.ndarray] = {} @@ -460,21 +397,8 @@ def _packed( ) -> dict[str, object]: """Return a :meth:`PiecewiseChebyshev.packed` table as the native plan reads it. - Parameters - ---------- - edges : numpy.ndarray - The breakpoints, m. - lower, upper : float - The span widened by its slack, m. - coefficients : numpy.ndarray - ``(n_intervals, n_terms, n_values)``. - dtype : type - The slot's type, float64 or complex128. - - Returns - ------- - dict - The plan's table. + - ``edges``: the breakpoints, m. + - ``lower``, ``upper``: the span widened by its slack, m. """ return { "edges": np.ascontiguousarray(edges, dtype=np.float64), @@ -487,23 +411,7 @@ def _packed( def _table(table: PiecewiseChebyshev | None) -> dict[str, object] | None: - """Return a problem's real table as the native plan reads it. - - Parameters - ---------- - table : solprop.translate.chebyshev.PiecewiseChebyshev or None - The table. - - Returns - ------- - dict or None - Its :meth:`~solprop.translate.chebyshev.PiecewiseChebyshev.packed` form. - - Raises - ------ - solprop.exceptions.BackendError - If it is complex. - """ + """Return a problem's real table as the native plan reads it.""" if table is None: return None edges, lower, upper, coefficients = table.packed() @@ -516,20 +424,8 @@ def _table(table: PiecewiseChebyshev | None) -> dict[str, object] | None: def _root(metric: np.ndarray) -> np.ndarray: """Return the metric's square root as :func:`solprop.stats._whitened` forms it. - Parameters - ---------- - metric : numpy.ndarray - ``W``, ``(n_modes, n_modes, n_omega)``. - - Returns - ------- - numpy.ndarray - ``W^(1/2)``, ``(n_omega, n_modes, n_modes)``. - - Raises - ------ - solprop.exceptions.OutputError - If the metric is not positive definite at some frequency, as the reference. + ``W`` is ``(n_modes, n_modes, n_omega)``; ``W^(1/2)`` comes back frequency first, + ``(n_omega, n_modes, n_modes)``. """ values, vectors = np.linalg.eigh(np.moveaxis(metric, (0, 1), (-2, -1))) if np.any(values <= 0.0): @@ -550,21 +446,6 @@ def _root_table(problem: PropagationProblem) -> dict[str, object] | None: the host), so its root there is the root of the very values the reference reads at every position of the interval: exact, as a table of degree zero over the reference's own breakpoints and slack. - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The general modal problem. - - Returns - ------- - dict or None - The table as the native plan reads it. - - Raises - ------ - solprop.exceptions.BackendError - For a metric that is not dense and constant on each interval. """ from solprop.translate.coupled import metric_at @@ -598,20 +479,9 @@ def _measured( ) -> tuple[float, float]: """Return a table's largest error against a profile on a check set. - Parameters - ---------- - table : solprop.translate.chebyshev.PiecewiseChebyshev - A scalar table. - profile : solprop.profiles.ZProfile - The path it stands for. - check : numpy.ndarray - Positions, m. - - Returns - ------- - tuple of float - The largest absolute error, in the profile's units, and the largest - magnitude of the profile on the check set. + - ``check``: positions, m. + - Returns: the largest absolute error, in the profile's units, and the largest + magnitude of the profile on the check set. """ exact = np.asarray(profile(check), dtype=np.float64) # The compiled Clenshaw pass: the same operations in the same order as the plain @@ -631,19 +501,8 @@ def _check_set( A uniform grid over the run, every declared knot of the profile inside it, and every edge of the table and every interval's midpoint. - Parameters - ---------- - profile : solprop.profiles.ZProfile - The path. - start, end : float - The run's span, m. - edges : numpy.ndarray - The table's breakpoints, m. - - Returns - ------- - numpy.ndarray - The positions, increasing. + - ``start``, ``end``: the run's span, m. + - ``edges``: the table's breakpoints, m. """ knots = np.asarray(profile.breakpoints, dtype=np.float64) inside = edges[(edges >= start) & (edges <= end)] @@ -689,22 +548,9 @@ def _profile_table( ) -> tuple[PiecewiseChebyshev, dict[str, object]]: """Return a profile statistic's table and its record, lowered once per problem. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem. - name : str - The statistic. - profile : solprop.profiles.ZProfile - The path it reads. - z_end : float - The run's last plane, m (:attr:`solprop.translate.problem.StepperSettings. - z_end`). - - Returns - ------- - tuple - The table, and its record for ``statistics_tables`` (a copy). + - ``z_end``: the run's last plane, m + (:attr:`solprop.translate.problem.StepperSettings. z_end`). + - Returns: the table, and its record for ``statistics_tables`` (a copy). """ held = _PROFILE_TABLES.setdefault(problem, {}) cached = held.get((name, z_end)) @@ -731,26 +577,8 @@ def _lowered_profile_table( gradient's square root near its floor) moves by more than rounding level of its scale over that ulp. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem. - name : str - The statistic. - profile : solprop.profiles.ZProfile - The path it reads. - z_end : float - The run's last plane, m. - - Returns - ------- - tuple - The table, and its record for ``statistics_tables``. - - Raises - ------ - solprop.exceptions.BackendError - Naming the statistic when no table reaches :data:`PROFILE_TOLERANCE`. + - ``z_end``: the run's last plane, m. + - Returns: the table, and its record for ``statistics_tables``. """ from solprop.translate.linop import DEFAULT_MARGIN, profile_table from solprop.translate.problem import ( @@ -810,24 +638,7 @@ def acceptable(error: float, scale: float) -> bool: def _shape( problem: PropagationProblem, name: str, n_windows: int, plan: Mapping[str, object] ) -> tuple[int, ...]: - """Return an output's shape on this problem, without a batch axis. - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem. - name : str - The output. - n_windows : int - Its spec's energy windows. - plan : dict - The plan's extents. - - Returns - ------- - tuple of int - The shape. - """ + """Return an output's shape on this problem, without a batch axis.""" modes, pol, n_omega = problem.shape apertures = int(np.asarray(plan["apertures"]).shape[0]) regions = int(np.asarray(plan["regions"]).shape[0]) @@ -867,16 +678,6 @@ def _on_axis(modes: Sequence[Mode]) -> np.ndarray: ``HostDriver._on_axis_synthesis`` before its taper: the single node ``r = 0`` of :func:`~solprop.modes.quadrature.on_axis_rule`, rows x and y. - - Parameters - ---------- - modes : sequence of Mode - The mode set. - - Returns - ------- - numpy.ndarray - ``(2, n_modes)``. """ rule = on_axis_rule(modes[0].dimlimits(0.0)[0]) return mode_matrix(modes, rule, 0.0, components=("x", "y")).synthesis @@ -892,18 +693,6 @@ def metric_refusal(problem: PropagationProblem, names: Sequence[str]) -> str | N allocated; ``numpy_ref`` computes them (maintainer's decision, SEP 47 phase 4). No built-in route builds such a metric (:func:`solprop.translate.coupled. dense_metric` is constant on each interval). - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem. - names : sequence of str - The statistics asked for. - - Returns - ------- - str or None - The refusal, or ``None``. """ if _route(problem) != "general" or not _metric_varies(problem): return None @@ -979,24 +768,11 @@ def _zdw_tables( constant around each (:func:`_plane_tables`); otherwise it is fitted (:func:`_lowered_zdw_tables`). - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem. - modes : sequence of Mode - The propagated modes, in state order. - grid : solprop.grid.TimeGrid - The time grid, whose band the roots are searched in. - z_end : float - The run's last plane, m. - planes : sequence of float or None, optional - Every position the statistic will be taken at, or ``None`` when they are not - all known before the run (step planes, interpolated planes, a caller's own). - - Returns - ------- - tuple - The tables, and their record for ``statistics_tables`` (a copy). + - ``z_end``: the run's last plane, m. + - ``planes``: every position the statistic will be taken at, or ``None`` when they + are not all known before the run (step planes, interpolated planes, a caller's + own). + - Returns: the tables, and their record for ``statistics_tables`` (a copy). """ held = _ZDW_TABLES.setdefault(problem, {}) identity = tuple(id(mode) for mode in modes) @@ -1025,21 +801,9 @@ def _plane_tables( between neighbours, so that it holds the reference's own root at each, to the bit: the run's searches, made once before it rather than at each plane. - Parameters - ---------- - modes : sequence of Mode - The propagated modes. - grid : solprop.grid.TimeGrid - The time grid. - positions : sequence of float - The positions, increasing and distinct, m. - z_end : float - The run's last plane, m. - - Returns - ------- - tuple - The tables, one per mode, and their record (error zero). + - ``positions``: the positions, increasing and distinct, m. + - ``z_end``: the run's last plane, m. + - Returns: the tables, one per mode, and their record (error zero). """ import warnings @@ -1115,31 +879,8 @@ def _lowered_zdw_tables( statistic is refused by name; so is a mode whose band holds a root over part of the run only. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem. - modes : sequence of Mode - The propagated modes. - grid : solprop.grid.TimeGrid - The time grid. - z_end : float - The run's last plane, m. - - Returns - ------- - tuple - The tables, one per mode, and the record of the largest measured error. - - Raises - ------ - solprop.exceptions.BackendError - Naming the mode whose root no table represents within :data:`ZDW_TOLERANCE`. - - Warns - ----- - solprop.exceptions.SolpropWarning - Once, if a mode's band holds no zero at the run's start. + - ``z_end``: the run's last plane, m. + - Returns: the tables, one per mode, and the record of the largest measured error. """ import warnings @@ -1260,16 +1001,6 @@ def _width_rule(problem: PropagationProblem) -> dict[str, object]: line's x, or a plane's axes and the angles its rings sample, which its axes alone fix. The fluence's term count fixes its rounding budget (:func:`~solprop.backends.spatial.fluence_budget`). - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The spatial problem. - - Returns - ------- - dict - The plan's width entries. """ geometry = problem.transform.geometry assert geometry is not None @@ -1320,44 +1051,16 @@ def lower( ) -> StatisticsLowering: """Lower statistics for native evaluation, once. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The translated problem. - spec : solprop.stats.StatsSpec - The windows the names read. - names : sequence of str - The outputs, each one the plan evaluates on this route (:func:`uncovered`), - and :data:`NODE_POPULATIONS`. - grid : solprop.grid.TimeGrid or None - The time grid; the widths and the general spectral flux read it. - modes : sequence of Mode or None, optional - The mode set; ``peak_intensity`` and the on-axis plasma statistics on a modal - route read its axis synthesis. - profiles : mapping or None, optional - The z profiles the profile statistics read (:func:`_profile_table`). - apertures : tuple or None, optional - A spatial route's far-field tables packed for the native pass (the adapter's - ``_aperture_payload``): the apertures, the regions and the exponent. - on_axis : numpy.ndarray or None, optional - The mode set's synthesis on the axis at the reference position, when the - caller already built it (``HostDriver._on_axis``); built here otherwise. - z_end : float - The run's last plane, m, which a profile statistic's table must reach. - planes : sequence of float or None, optional - Every position the plane statistics will be taken at, when the run fixes - them all before it starts; ``zdw`` is then searched exactly there - (:func:`_zdw_tables`). - - Returns - ------- - StatisticsLowering - The plan and the shapes. - - Raises - ------ - solprop.exceptions.BackendError - Naming a statistic the plan does not evaluate here, or an input it lacks. + - ``modes``: the mode set; ``peak_intensity`` and the on-axis plasma statistics on a + modal route read its axis synthesis. + - ``apertures``: a spatial route's far-field tables packed for the native pass (the + adapter's ``_aperture_payload``): the apertures, the regions and the exponent. + - ``on_axis``: the mode set's synthesis on the axis at the reference position, when + the caller already built it (``HostDriver._on_axis``); built here otherwise. + - ``z_end``: the run's last plane, m, which a profile statistic's table must reach. + - ``planes``: every position the plane statistics will be taken at, when the run + fixes them all before it starts; ``zdw`` is then searched exactly there + (:func:`_zdw_tables`). """ names = tuple(names) left = uncovered(problem, [name for name in names if name != NODE_POPULATIONS]) diff --git a/native/src/solprop_native/_store.py b/native/src/solprop_native/_store.py index 8cb6a6f2b..d54c091d7 100644 --- a/native/src/solprop_native/_store.py +++ b/native/src/solprop_native/_store.py @@ -81,13 +81,7 @@ class StoreWarning(UserWarning): def mode(environ: Mapping[str, str] = os.environ) -> Mode: - """Return ``SOLPROP_CALIBRATION``, ``"auto"`` when unset. - - Raises - ------ - ValueError - For a value other than ``auto``, ``use`` or ``off``. - """ + """Return ``SOLPROP_CALIBRATION``, ``"auto"`` when unset.""" value = environ.get("SOLPROP_CALIBRATION", "").strip().lower() or "auto" for known in MODES: if value == known: @@ -176,13 +170,7 @@ def record(self) -> dict[str, object]: @classmethod def read(cls, data: object) -> Row: - """Read a row written by :meth:`record`. - - Raises - ------ - ValueError - When `data` is not a row. - """ + """Read a row written by :meth:`record`.""" values = _mapping(data, "row") level = values.get("level") if not isinstance(level, str) or level not in LEVELS: @@ -238,13 +226,7 @@ def record(self) -> dict[str, object]: @classmethod def read(cls, data: object, source: Path | None = None) -> Generation: - """Read a generation written by :meth:`record`. - - Raises - ------ - ValueError - When `data` is not a generation of this format. - """ + """Read a generation written by :meth:`record`.""" values = _mapping(data, "generation") if values.get("format") != FORMAT: raise ValueError( @@ -316,10 +298,7 @@ def release(self) -> None: class Store: """A calibration store rooted at `root`; read-only unless writes are asked for. - Parameters - ---------- - root : Path - The store's directory (:func:`cache_directory`, or a read-only one). + - ``root``: the store's directory (:func:`cache_directory`, or a read-only one). """ def __init__(self, root: Path): @@ -359,10 +338,7 @@ def current(self, fingerprint: str) -> Generation | None: def publish(self, generation: Generation) -> Path: """Write `generation` completely, then make it current; return its file. - Raises - ------ - OSError - When the store cannot be written; the previous generation stays current. + If the store cannot be written, the previous generation stays current. """ directory = self.directory(generation.fingerprint) directory.mkdir(parents=True, exist_ok=True) @@ -487,13 +463,7 @@ def refinement(self, fingerprint: str, descriptor: str) -> dict[str, object] | N def publish_refinement( self, fingerprint: str, descriptor: str, record: Mapping[str, object] ) -> Path: - """Write one simulation's refinement whole; return its file. - - Raises - ------ - OSError - When the store cannot be written. - """ + """Write one simulation's refinement whole; return its file.""" directory = self.root / "problems" / fingerprint directory.mkdir(parents=True, exist_ok=True) path = directory / f"{descriptor}.json" diff --git a/native/src/solprop_native/_topology.py b/native/src/solprop_native/_topology.py index 9caed64d6..955c1b862 100644 --- a/native/src/solprop_native/_topology.py +++ b/native/src/solprop_native/_topology.py @@ -98,15 +98,7 @@ def _kind_counts(topology: Topology, cpus: Sequence[int]) -> list[int]: def parse_cpu_list(text: str) -> tuple[int, ...]: - """Parse a kernel CPU list such as ``"0-3,8,10-11"``. - - Examples - -------- - >>> parse_cpu_list("0-3,8,10-11") - (0, 1, 2, 3, 8, 10, 11) - >>> parse_cpu_list("") - () - """ + """Parse a kernel CPU list such as ``"0-3,8,10-11"``.""" cpus: list[int] = [] for part in text.strip().split(","): if not part: diff --git a/native/src/solprop_native/_transverse.py b/native/src/solprop_native/_transverse.py index 7b31f7003..314d9f367 100644 --- a/native/src/solprop_native/_transverse.py +++ b/native/src/solprop_native/_transverse.py @@ -151,28 +151,14 @@ class EdgeConfig: values: Mapping[str, object] def native(self) -> dict[str, object]: - """Return the configuration as ``_core`` reads it. - - Returns - ------- - dict - Its fields. - """ + """Return the configuration as ``_core`` reads it.""" return dict(self.values) def declared(problem: PropagationProblem) -> bool: """Return whether a run declares content above ``discard_above``. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The translated problem. - - Returns - ------- - bool - True when its transverse specification sets ``discard_above``. + - Returns: True when its transverse specification sets ``discard_above``. """ design = transverse_of(problem.route) return design is not None and design.specification.discard_above is not None @@ -183,20 +169,7 @@ def edge_config( ) -> EdgeConfig: """Return the native edge's configuration for a run's fresh edge. - Parameters - ---------- - edge : solprop.transverse_monitor.TransverseEdge - The run's fresh edge; it is not changed. - problem : solprop.translate.problem.PropagationProblem - The translated problem: its declared content, as ``HostDriver.attach_monitor`` - hands it to the edge's launch. - stepper : solprop.translate.problem.StepperSettings - The run's stepper section: its fixed schedule, if any. - - Returns - ------- - EdgeConfig - The configuration. + - ``stepper``: the run's stepper section: its fixed schedule, if any. """ schedule = stepper.schedule steps = None if schedule is None else np.asarray(schedule.steps) @@ -215,22 +188,6 @@ def config_of( Every threshold is formed here as the edge forms it, so that the native edge compares the same numbers: ``budget ** 2``, ``(stop_factor * budget) ** 2``, the longest fixed step, and the input noise's share of the weights and count of modes. - - Parameters - ---------- - edge : solprop.transverse_monitor.TransverseEdge - A fresh edge; it is not changed. - steps : numpy.ndarray or None - A fixed schedule's steps, as :meth:`TransverseEdge.launch` takes them. - declared : bool - Whether content above ``discard_above`` was declared. - reads : bool, optional - Whether the backend reads the edge; every native session does. - - Returns - ------- - EdgeConfig - The configuration. """ design = edge.design faces = edge._faces # pyright: ignore[reportPrivateUsage] @@ -290,20 +247,11 @@ def noise_readings( requested collar's strip. They are formed here from the same draw, the same way, once (``transverse::Noise``), so the native launch judges the same numbers. - Parameters - ---------- - edge : solprop.transverse_monitor.TransverseEdge - The run's fresh edge; it is not changed. - noise : numpy.ndarray - The input noise's draw, shape ``(n_modes, n_pol, n_omega)``, a host array. - clean : numpy.ndarray or None - Each column's unweighted launch energy without that noise - (``NativeBackend._launch_clean_columns``), or ``None``. - - Returns - ------- - dict - The readings, as ``transverse::Noise`` takes them. + - ``noise``: the input noise's draw, shape ``(n_modes, n_pol, n_omega)``, a host + array. + - ``clean``: each column's unweighted launch energy without that noise + (``NativeBackend._launch_clean_columns``), or ``None``. + - Returns: the readings, as ``transverse::Noise`` takes them. """ reduction = edge.reduction columns = reduction.column_power(noise) @@ -338,25 +286,9 @@ def resumption(record: object, faces: int) -> dict[str, object]: reads, made whole as it makes them and refused as it refuses them. Which fields are restored, their defaults and the status a resume keeps are native. - Parameters - ---------- - record : object - The ``transverse`` entry of the earlier monitor record, or anything else when - there is none. - faces : int - The edge's faces. - - Returns - ------- - dict - The resumption, as ``_core`` reads it. - - Raises - ------ - ValueError - For reads that are not a number Python's ``int`` takes, as solprop's resume. - OverflowError - Likewise, for infinite reads. + - ``record``: the ``transverse`` entry of the earlier monitor record, or anything + else when there is none. + - Returns: the resumption, as ``_core`` reads it. """ if not isinstance(record, Mapping): return {"record": False} @@ -390,20 +322,6 @@ def _per_face( """Return a record's per-face list, or ``None`` when it has no value per face. As ``TransverseEdge.resume``'s ``numbers`` reads it. - - Parameters - ---------- - record : collections.abc.Mapping - The edge's record. - name : str - The field. - faces : int - The edge's faces. - - Returns - ------- - list of float or None - One value per face. """ value = record.get(name) if not isinstance(value, list) or len(cast("list[object]", value)) != faces: @@ -414,47 +332,16 @@ def _per_face( def record_of(edge: TransverseEdge, native: Mapping[str, object]) -> dict[str, object]: """Format the native edge's record as :meth:`TransverseEdge.summary` does. - Parameters - ---------- - edge : solprop.transverse_monitor.TransverseEdge - The run's edge, which names the faces. - native : collections.abc.Mapping - ``_core``'s record: every number, NaN where nothing was measured. - - Returns - ------- - dict - The record, with ``None`` for what was never measured. + - ``native``: ``_core``'s record: every number, NaN where nothing was measured. + - Returns: the record, with ``None`` for what was never measured. """ def number(name: str) -> float: - """Return a recorded number. - - Parameters - ---------- - name : str - The field. - - Returns - ------- - float - The value. - """ + """Return a recorded number.""" return float(cast("float", native[name])) def numbers(name: str) -> list[float]: - """Return a recorded per-face list. - - Parameters - ---------- - name : str - The field. - - Returns - ------- - list of float - One value per face. - """ + """Return a recorded per-face list.""" return [float(one) for one in cast("list[float]", native[name])] launch = number("launch_energy") @@ -523,18 +410,7 @@ class EdgeNotice: @classmethod def of(cls, native: Mapping[str, object]) -> EdgeNotice: - """Return the notice ``_core`` reports. - - Parameters - ---------- - native : collections.abc.Mapping - A notice's dictionary. - - Returns - ------- - EdgeNotice - The notice. - """ + """Return the notice ``_core`` reports.""" return cls( NoticeKind(int(cast("int", native["kind"]))), bool(native["stop"]), @@ -547,43 +423,12 @@ def of(cls, native: Mapping[str, object]) -> EdgeNotice: def _face(edge: TransverseEdge, notice: EdgeNotice) -> str: - """Return the name of the face a notice names. - - Parameters - ---------- - edge : solprop.transverse_monitor.TransverseEdge - The run's edge. - notice : EdgeNotice - The notice. - - Returns - ------- - str - The face's name. - """ + """Return the name of the face a notice names.""" return edge._faces[notice.face].name # pyright: ignore[reportPrivateUsage] def notice_warning(edge: TransverseEdge, notice: EdgeNotice) -> BoundaryWarning: - """Return a native edge's warning, as the edge issues it. - - Parameters - ---------- - edge : solprop.transverse_monitor.TransverseEdge - The run's edge. - notice : EdgeNotice - The warning's verdict. - - Returns - ------- - solprop.exceptions.BoundaryWarning - The warning. - - Raises - ------ - solprop.exceptions.BackendError - For a stop, or a kind that does not warn. - """ + """Return a native edge's warning, as the edge issues it.""" kind = notice.kind if notice.stop or kind is NoticeKind.NONE: msg = f"the native transverse edge warned with a {kind.name.lower()} notice" @@ -608,21 +453,7 @@ def notice_warning(edge: TransverseEdge, notice: EdgeNotice) -> BoundaryWarning: def notice_error(edge: TransverseEdge, notice: EdgeNotice) -> BaseException: - """Return the exception a native edge's stop reports, as the edge raises it. - - Parameters - ---------- - edge : solprop.transverse_monitor.TransverseEdge - The run's edge. - notice : EdgeNotice - The stop's verdict. - - Returns - ------- - BaseException - The :class:`~solprop.exceptions.BoundaryError`; a - :class:`~solprop.exceptions.BackendError` for a notice that does not stop. - """ + """Return the exception a native edge's stop reports, as the edge raises it.""" stop = (edge.stop_factor * edge.budget) ** 2 kind = notice.kind if not notice.stop: @@ -655,10 +486,7 @@ def notice_error(edge: TransverseEdge, notice: EdgeNotice) -> BaseException: def log_finish(native: Mapping[str, object]) -> None: """Log the native edge's verdict at the end of a run, as the edge logs it. - Parameters - ---------- - native : collections.abc.Mapping - ``_core``'s record, with its ``finish``. + - ``native``: ``_core``'s record, with its ``finish``. """ finish = cast("Mapping[str, object]", native["finish"]) _log_finish( @@ -675,15 +503,7 @@ def log_declared(native: Mapping[str, object]) -> bool: As :meth:`TransverseEdge.launch` logs it, for a launch it judged: one with energy, not resumed. - Parameters - ---------- - native : collections.abc.Mapping - ``_core``'s record. - - Returns - ------- - bool - Whether the edge has launched, so that there is nothing left to log. + - ``native``: ``_core``'s record. """ launch = float(cast("float", native["launch_energy"])) if math.isnan(launch) and not native["resumed"]: diff --git a/native/src/solprop_native/_wait.py b/native/src/solprop_native/_wait.py index 6586708d5..7b6a71cc0 100644 --- a/native/src/solprop_native/_wait.py +++ b/native/src/solprop_native/_wait.py @@ -90,13 +90,7 @@ def _ours(environ: MutableMapping[str, str]) -> bool: def requested_wait(environ: Mapping[str, str] = os.environ) -> str | None: - """Return :data:`REQUEST`'s ``spin`` or ``passive``, or ``None``. - - Raises - ------ - ValueError - For another value. - """ + """Return :data:`REQUEST`'s ``spin`` or ``passive``, or ``None``.""" value = environ.get(REQUEST, "").strip() if not value: return None diff --git a/native/src/solprop_native/_watch.py b/native/src/solprop_native/_watch.py index 25ec7078d..df08ce089 100644 --- a/native/src/solprop_native/_watch.py +++ b/native/src/solprop_native/_watch.py @@ -148,13 +148,7 @@ class MonitorConfig: can_rest: bool def native(self) -> dict[str, object]: - """Return the configuration as ``_core`` reads it. - - Returns - ------- - dict - Its fields. - """ + """Return the configuration as ``_core`` reads it.""" return { "policy": dict(self.policy), "floor_share": self.floor_share, @@ -196,10 +190,7 @@ class Resumption: def native(self) -> dict[str, object]: """Return the resumption as ``_core`` reads it. - Returns - ------- - dict - Its fields, the numbers flattened in and absent lists left out. + - Returns: its fields, the numbers flattened in and absent lists left out. """ out: dict[str, object] = { "record": self.record, @@ -246,15 +237,7 @@ class Notice: def of(cls, native: Mapping[str, object]) -> Notice: """Return the notice ``_core`` reports. - Parameters - ---------- - native : collections.abc.Mapping - ``SessionEvent.notice``. - - Returns - ------- - Notice - The notice. + - ``native``: ``SessionEvent.notice``. """ return cls( NoticeKind(int(cast("int", native["kind"]))), @@ -274,21 +257,6 @@ def monitor_config( The predictions' policy is the monitor's own (:meth:`BoundaryMonitor.watch_policy`) with the split collar's crossing limit the driver would give it (``HostDriver.attach_monitor``). - - Parameters - ---------- - monitor : solprop.monitor.BoundaryMonitor - The run's fresh monitor; it is not changed. - crossing : solprop.boundaries.SplitCrossing or None - The split collar's crossing limit (the problem's, under the split scheme). - can_rest : bool - Whether the route's stages may leave a quiet collar out - (``HostDriver._collar_can_rest``). - - Returns - ------- - MonitorConfig - The configuration. """ configured = dataclasses.replace(monitor, crossing=crossing) policy = configured.watch_policy() @@ -309,18 +277,6 @@ def _number(record: Mapping[str, object], name: str) -> float | None: As :func:`solprop.monitor._number` reads it, without its default, which the native reconstruction applies. - - Parameters - ---------- - record : collections.abc.Mapping - A monitor record. - name : str - The field. - - Returns - ------- - float or None - The value. """ value = record.get(name) if isinstance(value, bool) or not isinstance(value, int | float): @@ -337,20 +293,8 @@ def resumption(record: Mapping[str, object] | None) -> Resumption: record's launch energy makes it read the counts. Which numbers are restored, their defaults and the fallbacks are native. - Parameters - ---------- - record : collections.abc.Mapping or None - The ``boundary_monitor`` record of the latest earlier execution, or ``None``. - - Returns - ------- - Resumption - The resumption. - - Raises - ------ - solprop.exceptions.BoundaryError - For a count that is not finite or is negative. + - ``record``: the ``boundary_monitor`` record of the latest earlier execution, or + ``None``. """ if record is None: return Resumption(record=False) @@ -400,15 +344,8 @@ def resumption(record: Mapping[str, object] | None) -> Resumption: def unwatched_of(stretches: Sequence[tuple[float, float]]) -> list[list[float]]: """Return the stretches a resumed run ran without a monitor, as lists. - Parameters - ---------- - stretches : collections.abc.Sequence of tuple of float - ``(start, end)`` in m. - - Returns - ------- - list - ``[start, end]`` pairs. + - ``stretches``: ``(start, end)`` in m. + - Returns: ``[start, end]`` pairs. """ return [[float(start), float(end)] for start, end in stretches] @@ -416,32 +353,12 @@ def unwatched_of(stretches: Sequence[tuple[float, float]]) -> list[list[float]]: def record_of(check: BoundaryCheck, native: Mapping[str, object]) -> dict[str, object]: """Format the native monitor's record as :meth:`BoundaryMonitor.summary` does. - Parameters - ---------- - check : solprop.monitor.BoundaryCheck - The run's thresholds, which head the record. - native : collections.abc.Mapping - ``_core``'s record: every number, NaN where nothing was measured. - - Returns - ------- - dict - The record, with ``None`` for what was never measured. + - ``native``: ``_core``'s record: every number, NaN where nothing was measured. + - Returns: the record, with ``None`` for what was never measured. """ def finite(value: object) -> float | None: - """Return a number, or ``None`` when it was never measured (not finite). - - Parameters - ---------- - value : object - A recorded number. - - Returns - ------- - float or None - The value, or ``None``. - """ + """Return a number, or ``None`` when it was never measured (not finite).""" number = float(cast("float", value)) return number if math.isfinite(number) else None @@ -489,25 +406,7 @@ def finite(value: object) -> float | None: def notice_warning(check: BoundaryCheck, notice: Notice) -> Warning: - """Return a native warning, as solprop's monitor issues it. - - Parameters - ---------- - check : solprop.monitor.BoundaryCheck - The run's thresholds. - notice : Notice - The warning's verdict. - - Returns - ------- - Warning - The :class:`~solprop.exceptions.BoundaryWarning`, with its attributes. - - Raises - ------ - solprop.exceptions.BackendError - For a kind that does not warn. - """ + """Return a native warning, as solprop's monitor issues it.""" if notice.kind is NoticeKind.GUARD: z = None if notice.launch else notice.z return _boundary_warning(_guard_message(check, notice.value, z, stop=False)) @@ -522,11 +421,6 @@ def _zero_division() -> ZeroDivisionError: Raised as the monitor's own float division raises it, so that its message is this interpreter's. - - Returns - ------- - ZeroDivisionError - The error. """ zero = 0.0 try: @@ -540,20 +434,10 @@ def _zero_division() -> ZeroDivisionError: def notice_error(check: BoundaryCheck, notice: Notice) -> BaseException: """Return the exception a native monitor's stop reports, as solprop's raises it. - Parameters - ---------- - check : solprop.monitor.BoundaryCheck - The run's thresholds. - notice : Notice - The stop's verdict. - - Returns - ------- - BaseException - A :class:`~solprop.exceptions.BoundaryError`; a - :class:`~solprop.exceptions.BackendError` for a reading the split left empty; - the :class:`ZeroDivisionError` solprop's monitor raises on light after a launch - that held none. + - Returns: a :class:`~solprop.exceptions.BoundaryError`; a + :class:`~solprop.exceptions.BackendError` for a reading the split left empty; the + :class:`ZeroDivisionError` solprop's monitor raises on light after a launch that + held none. """ kind = notice.kind if kind is NoticeKind.GUARD: @@ -578,19 +462,9 @@ def _split_crossing_message(z: float, cap: float, step: float) -> str: The reference's text (``HostDriver._refuse_scheduled_crossing``), for the cap the native session holds (SEP 47 section 6.4). - Parameters - ---------- - z : float - Where the cap was read, m. - cap : float - The split temporal collar's crossing limit, m. - step : float - The schedule's longest step before the next check, m. - - Returns - ------- - str - The message. + - ``z``: where the cap was read, m. + - ``cap``: the split temporal collar's crossing limit, m. + - ``step``: the schedule's longest step before the next check, m. """ return ( f"at z = {z:.6e} m the split temporal collar's crossing limit " diff --git a/native/src/solprop_native/_wisdom.py b/native/src/solprop_native/_wisdom.py index 268ce7f17..8b0be1c1e 100644 --- a/native/src/solprop_native/_wisdom.py +++ b/native/src/solprop_native/_wisdom.py @@ -106,19 +106,11 @@ def machine_key() -> str: def wisdom_file(environ: Mapping[str, str] = os.environ) -> Path | None: """Return this machine and build's plan file, or ``None`` without one. - Parameters - ---------- - environ : Mapping[str, str] - The environment. - - Returns - ------- - pathlib.Path or None - ``.native-`` beside the file ``SOLPROP_FFTW_WISDOM`` names; - otherwise ``wisdom.json`` in the calibration store's directory of this machine - and build. ``None`` under ``SOLPROP_CALIBRATION=off`` without the variable, on - a build that keeps no FFTW wisdom (a device build, or a provider without it), - and, with a logged warning, when the name cannot be used. + - Returns: ``.native-`` beside the file ``SOLPROP_FFTW_WISDOM`` + names; otherwise ``wisdom.json`` in the calibration store's directory of this + machine and build. ``None`` under ``SOLPROP_CALIBRATION=off`` without the + variable, on a build that keeps no FFTW wisdom (a device build, or a provider + without it), and, with a logged warning, when the name cannot be used. """ named = environ.get(ENVIRONMENT, "").strip() try: @@ -191,18 +183,10 @@ def _load(path: Path) -> dict[str, str]: def plans(path: Path | None) -> tuple[dict[str, str], bool]: """Return the plans a workspace is offered, and whether the file supplied any. - Parameters - ---------- - path : pathlib.Path or None - This build's plan file (:func:`wisdom_file`); ``None`` offers this process's - own plans alone. - - Returns - ------- - dict - Each plan's wisdom by key: the file's, under those this process measured. - bool - Whether the file held any plan. + - ``path``: this build's plan file (:func:`wisdom_file`); ``None`` offers this + process's own plans alone. + - Returns: each plan's wisdom by key, the file's under those this process measured, + and whether the file held any plan. """ with _LOCK: found = _load(path) if path is not None else {} @@ -210,13 +194,7 @@ def plans(path: Path | None) -> tuple[dict[str, str], bool]: def remember(measured: Mapping[str, str]) -> None: - """Keep plans this process measured for its later workspaces. - - Parameters - ---------- - measured : Mapping[str, str] - Each plan's own wisdom by key. - """ + """Keep plans this process measured for its later workspaces.""" with _LOCK: _measured.update(measured) @@ -247,19 +225,8 @@ def store( ) -> bool: """Merge plans a run measured into the file. - Parameters - ---------- - path : pathlib.Path or None - This build's plan file (:func:`wisdom_file`); ``None`` does nothing. - measured : Mapping[str, str] - Each plan's own wisdom by key, as the workspace measured it. - profiled : Collection[str] - The keys a calibration profile holds, which are never stored here. - - Returns - ------- - bool - Whether the file was replaced. + - ``path``: this build's plan file (:func:`wisdom_file`); ``None`` does nothing. + - ``profiled``: the keys a calibration profile holds, which are never stored here. """ new = {key: plan for key, plan in measured.items() if key not in profiled} if path is None or not new: @@ -290,16 +257,8 @@ def store( def digest(applied: Mapping[str, str]) -> str | None: """Return a short digest of a workspace's plans, for its run's metadata. - Parameters - ---------- - applied : Mapping[str, str] - The wisdom of each plan the workspace built from wisdom or measured, by key. - - Returns - ------- - str or None - The first sixteen hex characters of a BLAKE2b over the plans; ``None`` on a - build without FFTW wisdom. + - Returns: the first sixteen hex characters of a BLAKE2b over the plans; ``None`` on + a build without FFTW wisdom. """ if not _keeps_wisdom(): return None diff --git a/native/src/solprop_native/backend.py b/native/src/solprop_native/backend.py index 365b50539..573396518 100644 --- a/native/src/solprop_native/backend.py +++ b/native/src/solprop_native/backend.py @@ -189,13 +189,7 @@ def __exit__( error: BaseException | None, traceback: TracebackType | None, ) -> None: - """Raise a refinement failure leaving the call as solprop's error. - - Raises - ------ - solprop.exceptions.ConvergenceError - If the call raised the extension's ``EnvelopeConvergenceError``. - """ + """Raise a refinement failure leaving the call as solprop's error.""" del kind, traceback if isinstance(error, _core.EnvelopeConvergenceError): raise ConvergenceError(str(error)) from error @@ -505,37 +499,22 @@ def __init__( ) @property - def request(self) -> RunRequest: - """Return the run request this backend was built for (solprop SEP 47 5.2). + def request(self) -> RunRequest: # numpydoc ignore=RT01 + """Run request this backend was built for (solprop SEP 47 5.2). - Returns - ------- - solprop.translate.run_request.RunRequest - Its method, error control, itinerary, planes and statistics; a run adapts - it with :meth:`~solprop.translate.run_request.RunRequest.for_run`. + Its method, error control, itinerary, planes and statistics; a run adapts + it with :meth:`~solprop.translate.run_request.RunRequest.for_run`. """ return self._request @property - def chunk_plan(self) -> NodeChunkPlan: - """Return the host rule's node-chunk plan, made at construction (SEP 47 6.5). - - Returns - ------- - solprop.backends.node_chunk.NodeChunkPlan - The plan, which :attr:`node_chunk` lowers for this build. - """ + def chunk_plan(self) -> NodeChunkPlan: # numpydoc ignore=RT01 + """Host rule's node-chunk plan, made at construction (SEP 47 6.5).""" return self._chunk_plan @property - def closed(self) -> bool: - """Return whether the backend has released its buffers. - - Returns - ------- - bool - ``True`` once :meth:`close` ran: it can run no other input. - """ + def closed(self) -> bool: # numpydoc ignore=RT01 + """Whether the backend has released its buffers and can run no other input.""" return self._closed @property @@ -563,28 +542,11 @@ def _load( the Adams record's history and accounting started afresh; a session or a timing probe goes on from here. - Parameters - ---------- - field : numpy.ndarray - The state, ``complex128`` of shape ``(n_modes, n_pol, n_omega)``, of any - strides. - noise : solprop.noise.NoiseRealisation or None - The realisation, or ``None`` for the problem's own. - z : float - Where the state belongs, m. - resume : bool - Whether the state came back from a saved plane, taking no ``input`` draw. - - Returns - ------- - solprop_native._load.Loaded - The load, kept as the backend's (`_loaded`), its field emptied once the - state holds it. - - Raises - ------ - solprop.exceptions.BackendError - If the backend is closed, or the start does not fit the problem. + - ``field``: the state, ``complex128`` of shape ``(n_modes, n_pol, n_omega)``, + of any strides. + - ``noise``: the realisation, or ``None`` for the problem's own. + - ``z``: where the state belongs, m. + - ``resume``: the state came back from a saved plane, taking no ``input`` draw. """ if self._closed: raise BackendError("this backend is closed; its buffers were released") @@ -611,10 +573,7 @@ def _load( def _install(self, field: ComplexArray) -> None: """Load a resumed history's own state, with the loaded noise realisation. - Parameters - ---------- - field : numpy.ndarray - The state as the interrupted run held it, contiguous. + - ``field``: the state as the interrupted run held it, contiguous. """ loaded = self._loaded self._native_state.set_initial( @@ -627,19 +586,9 @@ def _install(self, field: ComplexArray) -> None: def _restore(self, start: Start, loaded: Loaded) -> tuple[str | None, float]: """Restore a resumed start's ledgers and method history after its load. - Parameters - ---------- - start : solprop.backends.session.Start - The start, with any persisted ledgers and snapshot. - loaded : solprop_native._load.Loaded - Its load. - - Returns - ------- - tuple - Why the snapshot's history was not installed (``None`` when it was or none - was given), and the next step's proposal, m: the snapshot's when its - history continues, else ``dz_init``. + - Returns: why the snapshot's history was not installed (``None`` when it was or + none was given), and the next step's proposal, m: the snapshot's when its + history continues, else ``dz_init``. """ adams = self._adams proposal = float(self._stepper.dz_init) @@ -681,12 +630,6 @@ def _adams_engine( Either formulation keeps its history, its step arithmetic and its norms in the native state (solprop ADR-043), beside a coupled flow too (SEP 16 phase 6), whose interaction formulation the admission refuses (`_refuse_adams`). - - Raises - ------ - solprop.exceptions.BackendError - For the untransported compensation, a reference test witness that native - does not step. """ # A varying operator's exponential formulation carries the transported # compensation natively, as does a frozen-residual flow's residual, whatever @@ -1189,13 +1132,7 @@ def _bind_monitor(self, monitor: BoundaryMonitor | None) -> None: self._bind_edge_lines(edge) def _bind_edge_lines(self, edge: TransverseEdge) -> None: - """Hand native a requested collar's lines, as arrays (solprop SEP 44). - - Parameters - ---------- - edge : solprop.transverse_monitor.TransverseEdge - The run's edge, whose lines may be none. - """ + """Hand native a requested collar's lines, as arrays (solprop SEP 44).""" lines = edge.reduction.lines axes = {"r": 0, "x": 1, "y": 2} none = 2**64 - 1 @@ -1259,24 +1196,8 @@ def _edge_reading( ) -> _EdgeReading: """Read the state for the transverse edge where it lives (solprop SEP 44). - Parameters - ---------- - state : solprop_native._core.State - This backend's state. - edge : solprop.transverse_monitor.TransverseEdge - The run's edge. - faces : bool, optional - Whether the regions, the columns and any collar's strip are wanted. - current : bool, optional - Whether a collar's inward current is wanted. - full : bool, optional - Whether every node is wanted, as a launch wants. - - Returns - ------- - solprop.transverse_monitor._EdgeReading - The reading, of which only numbers crossed. A requested collar still reduces - a host copy of the state. + - Returns: the reading, of which only numbers crossed. A requested collar still + reduces a host copy of the state. """ if state is not self._native_state: raise BackendError("the read state does not belong to this backend") @@ -1321,16 +1242,8 @@ def _launch_clean_columns(self, edge: TransverseEdge) -> RealArray | None: no state on the host; only a launch that resumed without its edge's record has none, and then the loaded state crosses once. - Parameters - ---------- - edge : solprop.transverse_monitor.TransverseEdge - The run's edge. - - Returns - ------- - numpy.ndarray or None - The unweighted energies, or ``None`` with no draw, no ramp or on a resumed - launch. + - Returns: the unweighted energies, or ``None`` with no draw, no ramp or on a + resumed launch. """ loaded = self._loaded floor = None if loaded is None else loaded.floor @@ -1563,14 +1476,8 @@ def metadata(self) -> dict[str, object]: } @property - def node_chunk(self) -> int: - """Return the nodes a native response pass holds at once. - - Returns - ------- - int - The host plan lowered by :func:`native_node_chunk`. - """ + def node_chunk(self) -> int: # numpydoc ignore=RT01 + """The nodes a native response pass holds at once: the host plan, lowered.""" return self._node_chunk def available_device_memory(self) -> int | None: @@ -1674,10 +1581,7 @@ def close(self) -> None: def _adopt_nothing(z: float | None) -> None: """Adopt nothing: the backend keeps no march state beside the native state. - Parameters - ---------- - z : float or None - Where the engine's state moved, m; unused. + - ``z``: where the engine's state moved, m; unused. """ del z @@ -1698,11 +1602,6 @@ def _build_facts() -> _BuildFacts: Reading them costs about a millisecond (the package metadata lookup and the native record), which a per-plane metadata record paid at every saved plane. - - Returns - ------- - _BuildFacts - The build record as a mapping, solprop's version and the device team sizes. """ native_record = { key: value for key, value in _core.build_record().items() if value is not None @@ -1926,13 +1825,6 @@ def load_rows(self, rows: Mapping[str, np.ndarray], shape: tuple[int, ...]) -> N the history as it was. The trial in flight and the last step's interpolant are forgotten, as ``HostHistory.load_rows`` forgets them: the march's next step is a new trial. - - Raises - ------ - BackendError - Naming the native backend and the mismatch: rows of another engine (a host - engine's frozen-residual weights), a row this history lacks or does not - keep, or one of the wrong shape or type. """ names = self.native.adams_row_names() weights = sorted(name for name in rows if name.startswith("weights_")) @@ -2455,16 +2347,6 @@ class _NativeAdamsMarch: (:meth:`resume`, :meth:`restore_ledger`). A native session hands it to the native state once (:meth:`start_state`, :meth:`_configure`) and holds the march's state there from then on (solprop SEP 47 section 5.2). - - Parameters - ---------- - backend : NativeBackend - The backend, whose resident history (``_adams_engine``) the march keeps. - method : solprop.stepper.multistep.Multistep - The method. - engine : object, optional - The history to keep instead of the backend's resident one: a scripted - harness's, in the decision-trace tests. """ def __init__( @@ -2564,17 +2446,8 @@ def restore_ledger( ) -> None: """Restore a resumed run's logical call counts, not its history. - Parameters - ---------- - logical : collections.abc.Mapping - The persisted logical counts; empty installs nothing. - atomic : collections.abc.Mapping or None, optional - Persisted atomic counts, which only the TDSE reference keeps. - - Raises - ------ - solprop.exceptions.BackendError - If atomic counts are given. + - ``logical``: the persisted logical counts; empty installs nothing. + - ``atomic``: persisted atomic counts, which only the TDSE reference keeps. """ if atomic: raise BackendError("native does not restore atomic response call ledgers") @@ -2588,18 +2461,9 @@ def resume(self, snapshot: MarchSnapshot, z: float) -> str | None: to this record; the snapshot's state and proposal are the caller's to load. A snapshot that cannot continue the march changes nothing. - Parameters - ---------- - snapshot : solprop.output.schema.MarchSnapshot - The snapshot a file holds. - z : float - The loaded position, m. - - Returns - ------- - str or None - ``None`` when the history was installed, otherwise why the march starts - afresh, as a resume did before snapshots were kept. + - ``z``: the loaded position, m. + - Returns: ``None`` when the history was installed, otherwise why the march + starts afresh, as a resume did before snapshots were kept. """ reason = self._refusal(snapshot, z) if reason is not None: @@ -2662,20 +2526,11 @@ def _refusal(self, snapshot: MarchSnapshot, z: float) -> str | None: def start_state(self, z: float, proposal: float, cursor: int) -> dict[str, object]: """Return the march's state at a start, as the native call takes it. - Parameters - ---------- - z : float - The loaded position, m. - proposal : float - The next step's proposal, m: ``dz_init``, or a resumed snapshot's. - cursor : int - The replayed schedule's edge index at `z`. - - Returns - ------- - dict - The position, the proposal, the order, the rejection flags, the cursor, the - history's entries and the ledger's counts. + - ``z``: the loaded position, m. + - ``proposal``: the next step's proposal, m: ``dz_init``, or a resumed + snapshot's. + - Returns: the position, the proposal, the order, the rejection flags, the + cursor, the history's entries and the ledger's counts. """ ledger = self.ledger return { @@ -2724,11 +2579,6 @@ def _raise(self, reason: int, result: dict[str, Any]) -> None: A session forms the call budget's own error (``NativeSession._budget_error``) before it asks here. - - Raises - ------ - solprop.exceptions.ConvergenceError - At the step floor, for a diverged replay or a native refinement. """ if reason == _FLOOR: proposed = float(result["floor_proposal"]) @@ -2757,13 +2607,7 @@ def _raise(self, reason: int, result: dict[str, Any]) -> None: def _raise_native(result: Mapping[str, Any]) -> None: - """Raise a native march's error as the exception class its category names. - - Raises - ------ - solprop.exceptions.ConvergenceError, ValueError, BackendError, RuntimeError - By the result's category. - """ + """Raise a native march's error as the exception class its category names.""" message = str(result["message"]) category = int(result["category"]) if category == _CONVERGENCE: @@ -2799,13 +2643,6 @@ class _ResidentLowStorage: the Cartesian and line routes the march is for or split across the exchange's two evaluations, and two more reductions a step. - Parameters - ---------- - backend : NativeBackend - The backend whose native state this engine steps. - method : solprop.stepper.low_storage.LowStorageRungeKutta - The march. - Attributes ---------- stiffness : float @@ -2842,18 +2679,8 @@ def close(self) -> None: def step(self, z: float, dz: float) -> float: """Take one step in four native register passes, accepted in place. - Parameters - ---------- - z : float - Where the step starts, m. - dz : float - The step, m, positive. - - Returns - ------- - float - The accepted state's squared norm over the norm mask, before the boundary - treatments. + - ``z``: where the step starts, m. + - ``dz``: the step, m, positive. """ backend = self._backend if (counters := _counters.active) is not None: @@ -2939,11 +2766,6 @@ class _ZTables: own. A compensated Adams march reads ``D = dPhi/dz`` from the derivative table (``linear.rate``) into one more ``Phi`` slot, ``rate_slot``. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - A problem that took the z table for ``Phi``, the prefactor or both. - Attributes ---------- phi, prefactor : bool @@ -3035,15 +2857,8 @@ def _upload(self, kind: str, table: PiecewiseChebyshev) -> None: def load(self, positions: Sequence[float]) -> dict[float, int]: """Return each position's slot, evaluating those no slot holds, in one call. - Parameters - ---------- - positions : sequence of float - The positions one native call reads, m, at most ``slots`` distinct. - - Returns - ------- - dict - Position to slot. + - ``positions``: the positions one native call reads, m, at most ``slots`` + distinct. """ assert self._native is not None wanted = [float(z) for z in positions] @@ -3069,17 +2884,9 @@ def at_row(self, slot_of: Mapping[float, int], z: float, count: int) -> RealArra def rate(self, z: float, *, before: bool) -> RealArray: """Evaluate ``D(z)`` into the rate slot; return its weights over the terms. - Parameters - ---------- - z : float - Position, m. - before : bool - Read an interior breakpoint from the interval ending there. - - Returns - ------- - numpy.ndarray - Shape ``(n_phi,)``, one at the rate slot. + - ``z``: position, m. + - ``before``: read an interior breakpoint from the interval ending there. + - Returns: shape ``(n_phi,)``, one at the rate slot. """ assert self._native is not None assert self.rate_slot is not None @@ -3116,17 +2923,6 @@ def _refuse_reference_only(stepper: StepperSettings) -> None: The benchmark-only comparison family and a controller that decides in Python run on ``numpy_ref`` alone; native refuses them, on CPU and device builds alike, before anything is allocated. - - Parameters - ---------- - stepper : solprop.translate.problem.StepperSettings - The run's stepping policy. - - Raises - ------ - solprop.exceptions.BackendError - Naming the comparison variant, a method type native does not step, or the - controller's overridden methods. """ integrator = stepper.integrator if isinstance(integrator, ComparisonMultistep): @@ -3380,15 +3176,7 @@ def _host_flow(problem: PropagationProblem) -> _HostFlow | None: The Schur decompositions (one per active bin and section) are the expensive part of admitting a coupled flow, so the decomposition is kept with the problem. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem. - - Returns - ------- - _HostFlow or None - The decomposition, or ``None`` without a coupled flow. + - Returns: the decomposition, or ``None`` without a coupled flow. """ if problem not in _HOST_FLOWS: _HOST_FLOWS[problem] = _decomposed_flow(problem) diff --git a/native/src/solprop_native/group.py b/native/src/solprop_native/group.py index a7520f58a..2fc723188 100644 --- a/native/src/solprop_native/group.py +++ b/native/src/solprop_native/group.py @@ -190,15 +190,7 @@ def _guide(problem: PropagationProblem) -> object: responses' model and policy, and a split block's flow, as the reference group compares them; ``None`` off the general modal route. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - A member's problem. - - Returns - ------- - object - A scalar-and-string description, or ``None``. + - Returns: a scalar-and-string description, or ``None``. """ tables = problem.transform.modal_tables if tables is None: @@ -245,14 +237,8 @@ def __init__( self._requests = tuple(requests) @property - def backend(self) -> str: - """Return the registered name of the backend that prepared it. - - Returns - ------- - str - The first member's registered name. - """ + def backend(self) -> str: # numpydoc ignore=RT01 + """Registered name of the backend that prepared it (the first member's).""" return self._members[0].name def start( @@ -288,18 +274,7 @@ def start( def _member_refusal(request: RunRequest) -> str | None: - """Return why one member's request falls outside a group's windows, or ``None``. - - Parameters - ---------- - request : solprop.translate.run_request.RunRequest - The member's run request. - - Returns - ------- - str or None - The reason, or ``None``. - """ + """Return why one member's request falls outside a group's windows, or ``None``.""" # A coupled flow is admitted: the lockstep batch this replaced refused one because # its fused accepted update applied no flow action, and a native group fuses # nothing -- each member's session carries its own flow (SEP 47 section 6.7). @@ -319,21 +294,7 @@ def _same_group( ) -> bool: """Return whether a member shares member 0's schedule, planes, method and spec. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The member's problem. - request : solprop.translate.run_request.RunRequest - The member's request, which replays a schedule. - first_problem : solprop.translate.problem.PropagationProblem - Member 0's problem. - first : solprop.translate.run_request.RunRequest - Member 0's request, which replays a schedule. - - Returns - ------- - bool - True when they agree. + - Returns: True when they agree. """ assert request.replay is not None assert first.replay is not None @@ -353,23 +314,7 @@ def _same_group( def _bound(event: Event, receipt: Receipt, member: int) -> Event: - """Return a member's event with its ticket, if any, rebound to the group's batch. - - Parameters - ---------- - event : solprop.backends.events.Event - The member's event, its ticket the member session's. - receipt : solprop.backends.events.Receipt - The group batch's receipt. - member : int - The member's position, which the group's ticket names. - - Returns - ------- - solprop.backends.events.Event - The event, its ticket naming the group's batch and the member; the same event - without one. - """ + """Return a member's event with its ticket, if any, rebound to the group's batch.""" ticket = cast("ExportTicket | None", getattr(event, "ticket", None)) if ticket is None: return event @@ -400,13 +345,7 @@ def __init__(self) -> None: self.event: Event | None = None def joined(self) -> tuple[BackendHistory, StepStats]: - """Return the window's attempts and rows so far, as one record. - - Returns - ------- - tuple - The joined history and rows. - """ + """Return the window's attempts and rows so far, as one record.""" return join_attempts(self.chunks) @@ -486,36 +425,18 @@ def __init__( # -- protocol ------------------------------------------------------------------- @property - def n_members(self) -> int: - """Return the number of members. - - Returns - ------- - int - The members. - """ + def n_members(self) -> int: # numpydoc ignore=RT01 + """Number of members.""" return len(self._sessions) @property - def finished(self) -> bool: - """Return whether the group's terminal batch was issued. - - Returns - ------- - bool - True once every member has ended. - """ + def finished(self) -> bool: # numpydoc ignore=RT01 + """Whether the group's terminal batch was issued: every member has ended.""" return self._ledger.finished @property - def closed(self) -> bool: - """Return whether the group was closed. - - Returns - ------- - bool - True once closed. - """ + def closed(self) -> bool: # numpydoc ignore=RT01 + """Whether the group was closed.""" return self._ledger.state is SessionState.CLOSED def advance(self) -> GroupBatch: @@ -613,25 +534,7 @@ def noise_phase(self, member: int, ticket: ExportTicket) -> np.ndarray | None: return self._sessions[member].noise_phase(self._held(member, ticket)) def _held(self, member: int, ticket: ExportTicket) -> ExportTicket: - """Return the member's own pending ticket that a group ticket stands for. - - Parameters - ---------- - member : int - The member's position, already checked. - ticket : solprop.backends.events.ExportTicket - A ticket of the pending group batch, for this member. - - Returns - ------- - solprop.backends.events.ExportTicket - The member session's ticket. - - Raises - ------ - solprop.backends.session.SessionError - If the ticket is not one of this member's in the pending batch. - """ + """Return the member's own pending ticket that a group ticket stands for.""" held = self._tickets.get(member) if held is None or held.generation != ticket.generation: msg = ( @@ -746,23 +649,7 @@ def __exit__( # -- internals ------------------------------------------------------------------ def _member(self, member: int) -> int: - """Return a valid member position. - - Parameters - ---------- - member : int - The position asked for. - - Returns - ------- - int - The same position. - - Raises - ------ - solprop.backends.session.SessionError - If the group has no such member. - """ + """Return a valid member position.""" if not 0 <= member < len(self._sessions): msg = f"the group has {len(self._sessions)} members, not a member {member}" raise SessionError(msg) @@ -771,19 +658,10 @@ def _member(self, member: int) -> int: def _window(self, *, stepping: bool, final: bool) -> GroupBatch: """Run every running member to its window's end, in rounds; report it. - Parameters - ---------- - stepping : bool - Whether the window takes common steps (a save plane's): the stop token is - polled before its first round. The launch and the completion take none; - any later round of any window is polled too. - final : bool - Whether it is the completion, which ends the group. - - Returns - ------- - solprop.backends.events.GroupBatch - Each member's events. + - ``stepping``: the window takes common steps (a save plane's), so the stop + token is polled before its first round. The launch and the completion take + none; any later round of any window is polled too. + - ``final``: the window is the completion, which ends the group. """ windows = { index: _Window() @@ -827,18 +705,8 @@ def _window(self, *, stepping: bool, final: bool) -> GroupBatch: def _turn(self, index: int, window: _Window) -> BaseException | None: """Advance one member by a round of common steps, or to its window's end. - Parameters - ---------- - index : int - The member's position. - window : _Window - Its progress through the window, extended here. - - Returns - ------- - BaseException or None - A group-wide failure the member reported, which ends the group; ``None`` - otherwise, its window's end, if reached, in `window`. + - Returns: a group-wide failure the member reported, which ends the group; + ``None`` otherwise, its window's end, if reached, in `window`. """ session = self._sessions[index] made = 0 @@ -883,14 +751,7 @@ def _serve(self, index: int, window: _Window, event: ServiceRequired) -> None: warning's committed endpoint, with its field there eligible for a checkpoint, as a lone run of it ends (SEP 47 section 5.5). The group asks for no snapshot. - Parameters - ---------- - index : int - The member's position. - window : _Window - Its window, ended here when the warning is raised. - event : solprop.backends.events.ServiceRequired - The barrier. + - ``window``: its window, ended here when the warning is raised. """ if event.kind is not ServiceKind.WARNING: return @@ -914,21 +775,9 @@ def _ended( ) -> GroupBatch: """End every running member with a group-wide stop or failure. - Parameters - ---------- - outcome : solprop.backends.events.Outcome - Stopped or failed. - error : BaseException - The primary failure, which the failing member's own terminal carries, or - the stop request. - windows : dict - The window's progress of each member running when it began. - - Returns - ------- - solprop.backends.events.GroupBatch - Each running member's terminal event at its own committed position; a - member that ended in this window keeps its own terminal event. + - ``windows``: the window's progress of each member running when it began. + - Returns: each running member's terminal event at its own committed position; a + member that ended in this window keeps its own terminal event. """ events: list[tuple[Event, ...]] = [()] * len(self._sessions) for index, window in windows.items(): @@ -970,19 +819,7 @@ def _issue( ) -> GroupBatch: """Issue a window's events under a new receipt, its tickets the group's. - Parameters - ---------- - members : list - Each member's events, their tickets the member sessions'. - outcome : solprop.backends.events.Outcome or None, optional - The group's outcome, when the batch ends it. - error : BaseException or None, optional - A group-wide failure's primary error. - - Returns - ------- - solprop.backends.events.GroupBatch - The batch. + - ``outcome``: the group's outcome, when the batch ends it. """ receipt = self._ledger.issue(outcome=outcome) for index, events in enumerate(members): @@ -998,22 +835,7 @@ def _issue( def _with_chunk(event: Event, history: BackendHistory, rows: StepStats) -> Event: - """Return a member's window-ending event carrying the whole window's chunk. - - Parameters - ---------- - event : solprop.backends.events.Event - The observation or terminal event that ended the member's window. - history : solprop.backends.protocol.BackendHistory - The window's attempts, joined. - rows : solprop.backends.protocol.StepStats - Its periodic rows, joined. - - Returns - ------- - solprop.backends.events.Event - The event with that chunk. - """ + """Return a member's window-ending event carrying the whole window's chunk.""" return cast( "Event", dataclasses.replace(cast("Any", event), history=history, step_stats=rows), diff --git a/native/src/solprop_native/session.py b/native/src/solprop_native/session.py index d6f056f0d..0d686237d 100644 --- a/native/src/solprop_native/session.py +++ b/native/src/solprop_native/session.py @@ -272,19 +272,7 @@ def _diverged( so the state is lost with it, and the terminal event that carries this error is poisoned. - Parameters - ---------- - name : str - The backend's name. - stepper : solprop.translate.problem.StepperSettings - The run's stepper section, whose method names the march. - event : solprop_native._core.SessionEvent - The terminal record: where the step started and its length. - - Returns - ------- - solprop.exceptions.ConvergenceError - The divergence. + - ``event``: the terminal record: where the step started and its length. """ method = cast("LowStorageRungeKutta", stepper.integrator) return ConvergenceError( @@ -307,18 +295,6 @@ def _statistics_refusal(problem: PropagationProblem, spec: StatsSpec) -> str | N (:func:`solprop.backends.session.statistics_unavailable`, which every executor refuses before it starts, and :func:`solprop.backends.populations.axis_refusal`), are refused by name. - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem. - spec : solprop.stats.StatsSpec - The plane statistics and their periodic subset. - - Returns - ------- - str or None - The reason, or ``None``. """ names = tuple(spec.names) if not names: @@ -353,22 +329,9 @@ def _refuse_foreign_monitor( """Refuse a boundary monitor built for another grid or mode set, by name. Its weights would be read out of bounds by the compiled sums, so they are held to - the grid's samples and the state's modes before they are bound, as the reference - refuses them where it attaches the monitor. - - Parameters - ---------- - name : str - The backend's name, for the message. - problem : solprop.translate.problem.PropagationProblem - The run's problem. - monitor : solprop.monitor.BoundaryMonitor - The request's monitor. - - Raises - ------ - solprop.exceptions.BackendError - If the weights are not ``(4, n_t)`` or the columns not ``(n_modes,)``. + the grid's samples (weights ``(4, n_t)``) and the state's modes (columns + ``(n_modes,)``) before they are bound, as the reference refuses them where it + attaches the monitor. """ n_t = problem.grids.n_t n_modes = problem.shape[0] @@ -589,29 +552,7 @@ def _lower( modes: Sequence[Mode] | None, profiles: Mapping[str, ZProfile] | None, ) -> StatisticsLowering: - """Lower statistics for the native plan, with their far-field tables. - - Parameters - ---------- - names : tuple of str - The statistics. - grid : solprop.grid.TimeGrid or None - The time grid. - modes : sequence of Mode or None - The mode set. - profiles : mapping or None - The z profiles. - - Returns - ------- - solprop_native._statistics.StatisticsLowering - The lowering. - - Raises - ------ - solprop.exceptions.BackendError - Naming an input a statistic lacks. - """ + """Lower statistics for the native plan, with their far-field tables.""" apertures = None if self._spatial and names: tables = self._far_field.for_spec(self._spec) @@ -637,17 +578,8 @@ def _prepare( ) -> tuple[str, dict[str, object]]: """Load the start and configure the backend's native march; return its state. - Parameters - ---------- - backend : solprop_native.backend.NativeBackend - The freshly built backend. - start : solprop.backends.session.Start - Where the run starts. - - Returns - ------- - tuple - The march's kind and its state at the start, as the native call takes it. + - Returns: the march's kind and its state at the start, as the native call takes + it. """ driver = self._driver(backend) integrator = self._stepper.integrator @@ -731,18 +663,8 @@ def _noise_readings(self, driver: Any, start: Start) -> dict[str, object] | None them: the native edge resumes exactly when the record's resumption holds a launch baseline (:func:`solprop_native._transverse.resumption`). - Parameters - ---------- - driver : solprop_native.backend.NativeBackend - The loaded backend, its state not yet leased. - start : solprop.backends.session.Start - Where the run starts, with a monitored resume's continuation. - - Returns - ------- - dict or None - The readings (:func:`solprop_native._transverse.noise_readings`), or - ``None`` without an edge or input noise. + - Returns: the readings (:func:`solprop_native._transverse.noise_readings`), or + ``None`` without an edge or input noise. """ edge = None if self._monitor is None else self._monitor.transverse assert self._loaded is not None # `_prepare` loaded the start @@ -763,33 +685,12 @@ def _driver(backend: NativeBackend | None) -> Any: restore, its march records and policies, its stage tables and monitor binding); confined here and typed ``Any`` because those members are the backend's own, shared within this package alone. - - Parameters - ---------- - backend : solprop_native.backend.NativeBackend or None - The backend. - - Returns - ------- - Any - The same object. """ assert backend is not None return backend def _plan(self, start: Start) -> dict[str, object]: - """Return the observation request and itinerary for the native session. - - Parameters - ---------- - start : solprop.backends.session.Start - Where the run starts. - - Returns - ------- - dict - The plan. - """ + """Return the observation request and itinerary for the native session.""" observation = self._request.observation schedule = ( None if self._request.replay is None else self._request.replay.schedule @@ -817,12 +718,9 @@ def _plan(self, start: Start) -> dict[str, object]: def _dense_plan(self) -> dict[str, object]: """Return the dense output the native session interpolates planes with. - Returns - ------- - dict - The tableau's dense-output weights, one per stage, empty for Adams: those - of :func:`~solprop.stepper.tableau.continuous_extension`, which the - translation requires of a Runge-Kutta run with interpolated planes. + - Returns: the tableau's dense-output weights, one per stage, empty for Adams: + those of :func:`~solprop.stepper.tableau.continuous_extension`, which the + translation requires of a Runge-Kutta run with interpolated planes. """ stepper = self._stepper if isinstance(stepper.integrator, Multistep): @@ -834,15 +732,7 @@ def _dense_plan(self) -> dict[str, object]: def _monitor_plan(self, start: Start) -> dict[str, object] | None: """Return the native monitor's configuration and any record it continues. - Parameters - ---------- - start : solprop.backends.session.Start - Where the run starts, with a monitored resume's continuation. - - Returns - ------- - dict or None - The monitor's plan, or ``None`` without a monitor. + - Returns: the monitor's plan, or ``None`` without a monitor. """ monitor = self._monitor if monitor is None: @@ -875,16 +765,9 @@ def _monitor_plan(self, start: Start) -> dict[str, object] | None: def _continuation(self, continuation: Mapping[str, object]) -> dict[str, object]: """Return a monitored resume's continuation as the native session reads it. - Parameters - ---------- - continuation : collections.abc.Mapping - As :attr:`solprop.backends.session.Start.monitor`. - - Returns - ------- - dict - The earlier record's resumption and its transverse edge's, whether the run - resumes at a saved plane, and the stretches run since without a monitor. + - Returns: the earlier record's resumption and its transverse edge's, whether + the run resumes at a saved plane, and the stretches run since without a + monitor. """ record = continuation.get("record") edge = None if self._monitor is None else self._monitor.transverse @@ -910,36 +793,18 @@ def _continuation(self, continuation: Mapping[str, object]) -> dict[str, object] # -- protocol ------------------------------------------------------------------- @property - def native(self) -> _core.Session: - """Return the native session, for the structural counters of the tests. - - Returns - ------- - solprop_native._core.Session - The native session. - """ + def native(self) -> _core.Session: # numpydoc ignore=RT01 + """The native session, for the structural counters of the tests.""" return self._native @property - def finished(self) -> bool: - """Return whether the session yielded its terminal event. - - Returns - ------- - bool - True once finished. - """ + def finished(self) -> bool: # numpydoc ignore=RT01 + """Whether the session yielded its terminal event.""" return self._native.status().outcome != 0 @property - def closed(self) -> bool: - """Return whether the session was closed. - - Returns - ------- - bool - True once closed. - """ + def closed(self) -> bool: # numpydoc ignore=RT01 + """Whether the session was closed.""" return _STATES[self._native.status().state] is SessionState.CLOSED def status(self) -> SessionStatus: @@ -1059,11 +924,6 @@ def _check_quadrature(self, event: Event) -> None: (``Session::quadrature_peak``); the warning is given once between two observations, as the reference gives it once a plane. - - Parameters - ---------- - event : solprop.backends.events.Event - The advance's event; an observation starts the next plane's watch. """ if self.closed: return @@ -1090,17 +950,7 @@ def _translate( ) -> tuple[BackendHistory, Event]: """Return a native batch's chunk of attempts and its one event. - Parameters - ---------- - batch : solprop_native._core.SessionBatch - The batch, pending. - receipt : solprop.backends.events.Receipt - Its receipt. - - Returns - ------- - tuple - The attempts since the previous event, and the event carrying them. + - Returns: the attempts since the previous event, and the event carrying them. """ # Each batch is one event and the chunk since the previous one, delivered once. chunk = self._history(batch.attempts) @@ -1406,14 +1256,11 @@ def march_export_refusal(self) -> str | None: return None @property - def march_refusal(self) -> str | None: - """Return why the start's method history was not installed, or ``None``. + def march_refusal(self) -> str | None: # numpydoc ignore=RT01 + """Why the start's method history was not installed. - Returns - ------- - str or None - The march record's reason (``NativeBackend._restore``), or ``None`` when - it was installed or none was given. + The march record's reason (``NativeBackend._restore``), or ``None`` when it + was installed or none was given. """ return self._march_refusal @@ -1467,11 +1314,6 @@ def _axis_scan_count(self) -> int: A Radau rule's axis node is read from the node scan the species reduction makes anyway; any other rule scans the axis row itself, which counts here. - - Returns - ------- - int - The scans since the session started. """ backend = self._backend if backend is None or backend.closed: @@ -1490,10 +1332,7 @@ def _sync_calls(self) -> CallLedger | None: orders of the steps accepted since the last call are appended, so that the record's run-length encoding is extended rather than redone. - Returns - ------- - solprop.backends.multistep.CallLedger or None - The ledger, or ``None`` for a Runge-Kutta march. + - Returns: the ledger, or ``None`` for a Runge-Kutta march. """ calls = self._calls if calls is None or self.closed: @@ -1649,30 +1488,18 @@ def failure_ticket(self) -> ExportTicket | None: return self.pending_ticket() @property - def in_flight(self) -> tuple[float, float] | None: - """Return the step a failure left in flight, or ``None``. + def in_flight(self) -> tuple[float, float] | None: # numpydoc ignore=RT01 + """Step a failure left in flight, ``(start, length)`` in m, or ``None``. A low-storage step overwrites the state in place, so a failure partway leaves neither of its ends: the session is poisoned, and the service loop falls back to its latest running checkpoint (SEP 47 section 5.5), naming this step. - - Returns - ------- - tuple or None - Where the abandoned step started and its length, m, or ``None``. """ return self._abandoned @property - def reads_transverse_edge(self) -> bool: - """Return whether the session's backend reads a transverse edge (SEP 40). - - Returns - ------- - bool - True: the native session reads the edge from its resident state and - judges it natively. - """ + def reads_transverse_edge(self) -> bool: # numpydoc ignore=RT01 + """Whether the backend reads a transverse edge (SEP 40): always, natively.""" return NativeBackend.reads_transverse_edge def close(self) -> None: @@ -1721,17 +1548,9 @@ def __exit__( # -- translation ---------------------------------------------------------------- def _check_destination(self, out: np.ndarray) -> None: - """Refuse a destination that cannot hold an export (as ``apply_recipe`` does). - - Parameters - ---------- - out : numpy.ndarray - The proposed destination. + """Refuse a destination that cannot hold an export. - Raises - ------ - solprop.exceptions.BackendError - Naming the mismatch. + The same check ``apply_recipe`` makes. """ refusal = destination_refusal(out, self._shape) if refusal is not None: @@ -1740,33 +1559,14 @@ def _check_destination(self, out: np.ndarray) -> None: def _plane_stats(self, batch: _core.SessionBatch) -> dict[str, np.ndarray]: """Return a batch's plane statistics by name. - Parameters - ---------- - batch : solprop_native._core.SessionBatch - The batch. - - Returns - ------- - dict - The statistics, empty when the batch carries none. + - Returns: the statistics, empty when the batch carries none. """ if not batch.event.has_stats: return {} return self._plane_lowering.unpack(batch.plane_stats[0]) def _chunk_rows(self, batch: _core.SessionBatch) -> StepStats: - """Return a batch's chunk of periodic rows, its steps indexing the chunk. - - Parameters - ---------- - batch : solprop_native._core.SessionBatch - The batch. - - Returns - ------- - solprop.backends.protocol.StepStats - The rows taken since the previous event. - """ + """Return a batch's chunk of periodic rows, its steps indexing the chunk.""" # Each array is made here and held, so the record takes it without a copy. z = hold(np.array(batch.row_z.reshape(-1), dtype=np.float64)) step = hold(batch.row_step.reshape(-1).astype(np.int64)) @@ -1781,13 +1581,7 @@ def _chunk_rows(self, batch: _core.SessionBatch) -> StepStats: return StepStats(z, step, values) def _no_rows(self) -> StepStats: - """Return no rows: no names before the first row, then each row's shape. - - Returns - ------- - solprop.backends.protocol.StepStats - Empty rows, as the driver's buffer drains them. - """ + """Return no rows: no names before the first row, then each row's shape.""" return StepStats( hold(np.empty(0)), hold(np.empty(0, dtype=np.int64)), @@ -1801,15 +1595,7 @@ def _no_rows(self) -> StepStats: def _history(attempts: np.ndarray) -> BackendHistory: """Return a chunk's attempt rows as a history. - Parameters - ---------- - attempts : numpy.ndarray - The rows, ``(n, 5)``: end, step, norm, acceptance, wall time. - - Returns - ------- - solprop.backends.protocol.BackendHistory - The attempts. + - ``attempts``: ``(n, 5)`` rows: end, step, norm, acceptance, wall time. """ # Copies of the columns, held, so the record takes them without a second copy. return BackendHistory( @@ -1827,19 +1613,7 @@ def _tally( chunk: BackendHistory, limit: int, ) -> None: - """Count a delivered batch for the benchmarks: one return to Python. - - Parameters - ---------- - counters : solprop_native._counters.Counters - The active counters. - batch : solprop_native._core.SessionBatch - The batch. - chunk : solprop.backends.protocol.BackendHistory - Its attempts. - limit : int - The ``advance``'s attempt bound. - """ + """Count a delivered batch for the benchmarks: one return to Python.""" event = batch.event attempts = chunk.n_steps rows = int(batch.row_z.size) @@ -1868,13 +1642,7 @@ def _tally( ) def _count(self, chunk: BackendHistory) -> None: - """Count a delivered chunk's attempts toward the next observation's summary. - - Parameters - ---------- - chunk : solprop.backends.protocol.BackendHistory - The attempts one event delivered; their wall times start at that chunk. - """ + """Count a delivered chunk's attempts toward the next observation's summary.""" self._plane_norms.extend(chunk.error_norm[chunk.accepted].tolist()) self._plane_steps += chunk.n_steps self._plane_rejected += chunk.n_rejected @@ -1887,15 +1655,7 @@ def _summary(self, z: float) -> StepSummary: Formed from every chunk since the previous observation, as the driver forms it from the same attempts, and started afresh. - Parameters - ---------- - z : float - Its position, m. - - Returns - ------- - solprop.backends.protocol.StepSummary - The summary. + - ``z``: its position, m. """ record = ControlRecord.from_norms( self._stepper.controller, @@ -1922,24 +1682,7 @@ def _event( chunk: BackendHistory, rows: StepStats, ) -> Event: - """Translate a batch's native event record and its chunk. - - Parameters - ---------- - batch : solprop_native._core.SessionBatch - The batch. - receipt : solprop.backends.events.Receipt - Its receipt. - chunk : solprop.backends.protocol.BackendHistory - Its attempts, since the previous event. - rows : solprop.backends.protocol.StepStats - Its periodic rows, likewise. - - Returns - ------- - solprop.backends.events.Event - The event. - """ + """Translate a batch's native event record and its chunk.""" event = batch.event ticket = ( ExportTicket(receipt, event.generation, event.z) if event.ticket else None @@ -2022,16 +1765,6 @@ def _budget_error(self, event: _core.SessionEvent) -> BaseException: Formed from the session's own ledger, position and committed field, as the backend's march forms it from its own (:func:`~solprop.backends.multistep. budget_error`). - - Parameters - ---------- - event : solprop_native._core.SessionEvent - The terminal record, its batch pending. - - Returns - ------- - BaseException - The budget's error. """ calls = self._sync_calls() assert calls is not None @@ -2048,16 +1781,8 @@ def _budget_error(self, event: _core.SessionEvent) -> BaseException: def _abandoned_step(self, event: _core.SessionEvent) -> float: """Return the length of the step a poisoned terminal event left in flight. - Parameters - ---------- - event : solprop_native._core.SessionEvent - The terminal record. - - Returns - ------- - float - The diverged step's length, or the schedule's step at the failure's cursor, - m. + - Returns: the diverged step's length, or the schedule's step at the failure's + cursor, m. """ if event.reason == _DIVERGED: return float(event.replay_dz) @@ -2068,15 +1793,7 @@ def _abandoned_step(self, event: _core.SessionEvent) -> float: def _warning(self, native: Mapping[str, object]) -> Warning: """Return a warning barrier's warning, the monitor's or its edge's. - Parameters - ---------- - native : collections.abc.Mapping - ``SessionEvent.notice``. - - Returns - ------- - Warning - The warning, as solprop's monitor or edge issues it. + - ``native``: ``SessionEvent.notice``. """ monitor = self._monitor assert monitor is not None @@ -2113,18 +1830,6 @@ def _error(self, event: _core.SessionEvent, outcome: Outcome) -> BaseException: A failure is formatted by the backend's own march adapter, as its native call would have raised it. - - Parameters - ---------- - event : solprop_native._core.SessionEvent - The terminal record. - outcome : solprop.backends.events.Outcome - Stopped or failed. - - Returns - ------- - BaseException - The stop request or the primary failure. """ if outcome is Outcome.STOPPED: return StopRequested(self._stop) @@ -2192,12 +1897,6 @@ class _NativeRungeKuttaMarch: rejection's, and the boundary checks -- runs natively in the session from one policy record (the kernel contract's section 12), which this hands the native state once. - - Parameters - ---------- - backend : NativeBackend - The backend whose native state steps the march; a scripted harness stands in - for it in the decision-trace tests. """ def __init__(self, backend: Any) -> None: @@ -2207,16 +1906,8 @@ def __init__(self, backend: Any) -> None: def _policy(self, interval: float | None = None) -> dict[str, object]: """Return the run's controller and landing record (kernel contract 12). - Parameters - ---------- - interval : float or None, optional - A resting collar's quiet interval, m; ``None`` for a session, whose - native monitor holds its own. - - Returns - ------- - dict - The record the native march reads. + - ``interval``: a resting collar's quiet interval, m; ``None`` for a session, + whose native monitor holds its own. """ backend = self._driver stepper = backend._stepper @@ -2284,26 +1975,10 @@ def _raise_rk( ) -> None: """Raise the error a native Runge-Kutta march returned with, if any. - Parameters - ---------- - reason : int - The call's return reason. - result : collections.abc.Mapping - Its result: the diverged replay's step and norm, the floor's proposal, or a - native error's message and category. - name : str - The backend's name, for a diverged replay's message. - dz_min : float - The step floor, m. - at : tuple of float and int - The position, m, and the replay's cursor the diverged step started from. - n_steps : int - The replayed schedule's steps. - - Raises - ------ - solprop.exceptions.ConvergenceError - For a diverged replay, the step floor or a native convergence failure. + - ``result``: its result: the diverged replay's step and norm, the floor's proposal, + or a native error's message and category. + - ``dz_min``: the step floor, m. + - ``at``: the position, m, and the replay's cursor the diverged step started from. """ if reason == _DIVERGED: z, cursor = at @@ -2399,14 +2074,8 @@ def __init__( self._closed = False @property - def backend(self) -> str: - """Return the registered name of the backend that prepared it. - - Returns - ------- - str - The backend's registered name. - """ + def backend(self) -> str: # numpydoc ignore=RT01 + """Registered name of the backend that prepared it.""" return self._backend.name def start( diff --git a/src/solprop/_display.py b/src/solprop/_display.py index 277d29fab..c41438a13 100644 --- a/src/solprop/_display.py +++ b/src/solprop/_display.py @@ -62,42 +62,17 @@ class _Printer(Protocol): """The part of IPython's pretty printer that :class:`Displayed` uses.""" def text(self, value: str) -> None: - """Write text. - - Parameters - ---------- - value : str - The text. - """ + """Write text.""" ... def engineering(value: float | None, unit: str = "", digits: int = 4) -> str: """Return a number with an SI prefix, as a person would write it. - Parameters - ---------- - value : float or None - The number, in the SI unit named by `unit`. - unit : str, optional - The unit's symbol, without a prefix: ``"m"``, ``"s"``, ``"J"``, ``"W"``. - With an empty unit no prefix is used and the number is written plainly. - digits : int, optional - Significant figures. - - Returns - ------- - str - For example ``"800 nm"``, ``"30 fs"``, ``"1.5 m"``; ``"none"`` for ``None`` - and ``"nan"``, ``"inf"`` or ``"-inf"`` for a value that is not finite. - - Examples - -------- - >>> from solprop._display import engineering - >>> engineering(800e-9, "m"), engineering(1.5, "m"), engineering(275e-6, "J") - ('800 nm', '1.5 m', '275 uJ') - >>> engineering(float("nan"), "s"), engineering(None, "s"), engineering(0.0, "W") - ('nan', 'none', '0 W') + - ``unit``: the unit's symbol, without a prefix: ``"m"``, ``"s"``, ``"J"``, ``"W"``. + With an empty unit no prefix is used and the number is written plainly. + - Returns: for example ``"800 nm"``, ``"30 fs"``, ``"1.5 m"``; ``"none"`` for + ``None`` and ``"nan"``, ``"inf"`` or ``"-inf"`` for a value that is not finite. """ if value is None: return "none" @@ -121,26 +96,8 @@ def engineering(value: float | None, unit: str = "", digits: int = 4) -> str: def summarise(values: object, unit: str = "") -> str: """Return a one-phrase summary of an array, never its contents. - Parameters - ---------- - values : array_like - The array. - unit : str, optional - SI unit of the values, for the range; empty to give the shape alone. - - Returns - ------- - str - For example ``"<201 values, 0 m to 1.5 m>"`` or ``"<2x1x8192 complex>"``. - - Examples - -------- - >>> import numpy as np - >>> from solprop._display import summarise - >>> summarise(np.linspace(0.0, 1.5, 201), "m") - '<201 values, 0 m to 1.5 m>' - >>> summarise(np.zeros((2, 1, 8), dtype=complex)) - '<2x1x8 complex>' + - ``unit``: SI unit of the values, for the range; empty to give the shape alone. + - Returns: for example ``"<201 values, 0 m to 1.5 m>"`` or ``"<2x1x8192 complex>"``. """ import numpy as np # noqa: PLC0415 - kept out of the import of modules that only format scalars @@ -162,24 +119,8 @@ def summarise(values: object, unit: str = "") -> str: def phase_text(coefficients: Iterable[float]) -> str: """Return Taylor spectral-phase coefficients as people quote them. - Parameters - ---------- - coefficients : iterable of float - ``c_0, c_1, ...`` in ``s**n``; a tuple or an array. - - Returns - ------- - str - For example ``"CEP 0.3 rad, GDD 250 fs^2"``: zero coefficients are left out, - order ``n`` is in ``fs^n``, and ``"none"`` stands for no phase at all. - - Examples - -------- - >>> from solprop._display import phase_text - >>> phase_text((0.3, 0.0, 250e-30, -4000e-45)) - 'CEP 0.3 rad, GDD 250 fs^2, TOD -4000 fs^3' - >>> phase_text(()) - 'none' + - Returns: for example ``"CEP 0.3 rad, GDD 250 fs^2"``: zero coefficients are left + out, order ``n`` is in ``fs^n``, and ``"none"`` stands for no phase at all. """ parts = [] for order, value in enumerate(coefficients): @@ -194,23 +135,7 @@ def phase_text(coefficients: Iterable[float]) -> str: def tail(text: str, width: int = 68) -> str: """Return the end of a long text, which for a path is the part that matters. - Parameters - ---------- - text : str - The text, typically a file path. - width : int, optional - Longest result, in characters. - - Returns - ------- - str - The text itself, or an ellipsis and its last ``width - 3`` characters. - - Examples - -------- - >>> from solprop._display import tail - >>> tail("/a/very/long/path/to/run.h5", 12) - '...to/run.h5' + `width` counts the leading ellipsis that marks a cut. """ return text if len(text) <= width else "..." + text[-(width - 3) :] @@ -218,24 +143,8 @@ def tail(text: str, width: int = 68) -> str: def line(name: str, fields: Iterable[tuple[str, object]]) -> str: """Return a one-line ``Name(key=value, ...)`` description. - Parameters - ---------- - name : str - The class name. - fields : iterable of tuple - ``(key, value)`` pairs; a value is written with ``str``, and a pair whose key - is empty is written as the value alone. - - Returns - ------- - str - The line. - - Examples - -------- - >>> from solprop._display import line - >>> line("Grid", [("n_t", 8192), ("", "field")]) - 'Grid(n_t=8192, field)' + - ``fields``: ``(key, value)`` pairs; a value is written with ``str``, and a pair + whose key is empty is written as the value alone. """ parts = [f"{key}={value}" if key else str(value) for key, value in fields] return f"{name}({', '.join(parts)})" @@ -244,29 +153,11 @@ def line(name: str, fields: Iterable[tuple[str, object]]) -> str: def block(title: str, rows: Sequence[tuple[str, object]]) -> str: """Return an aligned multi-line description. - Parameters - ---------- - title : str - The first line: the class and its headline. - rows : sequence of tuple - ``(key, value)`` pairs, one line each. A value holding newlines is indented - under its key; a line longer than the package's 88 columns is wrapped at a - space, its continuation indented under its value, over at most four lines, - and what is left over, or a single word too long for a line, is cut with an - ellipsis, so that a block is always safe to paste into a page. - - Returns - ------- - str - The block, without a trailing newline. - - Examples - -------- - >>> from solprop._display import block - >>> print(block("Pulse", [("energy", "275 uJ"), ("duration", "7.5 fs")])) - Pulse - energy: 275 uJ - duration: 7.5 fs + - ``rows``: ``(key, value)`` pairs, one line each. A value holding newlines is + indented under its key; a line longer than the package's 88 columns is wrapped at + a space, its continuation indented under its value, over at most four lines, and + what is left over, or a single word too long for a line, is cut with an ellipsis, + so that a block is always safe to paste into a page. """ width = max((len(key) for key, _ in rows), default=0) + 1 under = " " * (width + 3) @@ -284,22 +175,10 @@ def block(title: str, rows: Sequence[tuple[str, object]]) -> str: def _fitted(lead: str, value: str, indent: str) -> list[str]: """Return one line of a block within the package's width. - Parameters - ---------- - lead : str - What precedes the value on its first line: the key's column, or the - indentation of a continuation. - value : str - The text, wrapped at its spaces when the line is too long. - indent : str - The indentation of each wrapped continuation, under the value. - - Returns - ------- - list of str - The line or its wrapped pieces, at most :data:`_WRAPPED` of them; the last - kept piece of a longer value, and a piece holding a single word too long for - the width, end with an ellipsis. + - ``value``: the text, wrapped at its spaces when the line is too long. + - Returns: the line or its wrapped pieces, at most :data:`_WRAPPED` of them; the + last kept piece of a longer value, and a piece holding a single word too long for + the width, end with an ellipsis. """ if len(lead) + len(value) <= _WIDTH: return [lead + value] @@ -331,31 +210,14 @@ class Displayed: def _describe(self) -> tuple[str, Sequence[tuple[str, object]]]: """Return the title and the rows of the block. - Returns - ------- - tuple - The first line, and ``(key, value)`` pairs for the rest. + - Returns: the first line, and ``(key, value)`` pairs for the rest. """ return repr(self), () def __str__(self) -> str: - """Return the aligned block. - - Returns - ------- - str - The title and one line per row. - """ + """Return the aligned block.""" return block(*self._describe()) def _repr_pretty_(self, printer: _Printer, cycle: bool) -> None: - """Write the block for IPython's pretty printer. - - Parameters - ---------- - printer : object - IPython's printer. - cycle : bool - Whether the object was met again while printing itself. - """ + """Write the block for IPython's pretty printer.""" printer.text(repr(self) if cycle else str(self)) diff --git a/src/solprop/_helpers.py b/src/solprop/_helpers.py index a68e42985..b946e6cae 100644 --- a/src/solprop/_helpers.py +++ b/src/solprop/_helpers.py @@ -112,14 +112,6 @@ class Helpers: Returns the :data:`TablesFunction`, for a record that forms an exponential Adams trial's tables in one call; ``None``, the default, composes them from the basis. - - Examples - -------- - >>> import numpy as np - >>> from solprop._helpers import PLAIN - >>> rows, weights = PLAIN.basis(np.array([1.0, 0.0]), 1.0, True) - >>> weights.tolist() - [0.5, 0.5] """ name: str @@ -129,60 +121,33 @@ class Helpers: @functools.cached_property def points(self) -> PointsFunction: - """Return the Chebyshev rows at several positions. - - Returns - ------- - collections.abc.Callable - The :data:`PointsFunction`. - """ + """Return the Chebyshev rows at several positions.""" return self.load_points() @functools.cached_property def basis(self) -> BasisFunction: - """Return the Lagrange basis on an Adams formula's nodes. - - Returns - ------- - collections.abc.Callable - The :data:`BasisFunction`. - """ + """Return the Lagrange basis on an Adams formula's nodes.""" return self.load_basis() @functools.cached_property def tables(self) -> TablesFunction | None: """Return an exponential Adams trial's tables in one call, if the record has it. - Returns - ------- - collections.abc.Callable or None - The :data:`TablesFunction`, or ``None``: the tables are then composed from - :attr:`basis`. + - Returns: the :data:`TablesFunction`, or ``None``: the tables are then composed + from :attr:`basis`. """ return None if self.load_tables is None else self.load_tables() def _plain_points() -> PointsFunction: - """Return the Chebyshev rows' Python form. - - Returns - ------- - collections.abc.Callable - :func:`solprop.translate.chebyshev._clenshaw_points`. - """ + """Return the Chebyshev rows' Python form.""" from solprop.translate import chebyshev # noqa: PLC0415 - where it lives return chebyshev._clenshaw_points def _plain_basis() -> BasisFunction: - """Return the Adams basis' Python form. - - Returns - ------- - collections.abc.Callable - :func:`solprop.stepper.multistep._basis_rows`. - """ + """Return the Adams basis' Python form.""" from solprop.stepper import multistep # noqa: PLC0415 - where it lives return multistep._basis_rows diff --git a/src/solprop/_host.py b/src/solprop/_host.py index 334b9bd55..3e325187c 100644 --- a/src/solprop/_host.py +++ b/src/solprop/_host.py @@ -40,15 +40,7 @@ def _command(arguments: list[str]) -> str | None: """Return a short query's standard output, or ``None`` if it cannot run. - Parameters - ---------- - arguments : list of str - The command and its arguments, never passed through a shell. - - Returns - ------- - str or None - The output, or ``None`` when the command is absent, fails or times out. + - Returns: the output, or ``None`` when the command is absent, fails or times out. """ try: result = subprocess.run( @@ -70,21 +62,7 @@ def lscpu_model(output: str) -> str | None: Arm Linux kernels give ``/proc/cpuinfo`` no model name; ``lscpu`` decodes the implementer and part numbers into a vendor and a core name. - Parameters - ---------- - output : str - What ``lscpu`` printed. - - Returns - ------- - str or None - The vendor and model, or ``None`` when ``lscpu`` names none. - - Examples - -------- - >>> from solprop._host import lscpu_model - >>> lscpu_model(chr(10).join(["Vendor ID: ARM", "Model name: Neoverse-V2"])) - 'ARM Neoverse-V2' + - Returns: the vendor and model, or ``None`` when ``lscpu`` names none. """ fields = { key.strip(): value.strip() @@ -104,10 +82,7 @@ def lscpu_model(output: str) -> str | None: def _windows_model() -> str | None: """Read the processor name Windows keeps in its registry. - Returns - ------- - str or None - The name, or ``None`` off Windows or when the key cannot be read. + - Returns: the name, or ``None`` off Windows or when the key cannot be read. """ try: import winreg # noqa: PLC0415 # pyright: ignore[reportMissingModuleSource] @@ -125,11 +100,8 @@ def _windows_model() -> str | None: def cpu_model() -> str | None: """Return the CPU's marketing name, as the operating system reports it. - Returns - ------- - str or None - For example ``"AMD EPYC 9R45"`` or ``"Apple M4 Max"``; ``None`` when no query - answers. + - Returns: for example ``"AMD EPYC 9R45"`` or ``"Apple M4 Max"``; ``None`` when no + query answers. """ system = platform.system() if system == "Linux": @@ -150,10 +122,7 @@ def cpu_model() -> str | None: def _memory_bytes() -> int | None: """Return the machine's physical memory, or ``None`` where it cannot be read. - Returns - ------- - int or None - Bytes. + - Returns: bytes. """ try: pages, size = os.sysconf("SC_PHYS_PAGES"), os.sysconf("SC_PAGE_SIZE") @@ -166,21 +135,11 @@ def _memory_bytes() -> int | None: def machine() -> dict[str, object]: """Describe the machine this process runs on, asked once per process. - Returns - ------- - dict - ``system``, ``release`` and ``machine`` (the architecture) from - :mod:`platform`; ``cpu_model``; ``logical_cpus`` (the machine's) and - ``usable_cpus`` (the ones the scheduler granted this process); ``memory_bytes`` - (physical); ``slurm_job_id`` under Slurm. A value that cannot be read is - ``None``. Plain JSON values; never a host name. - - Examples - -------- - >>> from solprop._host import machine - >>> record = machine() - >>> record["usable_cpus"] >= 1 and isinstance(record["machine"], str) - True + - Returns: ``system``, ``release`` and ``machine`` (the architecture) from + :mod:`platform`; ``cpu_model``; ``logical_cpus`` (the machine's) and + ``usable_cpus`` (the ones the scheduler granted this process); ``memory_bytes`` + (physical); ``slurm_job_id`` under Slurm. A value that cannot be read is ``None``. + Plain JSON values; never a host name. """ record: dict[str, object] = { "system": platform.system(), @@ -236,40 +195,19 @@ class RunClock: @classmethod def start(cls) -> "RunClock": - """Return the clock of a run starting now. - - Returns - ------- - RunClock - Both clocks, read now. - """ + """Return the clock of a run starting now.""" return cls(time.perf_counter(), time.process_time()) def usage(clock: RunClock) -> dict[str, float | int | None]: """Return what a run has spent since `clock` started: wall, CPU and peak memory. - Parameters - ---------- - clock : RunClock - The run's start. - - Returns - ------- - dict - ``wall_time_s`` and ``cpu_time_s`` (every thread's user and system time) since - the start, and ``peak_rss_bytes``, the **process's** largest resident memory so - far: in a session running several runs it includes the earlier ones, and on a - device it does not count device memory; ``None`` where :mod:`resource` is - absent (Windows). A run with a plane observer adds ``observer_time_s`` and - ``observer_transfers``, the device fetches made for it. - - Examples - -------- - >>> from solprop._host import RunClock, usage - >>> record = usage(RunClock.start()) - >>> record["wall_time_s"] >= 0.0 and record["cpu_time_s"] >= 0.0 - True + - Returns: ``wall_time_s`` and ``cpu_time_s`` (every thread's user and system time) + since the start, and ``peak_rss_bytes``, the **process's** largest resident memory + so far: in a session running several runs it includes the earlier ones, and on a + device it does not count device memory; ``None`` where :mod:`resource` is absent + (Windows). A run with a plane observer adds ``observer_time_s`` and + ``observer_transfers``, the device fetches made for it. """ spent: dict[str, float | int | None] = { "wall_time_s": time.perf_counter() - clock.wall, diff --git a/src/solprop/_interrupts.py b/src/solprop/_interrupts.py index 3d810bfd0..b35faf789 100644 --- a/src/solprop/_interrupts.py +++ b/src/solprop/_interrupts.py @@ -35,15 +35,7 @@ def poll(flag: StopFlag | None) -> None: """Raise :class:`StopRequested` when a run's stop flag is set. - Parameters - ---------- - flag : StopFlag or None - The run's flag; ``None`` never stops. - - Raises - ------ - StopRequested - If the flag is set. + - ``flag``: the run's flag; ``None`` never stops. """ if flag is not None and flag.is_set(): raise StopRequested(flag) @@ -66,24 +58,8 @@ class committing: # noqa: N801 - used as a function, ``with committing():`` A class rather than a generator: it is entered at every accepted step. - Parameters - ---------- - deliver : bool, optional - Raise a signal that arrived during the block when it ends; ``False`` drops it, - for a commit written while the run is already stopping. - - Raises - ------ - solprop.exceptions.RunTerminated - If SIGTERM arrived during the block and `deliver` holds. - KeyboardInterrupt - If Ctrl-C arrived during the block and `deliver` holds. - - Examples - -------- - >>> from solprop._interrupts import committing - >>> with committing(): - ... state, position = "new state", 1.0 + - ``deliver``: raise a signal that arrived during the block when it ends; ``False`` + drops it, for a commit written while the run is already stopping. """ __slots__ = ("_deliver",) @@ -99,18 +75,8 @@ def __enter__(self) -> None: def __exit__(self, kind: type[BaseException] | None, *_: object) -> None: """Leave the commit, raising a signal that waited for it. - Parameters - ---------- - kind : type or None - The exception leaving the block, if any; a pending signal does not - replace it. - *_ : object - The exception and its traceback, unused. - - Raises - ------ - BaseException - The pending signal's exception, when the block ended normally. + - ``kind``: the exception leaving the block, if any; a pending signal does not + replace it. """ _GUARD.depth -= 1 if _GUARD.depth or _GUARD.pending is None: @@ -121,22 +87,7 @@ def __exit__(self, kind: type[BaseException] | None, *_: object) -> None: def _handle(signal_number: int, frame: FrameType | None) -> None: - """Raise the signal's exception now, or when the current commit ends. - - Parameters - ---------- - signal_number : int - The signal received. - frame : types.FrameType or None - The interrupted frame, unused. - - Raises - ------ - solprop.exceptions.RunTerminated - For SIGTERM, outside a commit. - KeyboardInterrupt - For SIGINT, outside a commit. - """ + """Raise the signal's exception now, or when the current commit ends.""" del frame error: BaseException = ( KeyboardInterrupt() @@ -157,25 +108,12 @@ def deferred_handlers() -> Iterator[None]: application handlers too, then restore and invoke them after the transaction. Ignored signals stay ignored. A failing transaction keeps its primary error. Outside the main thread only the existing commit guard applies. - - Yields - ------ - None - With TERM and Ctrl-C deferred until all working files are open again. """ pending: tuple[int, FrameType | None] | None = None previous: dict[int, Callable[[int, FrameType | None], object]] = {} def defer(number: int, frame: FrameType | None) -> None: - """Remember the last signal without running application code. - - Parameters - ---------- - number : int - Signal number. - frame : types.FrameType or None - Interrupted Python frame to pass to the original handler. - """ + """Remember the last signal without running application code.""" nonlocal pending pending = number, frame @@ -204,16 +142,7 @@ def handled() -> Iterator[None]: over Python's own defaults: an application that set its own handler keeps it. Restored on exit. - Yields - ------ - None - With the handlers installed where they could be. - - Examples - -------- - >>> from solprop._interrupts import handled - >>> with handled(): - ... pass + - Yields: with the handlers installed where they could be. """ defaults: dict[int, Callable[..., object] | int | None] = { signal.SIGTERM: signal.SIG_DFL, diff --git a/src/solprop/_ownership.py b/src/solprop/_ownership.py index 7ef7d40f4..a4c5d48f4 100644 --- a/src/solprop/_ownership.py +++ b/src/solprop/_ownership.py @@ -40,25 +40,7 @@ def hold(values: np.ndarray) -> np.ndarray: stands. A view is frozen but not held, since the memory beneath it could still be written: hold the array that owns the memory instead. - Parameters - ---------- - values : numpy.ndarray - The array. - - Returns - ------- - numpy.ndarray - The same array, read-only. - - Examples - -------- - >>> import numpy as np - >>> from solprop._ownership import hold, is_held - >>> made = hold(np.arange(3.0)) - >>> bool(made.flags.writeable), is_held(made) - (False, True) - >>> is_held(hold(np.arange(3.0)[1:])) - False + - Returns: the same array, read-only. """ values.setflags(write=False) if values.base is None: @@ -73,22 +55,8 @@ def hold_view(values: np.ndarray) -> np.ndarray: by indexing: :func:`hold` would freeze the view alone, and it would not be held. Only for a view whose memory no one else refers to. - Parameters - ---------- - values : numpy.ndarray - The view, or an array that owns its memory. - - Returns - ------- - numpy.ndarray - The same array, read-only, and held when the memory beneath it is an array's. - - Examples - -------- - >>> import numpy as np - >>> from solprop._ownership import hold_view, is_held - >>> is_held(hold_view(np.arange(3.0)[np.newaxis])) - True + - Returns: the same array, read-only, and held when the memory beneath it is an + array's. """ owner = values while isinstance(owner.base, np.ndarray): @@ -107,24 +75,8 @@ def borrow(values: np.ndarray) -> np.ndarray: copies the view. Whoever borrows promises the memory is not written while the view lives. - Parameters - ---------- - values : numpy.ndarray - A view, created for the borrowing. - - Returns - ------- - numpy.ndarray - The same view, read-only and held. - - Examples - -------- - >>> import numpy as np - >>> from solprop._ownership import borrow, is_held - >>> mine = np.arange(3.0) - >>> view = borrow(mine.view()) - >>> bool(mine.flags.writeable), is_held(view) - (True, True) + - `values`: a view, created for the borrowing. + - Returns: the same view, read-only and held. """ values.setflags(write=False) _HELD[id(values)] = values @@ -141,24 +93,8 @@ def is_held(values: object) -> bool: read-only array that is not held, however it was frozen, since whoever owns it can make it writeable again. - Parameters - ---------- - values : object - Anything. - - Returns - ------- - bool - True for a held array, or a read-only view of one, while it is read-only. - - Examples - -------- - >>> import numpy as np - >>> from solprop._ownership import is_held - >>> mine = np.arange(3.0) - >>> mine.setflags(write=False) - >>> is_held(mine) - False + - Returns: True for a held array, or a read-only view of one, while it is + read-only. """ array = values while isinstance(array, np.ndarray) and not array.flags.writeable: @@ -181,29 +117,9 @@ def frozen_copy( (:func:`is_held`) is returned as it stands. An array just built takes :func:`hold` instead, which copies nothing. - Parameters - ---------- - values : array_like - The values to freeze, of any shape. - dtype : numpy.dtype or type or None, optional - The element type of the result; None keeps the input's. - - Returns - ------- - numpy.ndarray - The same values and shape, C-contiguous, not writeable and held: `values` - itself when it is already all of that, with `dtype`; otherwise a new copy. - - Examples - -------- - >>> import numpy as np - >>> from solprop._ownership import frozen_copy - >>> mine = np.arange(3.0) - >>> frozen = frozen_copy(mine) - >>> bool(mine.flags.writeable), bool(frozen.flags.writeable) - (True, False) - >>> frozen_copy(frozen) is frozen - True + - `dtype`: the element type of the result; None keeps the input's. + - Returns: the same values and shape, C-contiguous, not writeable and held: `values` + itself when it is already all of that, with `dtype`; otherwise a new copy. """ if ( isinstance(values, np.ndarray) diff --git a/src/solprop/_process.py b/src/solprop/_process.py index 6ebf27b4b..43398706c 100644 --- a/src/solprop/_process.py +++ b/src/solprop/_process.py @@ -64,23 +64,7 @@ def child_environment(base: Mapping[str, str]) -> dict[str, str]: Adds :data:`BLAS_IDLE_DEFAULTS` where `base` sets none of them, so a user's own value wins. - Parameters - ---------- - base : collections.abc.Mapping - The environment the process would otherwise take. - - Returns - ------- - dict - A new environment. - - Examples - -------- - >>> from solprop._process import child_environment - >>> child_environment({})["OPENBLAS_THREAD_TIMEOUT"] - '20' - >>> child_environment({"OPENBLAS_THREAD_TIMEOUT": "28"})["OPENBLAS_THREAD_TIMEOUT"] - '28' + - ``base``: the environment the process would otherwise take. """ return {**BLAS_IDLE_DEFAULTS, **base} @@ -100,17 +84,7 @@ def spawn_environment( `removed` unset, and the parent's own values come back on exit. One lock serves every caller, so two starts never see each other's variables. - Parameters - ---------- - overrides : collections.abc.Mapping or None, optional - Variables the child takes, whatever this process has. - removed : collections.abc.Sequence of str, optional - Variables the child must not have. - - Yields - ------ - None - While the environment is the child's. + - ``removed``: variables the child must not have. """ with _ENVIRONMENT_LOCK: target = child_environment({**os.environ, **(overrides or {})}) @@ -135,13 +109,7 @@ def spawn_environment( def sizes_blas_after_load() -> bool: - """Say whether BLAS must be sized after it loads, not by the environment. - - Returns - ------- - bool - True on native Windows on Arm. - """ + """Say whether BLAS must be sized after it loads, not by the environment.""" return sys.platform == "win32" and platform.machine() == "ARM64" @@ -185,13 +153,7 @@ class _BlasLease(contextlib.AbstractContextManager[None]): paths: tuple[str, ...] def __exit__(self, *_exception: object) -> None: - """Release the owner without suppressing a context's exception. - - Parameters - ---------- - *_exception : object - Exception details supplied by the context protocol. - """ + """Release the owner without suppressing a context's exception.""" self.restore_original_limits() def restore_original_limits(self) -> None: @@ -223,20 +185,6 @@ def acquire_host_blas(libraries: Sequence[Any], threads: int) -> _BlasLease: them too early; this process-owned lease saves the original width once. Native's adapter supplies this hook explicitly, keeping standalone native imports independent of solprop. - - Parameters - ---------- - libraries : sequence - Threadpoolctl library controllers, already selected by the caller. Their - untyped library API is confined here and to the private pool record. - threads : int - Positive requested width, already resolved against the caller's budget. - - Returns - ------- - _BlasLease - Eagerly applied, idempotently released by a context exit or by - ``restore_original_limits``, as the backend adapters expect. """ owner = object() paths = tuple(os.path.realpath(library.filepath) for library in libraries) @@ -269,17 +217,6 @@ def host_blas() -> Iterator[None]: scope's limit. The last scope restores it: threadpoolctl changes process-wide pools, so one Python thread must not restore them while another is using them. Importing this module starts no library walk or numerical runtime. - - Yields - ------ - None - While the controllable host BLAS pools are limited. A BLAS with no - runtime thread control, such as Accelerate, is left as it was. - - Raises - ------ - ValueError - If a thread-count environment variable is invalid. """ with _BLAS_LOCK: if not _BLAS_USERS: @@ -321,13 +258,7 @@ def host_blas() -> Iterator[None]: def _query() -> tuple[int, ...]: - """Return the calling thread's CPUs: its affinity, or the usable count's range. - - Returns - ------- - tuple of int - The CPU indices, sorted. - """ + """Return the calling thread's CPUs: its affinity, or the usable count's range.""" if hasattr(os, "sched_getaffinity"): return tuple(sorted(os.sched_getaffinity(0))) return tuple(range(os.process_cpu_count() or 1)) @@ -339,11 +270,6 @@ def _at_import() -> tuple[int, ...]: ``solprop_native``, when imported before ``solprop``, starts its OpenMP runtime first; it records the process's CPUs just before (``PROCESS_CPUS``), and that wider set is the grant when this thread's answer is one of its CPUs. - - Returns - ------- - tuple of int - Sorted CPU indices. """ own = _query() earlier = getattr(sys.modules.get("solprop_native"), "PROCESS_CPUS", ()) @@ -365,16 +291,7 @@ def allowed_cpus() -> tuple[int, ...]: grant, so the import-time set is returned. A deliberate narrowing to several cores, or any narrowing without ``OMP_PROC_BIND``, is believed. - Returns - ------- - tuple of int - Sorted CPU indices, at least one. - - Examples - -------- - >>> from solprop._process import allowed_cpus - >>> len(allowed_cpus()) >= 1 - True + - Returns: sorted CPU indices, at least one. """ current = _query() binding = os.environ.get("OMP_PROC_BIND", "").strip().lower() @@ -444,16 +361,7 @@ def explicit(self, count: int) -> int: def _quota(directory: Path) -> int | None: """Return one control group's CPU quota in whole CPUs, or ``None`` for none. - Parameters - ---------- - directory : pathlib.Path - The group's directory: ``cpu.max`` (version 2) or ``cpu.cfs_quota_us`` and - ``cpu.cfs_period_us`` (version 1). - - Returns - ------- - int or None - The quota divided by its period, rounded down and at least one. + - Returns: the quota divided by its period, rounded down and at least one. """ try: limit = directory / "cpu.max" @@ -483,51 +391,20 @@ def cgroup_cpus(root: Path | None = None) -> int | None: build and every scan admission asks, and a running process's groups do not change under it. - Parameters - ---------- - root : pathlib.Path, optional - A filesystem root to read under instead, for tests; never kept. - - Returns - ------- - int or None - Whole CPUs, at least one, or ``None`` where no quota is set or none can be - read (not Linux, or no control groups). - - Examples - -------- - >>> from solprop._process import cgroup_cpus - >>> cgroup_cpus() is None or cgroup_cpus() >= 1 - True + - Returns: whole CPUs, at least one, or ``None`` where no quota is set or none can + be read (not Linux, or no control groups). """ return _own_cgroup_cpus() if root is None else _read_cgroup_cpus(root) @functools.cache def _own_cgroup_cpus() -> int | None: - """Return this process's CPU quota, read once. - - Returns - ------- - int or None - As :func:`cgroup_cpus`. - """ + """Return this process's CPU quota, read once.""" return _read_cgroup_cpus(Path("/")) def _read_cgroup_cpus(root: Path) -> int | None: - """Read the tightest CPU quota on this process's path under a filesystem root. - - Parameters - ---------- - root : pathlib.Path - The filesystem root. - - Returns - ------- - int or None - As :func:`cgroup_cpus`. - """ + """Read the tightest CPU quota on this process's path under a filesystem root.""" try: lines = (root / "proc/self/cgroup").read_text(encoding="utf-8").splitlines() except OSError: @@ -558,18 +435,9 @@ def _read_cgroup_cpus(root: Path) -> int | None: def cores_in_masks(masks: Sequence[int], cpus: Sequence[int]) -> int: """Count the cores, given as CPU bitmasks, that hold any of ``cpus``. - Parameters - ---------- - masks : sequence of int - One bitmask per physical core: bit ``n`` set when CPU ``n`` is one of its - hardware threads. - cpus : sequence of int - The CPU indices the process may run on. - - Returns - ------- - int - The number of cores holding at least one of ``cpus``. + - ``masks``: one bitmask per physical core: bit ``n`` set when CPU ``n`` is one of + its hardware threads. + - Returns: the number of cores holding at least one of ``cpus``. """ wanted = sum(1 << cpu for cpu in cpus) return sum(1 for mask in masks if mask & wanted) @@ -578,17 +446,8 @@ def cores_in_masks(masks: Sequence[int], cpus: Sequence[int]) -> int: def _linux_cores(cpus: Sequence[int], root: Path) -> int | None: """Count the distinct (package, core) pairs of ``cpus`` in a sysfs CPU tree. - Parameters - ---------- - cpus : sequence of int - The CPU indices the process may run on. - root : Path - The sysfs CPU directory, ``/sys/devices/system/cpu`` on a running system. - - Returns - ------- - int or None - The number of physical cores, or ``None`` if any CPU's topology is unreadable. + - Returns: the number of physical cores, or ``None`` if any CPU's topology is + unreadable. """ pairs: set[tuple[str, str]] = set() for cpu in cpus: @@ -608,15 +467,7 @@ def _linux_cores(cpus: Sequence[int], root: Path) -> int | None: def _windows_cores(cpus: Sequence[int]) -> int | None: # pragma: no cover - Windows """Count the physical cores holding ``cpus`` from Windows' processor table. - Parameters - ---------- - cpus : sequence of int - The CPU indices the process may run on (processor group 0). - - Returns - ------- - int or None - The number of physical cores, or ``None`` if the table cannot be read. + - Returns: the number of physical cores, or ``None`` if the table cannot be read. """ import ctypes # noqa: PLC0415 @@ -658,18 +509,8 @@ def physical_cores( reads each CPU's package and core from ``root``; Windows asks the kernel. macOS on Apple silicon has one thread per core, so the CPUs are the cores. - Parameters - ---------- - cpus : sequence of int - The CPU indices the process may run on. - root : Path, optional - The sysfs CPU directory, read on Linux. - - Returns - ------- - int or None - The number of physical cores, or ``None`` on other platforms or when the - topology cannot be read. + - Returns: the number of physical cores, or ``None`` on other platforms or when the + topology cannot be read. """ if sys.platform.startswith("linux"): return _linux_cores(cpus, root) @@ -681,15 +522,7 @@ def physical_cores( def _sysctl_int(name: str) -> int | None: # pragma: no cover - macOS """Read an integer ``sysctl`` value by name on macOS, or ``None``. - Parameters - ---------- - name : str - The variable, for example ``"hw.nperflevels"``. - - Returns - ------- - int or None - Its value, or ``None`` if the kernel does not have it. + - Returns: its value, or ``None`` if the kernel does not have it. """ import ctypes # noqa: PLC0415 @@ -714,11 +547,8 @@ def top_tier_cores() -> int | None: long at 18 threads as at 6 (solprop issue #333). macOS reports the kinds as performance levels, level 0 the fastest. Other platforms are not read here. - Returns - ------- - int or None - The cores of performance level 0 on a macOS chip with several levels, else - ``None``. + - Returns: the cores of performance level 0 on a macOS chip with several levels, + else ``None``. """ if sys.platform != "darwin": return None @@ -732,20 +562,7 @@ def top_tier_cores() -> int | None: def thread_count(name: str) -> int | None: """Read a positive thread count from the environment. - Parameters - ---------- - name : str - The variable, for example ``"SOLPROP_THREADS"``. - - Returns - ------- - int or None - The count, or ``None`` when the variable is unset or blank. - - Raises - ------ - ValueError - If the variable is set to something that is not a positive integer. + - Returns: the count, or ``None`` when the variable is unset or blank. """ value = os.environ.get(name, "").strip() if not value: @@ -771,19 +588,10 @@ def parallel_rows[T](n_rows: int, nbytes: int, work: Callable[[slice], T]) -> li For a NumPy pass over a state too large for one thread's page faults and memory bandwidth: ufuncs release the GIL, and a state's rows are independent. - Parameters - ---------- - n_rows : int - The rows to cover; positive. - nbytes : int - The bytes the pass reads. Below :data:`PARALLEL_BYTES` one call takes every row. - work : callable - Called with each slice of rows; it must touch nothing the others do. - - Returns - ------- - list - Each call's result, in row order. + - ``n_rows``: the rows to cover; positive. + - ``nbytes``: the bytes the pass reads. Below :data:`PARALLEL_BYTES` one call takes + every row. + - ``work``: called with each slice of rows; it must touch nothing the others do. """ # A small pass never threads, so it reads no budget: the topology walk behind it # cost 60 ms of a 0.36 s window in host statistics (solprop issue #556). @@ -850,18 +658,7 @@ def thread_budget() -> ThreadBudget: def oversubscribing() -> bool: - """Return whether ``SOLPROP_OVERSUBSCRIBE`` lets explicit counts pass the CPUs. - - Returns - ------- - bool - ``True`` for ``1``, ``False`` for ``0`` or unset. - - Raises - ------ - ValueError - For any other value. - """ + """Return whether ``SOLPROP_OVERSUBSCRIBE`` lets explicit counts pass the CPUs.""" value = os.environ.get("SOLPROP_OVERSUBSCRIBE", "").strip() if value not in {"", "0", "1"}: msg = f"SOLPROP_OVERSUBSCRIBE must be 0 or 1, and is {value!r}" @@ -875,16 +672,8 @@ def process_stamp(pid: int) -> str | None: A process number is reused once its process has gone; the start time tells a run's own process from a later one with its number. - Parameters - ---------- - pid : int - The process. - - Returns - ------- - str or None - Its start, in the system's own terms; ``None`` where the system does not say - or the process does not exist. + - Returns: its start, in the system's own terms; ``None`` where the system does not + say or the process does not exist. """ if sys.platform == "linux": return _proc_stamp(pid) @@ -896,16 +685,8 @@ def process_stamp(pid: int) -> str | None: def _proc_stamp(pid: int) -> str | None: """Return a process's start as Linux's ``/proc`` gives it, in clock ticks. - Parameters - ---------- - pid : int - The process. - - Returns - ------- - str or None - Field 22 of ``/proc//stat``; ``None`` when the process does not exist, - including when it exits while the file is read. + - Returns: field 22 of ``/proc//stat``; ``None`` when the process does not + exist, including when it exits while the file is read. """ try: stat = Path(f"/proc/{pid}/stat").read_text(encoding="utf-8") @@ -919,15 +700,7 @@ def _proc_stamp(pid: int) -> str | None: def _ps_stamp(pid: int) -> str | None: # pragma: no cover - not Linux, see above """Return a process's start as ``ps`` gives it, off Linux. - Parameters - ---------- - pid : int - The process. - - Returns - ------- - str or None - Its start; ``None`` when it does not exist. + - Returns: its start; ``None`` when it does not exist. """ import subprocess # noqa: PLC0415 - only off Linux @@ -943,15 +716,7 @@ def _ps_stamp(pid: int) -> str | None: # pragma: no cover - not Linux, see abov def _windows_stamp(pid: int) -> str | None: # pragma: no cover - Windows only """Return a Windows process's creation time, or ``None`` when it has gone. - Parameters - ---------- - pid : int - The process. - - Returns - ------- - str or None - Its creation time in 100 ns units, as text. + - Returns: its creation time in 100 ns units, as text. """ import ctypes # noqa: PLC0415 - Windows only from ctypes import wintypes # noqa: PLC0415 - Windows only diff --git a/src/solprop/adiabatic.py b/src/solprop/adiabatic.py index 1651e4ff3..ecab27cd5 100644 --- a/src/solprop/adiabatic.py +++ b/src/solprop/adiabatic.py @@ -298,10 +298,7 @@ class _AffineSquare: def magnitude(self) -> Float[np.ndarray, "n_modes n_omega"]: """Return the scale S of the terms that form s, for its rounding error. - Returns - ------- - numpy.ndarray - ``|base + k_perp^2| + sum_i |b_i| max|rho_i| + k_perp^2`` in m^-2. + - Returns: ``|base + k_perp^2| + sum_i |b_i| max|rho_i| + k_perp^2`` in m^-2. """ pieces = self.pieces scale = np.max( @@ -318,15 +315,7 @@ def magnitude(self) -> Float[np.ndarray, "n_modes n_omega"]: def at(self, values: Float[np.ndarray, " n_paths"]) -> np.ndarray: """Return s for one density per path. - Parameters - ---------- - values : numpy.ndarray - One density per path. - - Returns - ------- - numpy.ndarray - The square s in m^-2, shape ``(n_modes, n_omega)``. + - Returns: the square s in m^-2, shape ``(n_modes, n_omega)``. """ return self.base + np.sum(self.slope * values[:, None], axis=0)[None, :] @@ -335,20 +324,12 @@ def _term( ) -> np.ndarray: """Return ``|Delta ln q|`` from ``Delta s / s``, enlarged by its rounding. - Parameters - ---------- - change : numpy.ndarray - The change of s, formed from the density changes, not by subtraction. - reference : numpy.ndarray - The square s at the start of the change. - magnitude : numpy.ndarray - The scale S of the terms forming s. - - Returns - ------- - numpy.ndarray - ``|log1p(change/reference)| / 2`` times one plus its relative error - bound; zero where the reference is not positive (a crossing). + - ``change``: the change of s, formed from the density changes, not by + subtraction. + - ``reference``: the square s at the start of the change. + - ``magnitude``: the scale S of the terms forming s. + - Returns: ``|log1p(change/reference)| / 2`` times one plus its relative error + bound; zero where the reference is not positive (a crossing). """ terms = self.slope.shape[0] + 3 with np.errstate(divide="ignore", invalid="ignore"): @@ -367,20 +348,9 @@ def _term( def _box(self, j: int, low: np.ndarray, magnitude: np.ndarray) -> np.ndarray: """Return the bound ``sum_i |b_i| TV(rho_i) / (2 s_min)`` on piece j. - Parameters - ---------- - j : int - The piece. - low : numpy.ndarray - ``s_min`` over the piece's box of densities. - magnitude : numpy.ndarray - The scale S of the terms forming s. - - Returns - ------- - numpy.ndarray - The bound of ``TV(ln q)`` on the piece, enlarged by its rounding, and - infinite where ``s_min`` is not positive. + - ``magnitude``: the scale S of the terms forming s. + - Returns: the bound of ``TV(ln q)`` on the piece, enlarged by its rounding, and + infinite where ``s_min`` is not positive. """ terms = self.slope.shape[0] + 4 change = np.sum( @@ -395,12 +365,9 @@ def enclose( ) -> tuple[np.ndarray, np.ndarray, np.ndarray, bool]: """Enclose ``Lambda`` and the range of s for every column. - Returns - ------- - tuple - ``Lambda`` (meaningful where s stays positive), the lowest and highest - enclosed s, each of shape ``(n_modes, n_omega)``, and whether every - varying piece was monotone, so that ``Lambda`` is attained. + - Returns: ``Lambda`` (meaningful where s stays positive), the lowest and + highest enclosed s, each of shape ``(n_modes, n_omega)``, and whether every + varying piece was monotone, so that ``Lambda`` is attained. """ pieces, slope = self.pieces, self.slope magnitude = self.magnitude @@ -440,22 +407,7 @@ def enclose( def _variation_of(path: ZProfile, start: float, end: float) -> PathVariation: """Return a path's enclosure, refusing one that cannot give it. - Parameters - ---------- - path : ZProfile - A density path. - start, end : float - The interval in m. - - Returns - ------- - PathVariation - The path's record. - - Raises - ------ - MaterialError - If the path has no enclosure, such as a callable without declared variation. + - ``start``, ``end``: the interval in m. """ try: return path.variation(start, end) @@ -468,17 +420,8 @@ def _variation_of(path: ZProfile, start: float, end: float) -> PathVariation: def _pieces(paths: Sequence[ZProfile], start: float, end: float) -> _Pieces: """Collect every path's enclosure on the union of their pieces. - Parameters - ---------- - paths : sequence of ZProfile - The density paths, at least one. - start, end : float - The run path in m. - - Returns - ------- - _Pieces - The aligned records. + - ``paths``: the density paths, at least one. + - ``start``, ``end``: the run path in m. """ knots = np.unique( np.concatenate([_variation_of(p, start, end).knots for p in paths]) @@ -489,18 +432,7 @@ def _pieces(paths: Sequence[ZProfile], start: float, end: float) -> _Pieces: ] def table(name: str) -> np.ndarray: - """Return one field of every collapsed piece, paths by pieces. - - Parameters - ---------- - name : str - A :class:`~solprop.profiles.PathVariation` field. - - Returns - ------- - numpy.ndarray - Its first value per path and piece. - """ + """Return one field of every collapsed piece, paths by pieces.""" return np.array([[float(getattr(c, name)[0]) for c in row] for row in rows]) return _Pieces( @@ -515,18 +447,7 @@ def table(name: str) -> np.ndarray: def _log_condition(ratio: np.ndarray) -> np.ndarray: - """Return the relative condition number of ``log1p`` at each argument. - - Parameters - ---------- - ratio : numpy.ndarray - Arguments greater than -1. - - Returns - ------- - numpy.ndarray - ``|x / ((1 + x) log1p(x))|``, which tends to one at zero. - """ + """Return the relative condition number of ``log1p`` at each argument.""" with np.errstate(divide="ignore", invalid="ignore"): value = np.abs(ratio / ((1.0 + ratio) * np.log1p(ratio))) return np.where(np.abs(ratio) < 1e-8, 1.0, value) @@ -535,15 +456,7 @@ def _log_condition(ratio: np.ndarray) -> np.ndarray: def _wavelength_text(omega: float) -> str: """Return a vacuum wavelength for a message. - Parameters - ---------- - omega : float - Angular frequency in rad/s. - - Returns - ------- - str - For example ``'800 nm'``. + - ``omega``: angular frequency in rad/s. """ return f"{2 * math.pi * C / omega * 1e9:.6g} nm" @@ -731,18 +644,9 @@ def _material_terms( ) -> tuple[np.ndarray, np.ndarray, list[ZProfile], dict[str, object]]: """Split a spatial medium into its affine permittivity and density paths. - Parameters - ---------- - dispersion : solprop.spatial.SpatialDispersion - The medium and grid. - omega : numpy.ndarray - Active angular frequencies in rad/s. - - Returns - ------- - tuple - Constant permittivity, per-path susceptibilities, the density paths and - the material identity. + - ``omega``: active angular frequencies in rad/s. + - Returns: constant permittivity, per-path susceptibilities, the density paths and + the material identity. """ medium = dispersion.medium material = medium.material diff --git a/src/solprop/backends/events.py b/src/solprop/backends/events.py index 09f08b59c..b5111614d 100644 --- a/src/solprop/backends/events.py +++ b/src/solprop/backends/events.py @@ -386,14 +386,8 @@ class EventBatch: events: tuple[Event, ...] = field(default_factory=tuple) @property - def terminal(self) -> Terminal | None: - """Return the batch's terminal event, if it ended the session. - - Returns - ------- - Terminal or None - The last event when it is terminal; ``None`` otherwise. - """ + def terminal(self) -> Terminal | None: # numpydoc ignore=RT01 + """The batch's terminal event, or ``None`` if it did not end the session.""" if self.events and isinstance(self.events[-1], Terminal): return self.events[-1] return None @@ -439,19 +433,7 @@ def _column( At module level rather than nested in :func:`join_attempts`, so that no function object is made per call (each one costs the coverage lane's import hook a compile). - Parameters - ---------- - histories : list of BackendHistory - The histories, in delivery order. - name : str - The column's attribute on a history. - dtype : type, optional - The column's dtype, which an empty join keeps. - - Returns - ------- - numpy.ndarray - The column over every history. + - ``dtype``: the column's dtype, which an empty join keeps. """ # A new array, held, so the joined record takes it without a copy. return hold( diff --git a/src/solprop/backends/exponential.py b/src/solprop/backends/exponential.py index 006a0b20b..d219652b7 100644 --- a/src/solprop/backends/exponential.py +++ b/src/solprop/backends/exponential.py @@ -165,20 +165,7 @@ def phi_functions( def _phi_far( x: Complex[np.ndarray, " n_elements"], order: int ) -> Complex[np.ndarray, "n_phi n_elements"]: - """Recur up from ``exp(x)``: stable where every ``|x|`` exceeds the join. - - Parameters - ---------- - x : numpy.ndarray - The arguments, each beyond :data:`PHI_JOIN` in magnitude. - order : int - The highest function. - - Returns - ------- - numpy.ndarray - ``phi_0`` to ``phi_order``, shape ``(order + 1, n_elements)``. - """ + """Recur up from ``exp(x)``: stable where every ``|x|`` exceeds the join.""" out = np.empty((order + 1, x.size), dtype=np.complex128) out[0] = np.exp(x) for k in range(1, order + 1): @@ -189,20 +176,7 @@ def _phi_far( def _phi_near( x: Complex[np.ndarray, " n_elements"], order: int ) -> Complex[np.ndarray, "n_phi n_elements"]: - """Scale, sum the highest series, recur down and double back, near zero. - - Parameters - ---------- - x : numpy.ndarray - The arguments, each within :data:`PHI_JOIN` in magnitude. - order : int - The highest function. - - Returns - ------- - numpy.ndarray - ``phi_0`` to ``phi_order``, shape ``(order + 1, n_elements)``. - """ + """Scale, sum the highest series, recur down and double back, near zero.""" magnitude = np.abs(x) # The number of halvings bringing |x| to at most 1/2; zero at and below it. squarings = np.where( @@ -410,33 +384,27 @@ def carrier_chord(linear: LinearOperator, index: int) -> CarrierChord | None: refuses. """ - def element(values: np.ndarray) -> np.ndarray: + def _element(values: np.ndarray) -> np.ndarray: """Return ``values[..., b, 0, 0, index]`` over its leading axes and batch. - Parameters - ---------- - values : numpy.ndarray - A representation's array, its last four axes the state's layout. - - Returns - ------- - numpy.ndarray - Column 0, polarisation 0 and the reference bin, every leading axis kept. + - ``values``: a representation's array, its last four axes the state's layout. + - Returns: column 0, polarisation 0 and the reference bin, every leading axis + kept. """ column = min(index, values.shape[-1] - 1) return np.ascontiguousarray(values[..., 0, 0, column]) if linear.phi_constant is not None: - return CarrierChord(constant=element(linear.phi_constant)) + return CarrierChord(constant=_element(linear.phi_constant)) if linear.phi_expansion is not None: expansion = linear.phi_expansion return CarrierChord( - expansion=dataclasses.replace(expansion, terms=element(expansion.terms)) + expansion=dataclasses.replace(expansion, terms=_element(expansion.terms)) ) if linear.phi_coefficients is not None: table = linear.polynomial return CarrierChord( - table=dataclasses.replace(table, coefficients=element(table.coefficients)) + table=dataclasses.replace(table, coefficients=_element(table.coefficients)) ) return None @@ -474,13 +442,10 @@ class CarrierReference: chord: CarrierChord | None = None @property - def active(self) -> bool: - """Return whether the shift can be nonzero. + def active(self) -> bool: # numpydoc ignore=RT01 + """Whether the shift can be nonzero. - Returns - ------- - bool - ``False`` when the run takes ``sigma = 0``, the present arithmetic exactly. + ``False`` when the run takes ``sigma = 0``, the present arithmetic exactly. """ return self.bin is not None @@ -616,17 +581,8 @@ def carrier_reference( def _nearest_bin(omega: Float[np.ndarray, " n_omega"], omega0: float) -> int: """Return the bin nearest `omega0`, ties going to the lower index. - Parameters - ---------- - omega : numpy.ndarray - The frequency axis, rad/s, in the grid's own order. - omega0 : float - The reference frequency, rad/s. - - Returns - ------- - int - The bin. + - ``omega``: the frequency axis, rad/s, in the grid's own order. + - ``omega0``: the reference frequency, rad/s. """ # argmin returns the first of equal distances: the lower index of the axis's order. return int(np.argmin(np.abs(omega - omega0))) @@ -642,24 +598,9 @@ def _carrier_refusal( ) -> str | None: """Return why `backend` cannot step a carrier frame, or ``None`` when it can. - Parameters - ---------- - policy : {'off', 'scalar'} - The policy asked for. - backend : str - The backend's name, for the message. - delivered : frozenset of str - The policies the backend steps. - geometry : str or None - The spatial geometry's kind; ``None`` on a guided route. - single : bool, optional - Whether the run is held in single precision (SEP 33), where the shift was not - qualified. - - Returns - ------- - str or None - The refusal. + - ``geometry``: the spatial geometry's kind; ``None`` on a guided route. + - ``single``: whether the run is held in single precision (SEP 33), where the shift + was not qualified. """ if policy == "off": return None diff --git a/src/solprop/backends/export.py b/src/solprop/backends/export.py index f1a76c361..edc4c0f54 100644 --- a/src/solprop/backends/export.py +++ b/src/solprop/backends/export.py @@ -173,18 +173,8 @@ def _convert_rows( conjugate and the product with ``(-1)**k`` (``fourier.spectrum_to_optics``). Rows are independent, so slices may run in threads. - Parameters - ---------- - state : numpy.ndarray - The field's rows. - out : numpy.ndarray - The destination's rows; may be `state` itself. - weight : numpy.ndarray or None - The weight's rows, or ``None`` for no amplitude conversion. - selected : slice - The rows to convert. - optics : bool - Whether to convert to the optics convention. + - ``weight``: the weight's rows, or ``None`` for no amplitude conversion. + - ``optics``: whether to convert to the optics convention. """ source = state[selected] target = out[selected] @@ -205,20 +195,7 @@ def _check_destination( state: Complex[np.ndarray, "n_modes n_pol n_omega"], out: Complex[np.ndarray, "n_modes n_pol n_omega"], ) -> None: - """Refuse a destination that cannot hold an export of `state`. - - Parameters - ---------- - state : numpy.ndarray - The field being exported. - out : numpy.ndarray - The proposed destination; `state` itself for a conversion in place. - - Raises - ------ - BackendError - Naming the mismatch. - """ + """Refuse a destination that cannot hold an export of `state`.""" refusal = destination_refusal(out, state.shape) if refusal is not None: raise BackendError(refusal) diff --git a/src/solprop/backends/host/adams.py b/src/solprop/backends/host/adams.py index f123f11a4..45232555a 100644 --- a/src/solprop/backends/host/adams.py +++ b/src/solprop/backends/host/adams.py @@ -317,15 +317,7 @@ def load(self, snapshot: MarchSnapshot) -> str | None: def _refusal(self, snapshot: MarchSnapshot) -> str | None: """Return why a snapshot cannot continue this march, or ``None``. - Parameters - ---------- - snapshot : solprop.output.schema.MarchSnapshot - The candidate. - - Returns - ------- - str or None - The reason, or ``None`` when the snapshot fits. + - Returns: the reason, or ``None`` when the snapshot fits. """ backend = self._backend refusal = self.export_refusal() @@ -381,19 +373,8 @@ def _source( ) -> tuple[_State, _State]: """Return the source at a lab-frame field, in the anchor's frame, and U. - Parameters - ---------- - lab : numpy.ndarray - The lab-frame field at `position`. - anchor, position : float - The frame's anchor and the evaluation position, m. - purpose : str, optional - Atomic evaluation label, distinguishing startup and predictor work. - - Returns - ------- - tuple - ``U(anchor, position) N(lab, position)`` and ``U(position, anchor)``. + - ``anchor``, ``position``: the frame's anchor and the evaluation position, m. + - Returns: ``U(anchor, position) N(lab, position)`` and ``U(position, anchor)``. """ return self._backend._adams_source(lab, anchor, position, purpose=purpose) @@ -472,17 +453,7 @@ def _replay_to(self, schedule: FixedSchedule, target: float) -> None: while a replay of a replay is identical. A hard kink, an edge of the schedule, restarts the history as it does in an adaptive run. - Parameters - ---------- - schedule : solprop.stepper.schedule.FixedSchedule - The schedule to replay. - target : float - Position to reach, m. - - Raises - ------ - BackendError - If the schedule runs out before the target is reached. + - ``target``: position to reach, m. """ backend = self._backend edges = schedule.edges @@ -564,27 +535,12 @@ def _observe(self, position: float) -> object: Interpolated and carried to `position` by the exact linear flow (SEP 13 section 6.4, SEP 16 section 4.3); never a history point, and no source call. - Parameters - ---------- - position : float - The observation, m, inside the last accepted step. - - Returns - ------- - object - The masked lab-frame field at `position`, as the backend holds a state: - a host array. + - ``position``: the observation, m, inside the last accepted step. """ return self._engine.observe(position) def _afford(self) -> None: - """Refuse a trial the call budget cannot pay for, before any call is made. - - Raises - ------ - ResponseBudgetError - Carrying the last accepted position and state and the calls spent. - """ + """Refuse a trial the call budget cannot pay for, before any call is made.""" self._raise_floor() budget = self._policy.max_calls cost = (0 if self._history else 1) + ( @@ -597,18 +553,7 @@ def _afford(self) -> None: backend._reserve_source_calls(cost) def _budget_error(self, cost: int) -> ResponseBudgetError: - """Return the error of a trial the call budget cannot pay for. - - Parameters - ---------- - cost : int - The calls the refused trial needs. - - Returns - ------- - solprop.exceptions.ResponseBudgetError - Carrying the last accepted position and state and the calls spent. - """ + """Return the error of a trial the call budget cannot pay for.""" backend = self._backend spent = self.ledger.total msg = ( @@ -641,15 +586,8 @@ def _capped(self, proposal: float) -> float: landing rule, so a shortened step is not a landing; a fixed schedule's replay is not capped. - Parameters - ---------- - proposal : float - The controller's proposal, m. - - Returns - ------- - float - The proposal, or the shorter step the cap allows, m. + - ``proposal``: the controller's proposal, m. + - Returns: the proposal, or the shorter step the cap allows, m. """ if not self._policy.carrier: return proposal @@ -664,15 +602,9 @@ def _quiet_capped(self, proposal: float) -> float: :func:`~solprop.backends.adams_core.quiet_capped` of the backend's collar and monitor, the rule of the driver's own ``_quiet_capped``. - Parameters - ---------- - proposal : float - The controller's proposal, m. - - Returns - ------- - float - The proposal, or the monitor's interval while the collar is left out, m. + - ``proposal``: the controller's proposal, m. + - Returns: the proposal, or the monitor's interval while the collar is left out, + m. """ backend = self._backend monitor = backend._monitor @@ -691,20 +623,13 @@ def _attempt( ) -> tuple[bool, float]: """Try one step of length `dz` and commit it if accepted. - Parameters - ---------- - dz : float - The step, m. - fixed : bool, optional - Whether the step is a prescribed schedule's, accepted whatever its norm. - landing : tuple or None, optional - Where an accepted step ends, m, and whether that is a hard kink, which - restarts the history; ``None`` ends it at ``z + dz`` and restarts nothing. - - Returns - ------- - tuple - Whether the step was accepted, and its scaled error norm. + - ``dz``: the step, m. + - ``fixed``: whether the step is a prescribed schedule's, accepted whatever its + norm. + - ``landing``: where an accepted step ends, m, and whether that is a hard kink, + which restarts the history; ``None`` ends it at ``z + dz`` and restarts + nothing. + - Returns: whether the step was accepted, and its scaled error norm. """ if self._exponential is not None: return self._attempt_exponential(dz, fixed=fixed, landing=landing) @@ -727,20 +652,10 @@ def _attempt_fused( (:meth:`HostHistory.fused_trial`): the defects of the neighbouring orders are scored in the same pass as the step's own, before the decision. - Parameters - ---------- - dz : float - The step, m. - fixed : bool, optional - Whether the step is a prescribed schedule's, accepted whatever its norm. - landing : tuple or None, optional - Where an accepted step ends and whether it is a hard kink - (:meth:`_attempt`). - - Returns - ------- - tuple - Whether the step was accepted, and its scaled error norm. + - ``dz``: the step, m. + - ``landing``: where an accepted step ends and whether it is a hard kink + (:meth:`_attempt`). + - Returns: whether the step was accepted, and its scaled error norm. """ z, nodes, slots, order, orders = self._stencil(dz) self.ledger.predictor += 1 @@ -754,16 +669,9 @@ def _stencil( :func:`~solprop.backends.adams_core.stencil` of the history. - Parameters - ---------- - dz : float - The step, m. - - Returns - ------- - tuple - The anchor, m; the history's normalised positions and store rows, newest - first; the step's order; and the orders scored, its own first. + - ``dz``: the step, m. + - Returns: the anchor, m; the history's normalised positions and store rows, + newest first; the step's order; and the orders scored, its own first. """ z = self._backend._z history = self._history @@ -792,26 +700,10 @@ def _decide( The new sample takes the oldest row past the method's need. - Parameters - ---------- - dz : float - The step, m. - order : int - Its order. - orders : list of int - The orders scored, its own first. - norms : numpy.ndarray - Their scaled norms. - fixed : bool - Whether the step is a prescribed schedule's, accepted whatever its norm. - landing : tuple or None, optional - Where an accepted step ends and whether it is a hard kink - (:meth:`_attempt`). - - Returns - ------- - tuple - Whether the step was accepted, and its scaled error norm. + - ``dz``: the step, m. + - ``landing``: where an accepted step ends and whether it is a hard kink + (:meth:`_attempt`). + - Returns: whether the step was accepted, and its scaled error norm. """ backend, policy, engine = self._backend, self._policy, self._engine norm = float(norms[0]) @@ -857,12 +749,7 @@ def _propose(self, dz: float, factor: float) -> None: step it accepted at that step's end (SEP 39 section 4.4). The floor is raised once the march has recorded the step (:meth:`_raise_floor`). - Parameters - ---------- - dz : float - The accepted step, m. - factor : float - The factor its defect allows. + - ``dz``: the accepted step, m. """ policy = self._policy try: @@ -878,11 +765,6 @@ def _raise_floor(self) -> None: Every caller that records an accepted step calls this straight after, and every attempt calls it before it starts (:meth:`_afford`), so a floor is never passed over. - - Raises - ------ - ConvergenceError - The floor's error, if one is pending. """ error, self._floor = self._floor, None if error is not None: @@ -894,14 +776,9 @@ def _land(self, z: float, dz: float, landing: tuple[float, bool] | None) -> None Called inside the commit that installed the step's state, so that the state and its position change together whatever interrupts the march afterwards. - Parameters - ---------- - z : float - The step's start, m. - dz : float - The step, m. - landing : tuple or None - Where it ends and whether that is a hard kink (:meth:`_attempt`). + - ``z``: the step's start, m. + - ``dz``: the step, m. + - ``landing``: where it ends and whether that is a hard kink (:meth:`_attempt`). """ end, kink = (z + dz, False) if landing is None else landing self._backend._z = end @@ -922,20 +799,10 @@ def _attempt_exponential( (:meth:`HostHistory.exponential_trial`): untransported samples, integrated exactly against the step's flow; an accepted step re-anchors nothing. - Parameters - ---------- - dz : float - The step, m. - fixed : bool, optional - Whether the step is a prescribed schedule's, accepted whatever its norm. - landing : tuple or None, optional - Where an accepted step ends and whether it is a hard kink - (:meth:`_attempt`). - - Returns - ------- - tuple - Whether the step was accepted, and its scaled error norm. + - ``dz``: the step, m. + - ``landing``: where an accepted step ends and whether it is a hard kink + (:meth:`_attempt`). + - Returns: whether the step was accepted, and its scaled error norm. """ z, nodes, slots, order, orders = self._stencil(dz) self.ledger.predictor += 1 @@ -948,16 +815,9 @@ def _collar_reentry(self, proposal: float) -> float: :func:`~solprop.backends.adams_core.collar_reentry`, recording the collar's flag at this attempt. - Parameters - ---------- - proposal : float - The controller's proposal, m. - - Returns - ------- - float - The proposal, or the collar's stable step when the term has just - returned, m. + - ``proposal``: the controller's proposal, m. + - Returns: the proposal, or the collar's stable step when the term has just + returned, m. """ quiet = self._backend._collar_quiet capped = adams_core.collar_reentry( @@ -983,19 +843,8 @@ def _choose_order( 40 % apart. The cap weighs the choice only; the step is the chosen order's defect's, as before. - Parameters - ---------- - current : int - The step's own order, which `scores` contains. - scores : dict - The scaled defect of every scored order. - dz : float - The accepted step, m. - - Returns - ------- - tuple - The order and the norm the next step is controlled with. + - ``dz``: the accepted step, m. + - Returns: the order and the norm the next step is controlled with. """ stored, control, norm = adams_core.choose_order( self._policy, current, scores, dz, quiet=self._backend._collar_quiet @@ -1011,14 +860,8 @@ def _reject(self, dz: float, norm: float, order: int) -> None: :data:`~solprop.stepper.multistep.RESTART_AFTER` in a row the method restarts at order one from the accepted state, paying one startup call. - Parameters - ---------- - dz : float - The rejected step, m. - norm : float - Its scaled defect, possibly not finite. - order : int - Its order. + - ``dz``: the rejected step, m. + - ``norm``: its scaled defect, possibly not finite. """ policy = self._policy self.ledger.rejected_steps += 1 @@ -1050,20 +893,7 @@ def _reject(self, dz: float, norm: float, order: int) -> None: def _check_engine(engine: object, *, exponential: bool) -> None: - """Refuse an injected engine that lacks a member its path calls. - - Parameters - ---------- - engine : object - The engine, an :class:`~solprop.backends.multistep.AdamsEngine`. - exponential : bool - Whether the path is the exponential formulation's, or the fused one's. - - Raises - ------ - solprop.exceptions.BackendError - Naming the missing members. - """ + """Refuse an injected engine that lacks a member its path calls.""" family = "exponential" if exponential else "fused" wanted = (*_ENGINE_MEMBERS, f"{family}_trial", f"{family}_accept") missing = [name for name in wanted if not hasattr(engine, name)] diff --git a/src/solprop/backends/host/driver.py b/src/solprop/backends/host/driver.py index 6c3650837..de368aa83 100644 --- a/src/solprop/backends/host/driver.py +++ b/src/solprop/backends/host/driver.py @@ -141,20 +141,9 @@ def reset(self) -> None: def append(self, z: float, step: int, values: Mapping[str, np.ndarray]) -> None: """Add one row. - Parameters - ---------- - z : float - Position the row was taken at, m. - step : int - Index of the accepted step it was taken after, into the plane's history. - values : collections.abc.Mapping - What the backend's ``stats`` returned, keyed by name, without a batch - axis. - - Raises - ------ - BackendError - If a statistic's shape is not the one the first row of this plane set. + - ``z``: position the row was taken at, m. + - ``values``: what the backend's ``stats`` returned, keyed by name, without a + batch axis. """ if self._capacity == 0: self._allocate(values) @@ -178,11 +167,8 @@ def append(self, z: float, step: int, values: Mapping[str, np.ndarray]) -> None: def drain(self) -> StepStats: """Return the rows as a frozen record and forget them. - Returns - ------- - solprop.backends.protocol.StepStats - Copies, because the buffer is about to be written over, held so that the - record takes them without another. + - Returns: copies, because the buffer is about to be written over, held so that + the record takes them without another. """ rows = self._n_rows drained = StepStats( @@ -198,10 +184,8 @@ def drain(self) -> StepStats: def _allocate(self, values: Mapping[str, np.ndarray]) -> None: """Lay the block out from the first row's shapes. - Parameters - ---------- - values : collections.abc.Mapping - The first row, whose shapes every later row of this plane must match. + - ``values``: the first row, whose shapes every later row of this plane must + match. """ self._capacity = INITIAL_STEP_STATS_CAPACITY self._z = np.zeros(self._capacity) @@ -230,12 +214,7 @@ class _TrialProbe: position and the history alone. The low-storage march has no separate trial: its step advances the discarded registers in place and leaves no trial field. - Parameters - ---------- - driver : HostDriver - The driver, loaded from the probe's start. - start : tuple - The loaded position, m, and the replayed itinerary's index there. + - ``start``: the loaded position, m, and the replayed itinerary's index there. """ def __init__( @@ -251,13 +230,9 @@ def __init__( def trial(self, z: float | None = None, dz: float | None = None) -> None: """Take one trial step and keep its end state for :meth:`field`. - Parameters - ---------- - z : float or None, optional - Where the trial starts, m; the loaded position by default. - dz : float or None, optional - Its length, m, positive; the problem's ``dz_init`` by default, or the - scheduled step at the loaded position for the low-storage march. + - ``z``: where the trial starts, m; the loaded position by default. + - ``dz``: its length, m, positive; the problem's ``dz_init`` by default, or the + scheduled step at the loaded position for the low-storage march. """ driver = self._driver start = self._z if z is None else float(z) @@ -280,17 +255,8 @@ def field(self) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: and the split-step absorber where it runs). A host trial is a new array beside the loaded state, so finishing it leaves the loaded state alone. - Returns - ------- - numpy.ndarray - An owning copy, internal units and the FFT convention, shape - ``(n_modes, n_pol, n_omega)``. - - Raises - ------ - solprop.exceptions.BackendError - With no trial since the last readback, or for the low-storage march, which - has no trial state. + - Returns: an owning copy, internal units and the FFT convention, shape + ``(n_modes, n_pol, n_omega)``. """ driver = self._driver if driver._low_storage is not None: @@ -473,19 +439,11 @@ def __init__( def _trial_step(self, z: float, dz: float) -> tuple[float, _StateT]: """Take one interaction-picture Runge-Kutta step and score it. - Parameters - ---------- - z : float - Position at the start of the step, m. - dz : float - Length of the trial step, m, positive. - - Returns - ------- - tuple - The scaled error norm and the state at the end of the step. The state is - *raw*: the boundary treatments of :meth:`_finish` are applied by the march, - and only to a step it keeps. + - ``z``: position at the start of the step, m. + - ``dz``: length of the trial step, m, positive. + - Returns: the scaled error norm and the state at the end of the step. The state + is *raw*: the boundary treatments of :meth:`_finish` are applied by the march, + and only to a step it keeps. """ @abstractmethod @@ -497,15 +455,7 @@ def _apply_mask( :meth:`HostDriver.set_initial` masks the state it is given, as the scaffold does, and that is the only place the driver needs the mask on its own. - Parameters - ---------- - state : numpy.ndarray - The state in the canonical layout. - - Returns - ------- - numpy.ndarray - The state, with everything outside the active band exactly zero. + - ``state``: the state in the canonical layout. """ def _finish( @@ -526,17 +476,8 @@ def _finish( accepted step, and keeps the energies of the power it found (:attr:`_split_found`), which the occupancies are judged on. - Parameters - ---------- - state : numpy.ndarray - The state at the end of an accepted step, in the canonical layout. - dz : float - The length of that step, m, which the absorber's rate needs. - - Returns - ------- - numpy.ndarray - The state as the run carries it on. + - ``state``: the state at the end of an accepted step, in the canonical layout. + - ``dz``: the length of that step, m, which the absorber's rate needs. """ del dz return self._apply_mask( @@ -546,27 +487,13 @@ def _finish( def _reserve_source_calls(self, count: int) -> None: """Reserve a complete trial's external source calls when a service is bound. - Parameters - ---------- - count : int - Number of logical source evaluations required by the trial. - - Raises - ------ - NotImplementedError - Always: the driver that runs an Adams march (the reference backend) keeps - its own reservations; a group's members reserve theirs. + The base always raises: the driver that runs an Adams march (the reference + backend) keeps its own reservations, and a group's members reserve theirs. """ raise NotImplementedError def _release_source_calls(self) -> None: - """Release the unused part of an external source-call reservation. - - Raises - ------ - NotImplementedError - Always, as :meth:`_reserve_source_calls`. - """ + """Release the unused part of an external source-call reservation.""" raise NotImplementedError def _adams_source( @@ -588,25 +515,12 @@ def _adams_source( :class:`~solprop.stepper.multistep.Multistep` overrides this and :meth:`_adams_propagator`; the default refuses, naming the backend. - Parameters - ---------- - lab : numpy.ndarray - The lab-frame field at `position`, in the canonical layout. - anchor, position : float - The step's anchor and the evaluation position, m. - purpose : str, optional - Atomic-call accounting label; ignored when no atomic service is bound. - - Returns - ------- - tuple - ``U(anchor, position) N(lab, position)`` and ``U(position, anchor)``, with - ``U(z_2, z_1) = exp(Phi(z_2) - Phi(z_1))``. - - Raises - ------ - BackendError - Always, on a backend that does not execute the Adams march. + - ``lab``: the lab-frame field at `position`, in the canonical layout. + - ``anchor``, ``position``: the step's anchor and the evaluation position, m. + - ``purpose``: Atomic-call accounting label; ignored when no atomic service is + bound. + - Returns: ``U(anchor, position) N(lab, position)`` and ``U(position, anchor)``, + with ``U(z_2, z_1) = exp(Phi(z_2) - Phi(z_1))``. """ del lab, anchor, position, purpose msg = f"{self.name} does not execute the low-call Adams integrator" @@ -628,24 +542,10 @@ def _adams_lab_source( backend that executes that formulation overrides this; the default refuses, naming the backend. - Parameters - ---------- - lab : numpy.ndarray - The lab-frame field at `position`, in the canonical layout. - position : float - The evaluation position, m. - purpose : str, optional - Atomic-call accounting label; ignored when no atomic service is bound. - - Returns - ------- - numpy.ndarray - ``F(lab, position)`` in the lab frame. - - Raises - ------ - BackendError - Always, on a backend that does not execute the exponential formulation. + - ``lab``: the lab-frame field at `position`, in the canonical layout. + - ``position``: the evaluation position, m. + - ``purpose``: Atomic-call accounting label; ignored when no atomic service is + bound. """ del lab, position, purpose msg = ( @@ -664,17 +564,9 @@ def _adams_block(self, z: float, dz: float) -> object: backend that holds one returns its :class:`~solprop.backends.multistep.CoupledBlock`. - Parameters - ---------- - z : float - Where the step starts, m. - dz : float - The step, m. - - Returns - ------- - object - The step's ``CoupledBlock``, or ``None``. + - ``z``: where the step starts, m. + - ``dz``: the step, m. + - Returns: the step's ``CoupledBlock``, or ``None``. """ del z, dz return None @@ -684,16 +576,9 @@ def _adams_propagator( ) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"]: """Return the linear propagator of an Adams step, ``U(position, anchor)``. - Parameters - ---------- - anchor, position : float - The step's anchor and its end, m. - - Returns - ------- - numpy.ndarray - ``exp(Phi(position) - Phi(anchor))`` in the canonical layout, an array the - caller owns. + - ``anchor``, ``position``: the step's anchor and its end, m. + - Returns: ``exp(Phi(position) - Phi(anchor))`` in the canonical layout, an + array the caller owns. """ bundle = self._stage_scalars(anchor, position) return np.exp(self._delta_phi(bundle)) @@ -704,10 +589,7 @@ def _adams_state(self) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"]: The default is the protected state itself; a backend whose state lives elsewhere returns a host copy it keeps current through :meth:`_adams_adopt`. - Returns - ------- - numpy.ndarray - The accepted state in the canonical layout. + - Returns: the accepted state in the canonical layout. """ return cast( Complex[np.ndarray, "n_batch n_modes n_pol n_omega"], self._require_state() @@ -718,17 +600,7 @@ def _adams_adopt( ) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"]: """Adopt an accepted Adams step: its boundary treatments, then the state. - Parameters - ---------- - advanced : numpy.ndarray - The step's end state before the boundary treatments, a host array. - dz : float - The step, m, which the split-step absorber needs. - - Returns - ------- - numpy.ndarray - The state the run carries on, as :meth:`_adams_state` will return it. + - ``dz``: the step, m, which the split-step absorber needs. """ finished = self._finish(cast(_StateT, advanced), dz) self._state = finished @@ -739,12 +611,7 @@ def _adams_restore( ) -> None: """Restore an accepted Adams state after interpolation or a failed correction. - Parameters - ---------- - z : float - The position of the accepted state, m. - state : numpy.ndarray - Its accepted state there, as :meth:`_adams_state` returned it. + - ``z``: the position of the accepted state, m. """ self._z = z self._state = cast(_StateT, state) @@ -755,11 +622,6 @@ def _norm_controller(self) -> PIController: The problem's own; a backend whose state is held at a scale ``s`` divides its absolute tolerance by ``s`` (SEP 33 section 4.3). - - Returns - ------- - solprop.stepper.controller.PIController - The controller. """ return self._stepper.controller @@ -773,15 +635,7 @@ def _observed_field( representation, and sets :attr:`observes_fresh_field` when every call makes a new array. - Parameters - ---------- - state : object - The backend's protected state representation. - - Returns - ------- - numpy.ndarray - An owning host ``complex128`` field in canonical layout. + - Returns: an owning host ``complex128`` field in canonical layout. """ return cast(Complex[np.ndarray, "n_batch n_modes n_pol n_omega"], state) @@ -815,12 +669,9 @@ def _on_accepted_step(self) -> None: # noqa: B027 - optional by design def _reset_step_stats(self, *, release: bool) -> None: """Discard periodic rows at a load or at the start of a plane. - Parameters - ---------- - release : bool - True for a new load, which releases the old storage; false for a new - plane, which retains capacity. The accepted-step period is owned by the - march and is unaffected by this hook. + - ``release``: true for a new load, which releases the old storage; false for + a new plane, which retains capacity. The accepted-step period is the + march's and is unaffected. """ if release: self._rows.clear() @@ -834,26 +685,17 @@ def _record_step_stats(self, spec: StatsSpec, z: float, step: int) -> None: committed. A device override must retain the values without copying them to the host, using the same evaluator as :meth:`stats`. - Parameters - ---------- - spec : solprop.stats.StatsSpec - Periodic subset promoted to ``names``, with no periodic subset itself. - z : float - Position of the committed state, m. - step : int - Attempt index in the history since the previous observation, including - rejected attempts. It is supplied by the march, not inferred from rows. + - ``z``: position of the committed state, m. + - ``step``: the attempt's index in the history since the previous observation, + rejected attempts included; supplied by the march, not inferred from rows. """ self._rows.append(z, step, self.stats(spec)) def _drain_step_stats(self) -> StepStats: """Return owning host rows at observation and retain reusable capacity. - Returns - ------- - solprop.backends.protocol.StepStats - Rows since the start of this plane. A device override copies the packed - values once here. Repeated drains return no rows until more are recorded. + A device override copies the packed values once here. Repeated drains return + no rows until more are recorded. """ return self._rows.drain() @@ -861,11 +703,8 @@ def _drain_step_stats(self) -> StepStats: def _propagator_mode(self) -> PropagatorMode: """Return the propagator mode this run actually uses. - Returns - ------- - str - ``"tabulate"`` or ``"recompute"``, with the contract's ``"auto"`` resolved - to :attr:`propagator_default`. + - Returns: ``"tabulate"`` or ``"recompute"``, with the contract's ``"auto"`` + resolved to :attr:`propagator_default`. """ asked = self._stepper.propagator_mode if asked == "auto" and self._low_storage is not None: @@ -947,37 +786,25 @@ def prepare_group( return HostPreparedGroup(members, problems, requests) @property - def request(self) -> RunRequest: - """Return the request this backend was built for. + def request(self) -> RunRequest: # numpydoc ignore=RT01 + """Request this backend was built for. - Returns - ------- - solprop.translate.run_request.RunRequest - Its method, error control, itinerary, planes and statistics; a run adapts - it with :meth:`~solprop.translate.run_request.RunRequest.for_run`. + Its method, error control, itinerary, planes and statistics; a run adapts + it with :meth:`~solprop.translate.run_request.RunRequest.for_run`. """ return self._request @property - def node_chunk(self) -> int: - """Return the nodes a response pass of this backend holds at once. + def node_chunk(self) -> int: # numpydoc ignore=RT01 + """The nodes a response pass of this backend holds at once. - Returns - ------- - int - The planned chunk (:attr:`chunk_plan`). + The planned chunk (:attr:`chunk_plan`). """ return self._chunk_plan.chunk @property - def chunk_plan(self) -> NodeChunkPlan: - """Return the node-chunk plan and what chose it. - - Returns - ------- - solprop.backends.node_chunk.NodeChunkPlan - The plan made at construction. - """ + def chunk_plan(self) -> NodeChunkPlan: # numpydoc ignore=RT01 + """Node-chunk plan and what chose it.""" return self._chunk_plan def available_device_memory(self) -> int | None: @@ -1147,10 +974,7 @@ def _hoist(self, z0: float) -> None: On the RDW gradient the Clenshaw pass over the scalar tables is the largest part of what the host does per step, and this is a sixth of it. - Parameters - ---------- - z0 : float - The step's anchor, m. + - ``z0``: the step's anchor, m. """ self._anchor = anchor(self._problem, z0, helpers=self.helpers) self._prepared.clear() @@ -1171,17 +995,8 @@ def _stage_bundles( positions as its own anchor. A caller passes only positions the trial will evaluate. - Parameters - ---------- - z0 : float - The step's anchor, m. - positions : sequence of float - The stages' positions, m. - - Returns - ------- - tuple of solprop.translate.stage.StageScalars - One bundle per position, in order. + - ``z0``: the step's anchor, m. + - ``positions``: the stages' positions, m. """ prepared = self._prepared missing = [z1 for z1 in dict.fromkeys(positions) if (z0, z1) not in prepared] @@ -1200,17 +1015,8 @@ def _stage_scalars(self, z0: float, z1: float) -> StageScalars: taken them for this step, and are evaluated here otherwise. A bundle :meth:`_stage_bundles` took for this anchor and position is returned as kept. - Parameters - ---------- - z0 : float - The step's anchor, m. - z1 : float - The stage's position, m. - - Returns - ------- - solprop.translate.stage.StageScalars - The weights and the scalar path values. + - ``z0``: the step's anchor, m. + - ``z1``: the stage's position, m. """ kept = self._prepared.get((z0, z1)) if kept is not None: @@ -1227,16 +1033,6 @@ def _delta_phi( floats. A problem that took the z table has no such form and falls back to ``delta_phi``; a compiled backend that holds the table itself overrides this with its own evaluation. - - Parameters - ---------- - bundle : solprop.translate.stage.StageScalars - The stage's host scalars. - - Returns - ------- - numpy.ndarray - ``Phi(z1) - Phi(z0)``, dimensionless. """ if bundle.phi_delta is None or self._terms.phi is None: return delta_phi( @@ -1249,15 +1045,7 @@ def _prefactor( ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: """Return the response prefactor at the stage's position, from the bundle. - Parameters - ---------- - bundle : solprop.translate.stage.StageScalars - The stage's host scalars. - - Returns - ------- - numpy.ndarray - The prefactor times the crop scale, in m/(V s) per unit polarisation. + - Returns: the prefactor times the crop scale, in m/(V s) per unit polarisation. """ if bundle.prefactor is None or self._terms.prefactor is None: return prefactor_at( @@ -1273,16 +1061,6 @@ def _unitary_phase( An expansion of ``Phi`` is cumulative and vanishes at the start of the range, so its value at a position is the bundle's ``phi_at`` against the same arrays and the noise costs no second representation. - - Parameters - ---------- - bundle : solprop.translate.stage.StageScalars - The stage's host scalars. - - Returns - ------- - numpy.ndarray - The phase in radians. """ if bundle.phi_at is None or self._terms.phi is None: return unitary_phase(self._problem.linear, bundle.z1) @@ -1454,13 +1232,7 @@ def set_stop(self, flag: StopFlag | None) -> None: self._stop = flag def _poll_stop(self) -> None: - """Raise when the run's stop flag is set; called before every trial or call. - - Raises - ------ - solprop.exceptions.StopRequested - If the flag is set. - """ + """Raise when the run's stop flag is set; called before every trial or call.""" poll(self._stop) def propagate_to(self, z: float) -> StepSummary: @@ -1632,51 +1404,39 @@ def observe(self, *, field: bool = True) -> Observation: return plane @property - def in_flight(self) -> tuple[float, float] | None: - """Return the low-storage step a failure abandoned in flight, or ``None``. + def in_flight(self) -> tuple[float, float] | None: # numpydoc ignore=RT01 + """Low-storage step a failure abandoned in flight, or ``None``. - Returns - ------- - tuple of float or None - The step's start and length, m, while the state is neither of its ends - (``solprop.backends.low_storage``). + The step's start and length, m, while the state is neither of its ends + (``solprop.backends.low_storage``). """ return self._in_flight @property - def position(self) -> float: - """Return where the march stands, m. + def position(self) -> float: # numpydoc ignore=RT01 + """Where the march stands, m. - Returns - ------- - float - The committed state's position, or the end of a step a group took but - neither validated nor recorded, which leaves the state in flight - (:attr:`in_flight`). + The committed state's position, or the end of a step a group took but + neither validated nor recorded, which leaves the state in flight + (:attr:`in_flight`). """ return float(self._z) @property - def halted(self) -> bool: - """Return whether the last march stopped at a requested halt short of its end. + def halted(self) -> bool: # numpydoc ignore=RT01 + """Whether the last march stopped at a requested halt short of its end. - Returns - ------- - bool - True while the halt latch of :meth:`request_halt` or the yield check of - :meth:`set_halt_check` stopped it. + True while the halt latch of :meth:`request_halt` or the yield check of + :meth:`set_halt_check` stopped it. """ return self._halted @property - def holding_plane(self) -> bool: - """Return whether a dense observation holds the state between endpoints. + def holding_plane(self) -> bool: # numpydoc ignore=RT01 + """Whether a dense observation holds the state between endpoints. - Returns - ------- - bool - True while this driver's own (a Runge-Kutta march's) or its Adams march's - observation holds an interpolated plane, the march ahead of it. + True while this driver's own (a Runge-Kutta march's) or its Adams march's + observation holds an interpolated plane, the march ahead of it. """ return self._dense_ahead is not None or ( self._adams is not None and self._adams._ahead is not None @@ -1748,10 +1508,7 @@ def _begin_plane(self) -> None: # noqa: B027 - optional by design def _march_to(self, target: float) -> None: """Step adaptively to a position, landing on it exactly. - Parameters - ---------- - target : float - Position to reach, m. + - ``target``: position to reach, m. """ settings = self._stepper policy = self._controller_policy() @@ -1843,17 +1600,9 @@ def _dense_record( II.6): ``v0``, ``v1 - v0``, ``h k1 - (v1 - v0)``, ``(v1 - v0) - h k7 - (h k1 - (v1 - v0))`` and ``h sum_i d_i k_i``. - Parameters - ---------- - z : float - The step's anchor, m. - dz : float - The step, m. - - Returns - ------- - tuple - The anchor, the step and the five coefficients, host arrays. + - ``z``: the step's anchor, m. + - ``dz``: the step, m. + - Returns: the anchor, the step and the five coefficients, host arrays. """ start = self._observed_field(cast(_StateT, self._state)).reshape( (1, *self._problem.shape) @@ -1866,10 +1615,7 @@ def _dense_record( def _observe_inside(self, position: float) -> None: """Make the state the interpolated field at `position` and hold the march's. - Parameters - ---------- - position : float - The plane, m, inside the last accepted step. + - ``position``: the plane, m, inside the last accepted step. """ assert self._dense_step is not None z, dz, coefficients = self._dense_step @@ -1897,28 +1643,12 @@ def _dense_stages(self) -> list[np.ndarray]: In the step's anchor frame and the canonical layout, one per tableau stage. A backend that observes densely overrides this; the default refuses. - - Returns - ------- - list of numpy.ndarray - The stage derivatives. - - Raises - ------ - BackendError - Always, on a backend without dense output. """ msg = f"{self.name} does not observe densely" raise BackendError(msg) def _controller_policy(self) -> rk_core.RungeKuttaPolicy: - """Return the Runge-Kutta march's controller policy (SEP 45). - - Returns - ------- - solprop.backends.rk_core.RungeKuttaPolicy - The run's controller, floor, cap and start-up flag. - """ + """Return the Runge-Kutta march's controller policy (SEP 45).""" return rk_policy(self._stepper) def _start_up(self, norm: float) -> None: @@ -1936,11 +1666,6 @@ def _start_up(self, norm: float) -> None: into the history it would steer every later step by that round-off, so the history records zero instead -- no estimate -- and the backends take the same steps. - - Parameters - ---------- - norm : float - The first accepted step's norm at its proposed length. """ self._proposal, self._accepted_norms[-1] = rk_core.start_up( self._controller_policy(), self._proposal, norm @@ -1957,20 +1682,7 @@ def _replay_to(self, schedule: FixedSchedule, target: float) -> None: :meth:`~solprop.translate.problem.PropagationProblem.validate` has already checked the schedule can do. - Parameters - ---------- - schedule : solprop.stepper.schedule.FixedSchedule - The schedule to replay. - target : float - Position to reach, m. - - Raises - ------ - BackendError - If the schedule runs out before the target is reached. - ConvergenceError - If a step's error norm is not finite: the field has diverged, and a replay - cannot reject the step, so it stops there rather than carry NaN on. + - ``target``: position to reach, m. """ edges = schedule.edges slack = replay_tolerance(schedule, target) @@ -2018,15 +1730,7 @@ def _natural_stop(self, target: float) -> bool: saved as the target; one that crosses a densely observed target is saved after it, when the next call finds the stop due (:meth:`_natural_stop_due`). - Parameters - ---------- - target : float - The position the call is stepping to, m. - - Returns - ------- - bool - Whether the march stops here, short of `target`. + - ``target``: the position the call is stepping to, m. """ every = self._stepper.step_planes self._natural_count += 1 @@ -2088,16 +1792,9 @@ def poll_halt(self) -> None: def _halted_short(self, target: float) -> bool: """Return whether a halt stops the march here, short of `target`. - Parameters - ---------- - target : float - The position the call is stepping to, m. - - Returns - ------- - bool - True when a halt is requested or the yield check asks for one, and the - endpoint is short of `target`. + - ``target``: the position the call is stepping to, m. + - Returns: True when a halt is requested or the yield check asks for one, and + the endpoint is short of `target`. """ if reached(self._z, target): return False @@ -2114,15 +1811,7 @@ def _natural_stop_due(self, target: float) -> bool: saved now, unless `target` is another plane it also crossed; otherwise a halt asked for there (:meth:`halt_ahead`) stops the march there. - Parameters - ---------- - target : float - The position the call is stepping to, m. - - Returns - ------- - bool - Whether to return at once, at the step's end. + - ``target``: the position the call is stepping to, m. """ ahead, self._halt_ahead = self._halt_ahead, False if self._natural_due and not reached(self._z, target): @@ -2229,12 +1918,7 @@ def attach_monitor(self, monitor: BoundaryMonitor) -> None: def _read_transverse(self, monitor: BoundaryMonitor, *, plane: bool) -> None: """Read the transverse edge where its spacing or a saved plane asks (SEP 40). - Parameters - ---------- - monitor : solprop.monitor.BoundaryMonitor - The attached monitor. - plane : bool - Whether a saved plane has just been reached, which is always read. + - ``plane``: whether a saved plane has just been reached, which is always read. """ edge = monitor.transverse if edge is None: @@ -2289,23 +1973,7 @@ def _edge_reading( (``_HostEdgeReduction``); a backend whose state lives elsewhere reduces it there and returns the numbers, without a copy of it. - Parameters - ---------- - state : object - The backend's protected state representation. - edge : solprop.transverse_monitor.TransverseEdge - The run's edge. - faces : bool, optional - Whether the nodes, the columns and any collar's strip are wanted. - current : bool, optional - Whether a collar's inward current is wanted. - full : bool, optional - Whether every node is wanted, not only the rows a read uses. - - Returns - ------- - solprop.transverse_monitor._EdgeReading - The reading. + - ``full``: whether every node is wanted, not only the rows a read uses. """ return edge.reduction.reading( np.asarray(self._observed_field(state)[0]), @@ -2324,16 +1992,8 @@ def _edge_clean_columns( resume that found no record of the monitor has none, and then the state has to cross once. - Parameters - ---------- - edge : solprop.transverse_monitor.TransverseEdge - The run's edge. - - Returns - ------- - numpy.ndarray or None - The unweighted energies, or ``None`` with no noise, no ramp or on a resumed - launch. + - Returns: the unweighted energies, or ``None`` with no noise, no ramp or on a + resumed launch. """ floor = self._input_floor if floor is None or edge.resumed or edge.design is None: @@ -2349,11 +2009,8 @@ def _next_step(self) -> float: A replay takes its schedule's and never updates the proposal, which stays ``dz_init``: read from that, an edge read comes early or late by the difference. - Returns - ------- - float - The schedule's next step on a replay, zero past its end, and otherwise the - controller's proposal. + - Returns: the schedule's next step on a replay, zero past its end, and + otherwise the controller's proposal. """ schedule = self._stepper.schedule if schedule is None: @@ -2369,15 +2026,8 @@ def _transverse_capped(self, proposal: float) -> float: within one; the read spacing is a ceiling on the step instead. The step is not made to land on a read: the edge is read before a step would pass one. - Parameters - ---------- - proposal : float - The controller's proposal, m. - - Returns - ------- - float - The proposal, or the read spacing, m. + - ``proposal``: the controller's proposal, m. + - Returns: the proposal, or the read spacing, m. """ monitor = self._monitor edge = None if monitor is None else monitor.transverse @@ -2393,15 +2043,8 @@ def _split_capped(self, proposal: float) -> float: into faster-walking bins shortens the steps until the next check, which reads it again. - Parameters - ---------- - proposal : float - The controller's proposal, m. - - Returns - ------- - float - The proposal, or the cap, m. + - ``proposal``: the controller's proposal, m. + - Returns: the proposal, or the cap, m. """ monitor = self._monitor return proposal if monitor is None else min(proposal, monitor.step_cap) @@ -2416,10 +2059,7 @@ def _bind_monitor(self, monitor: BoundaryMonitor | None) -> None: columns' quadrature) where it reduces, and to add each accepted step's split removal there from now on (:meth:`_boundary_energies`). - Parameters - ---------- - monitor : solprop.monitor.BoundaryMonitor or None - The run's monitor, or ``None`` to stop. + - ``monitor``: the run's monitor, or ``None`` to stop. """ del monitor self._split_removed = 0.0 @@ -2429,10 +2069,8 @@ def _bind_monitor(self, monitor: BoundaryMonitor | None) -> None: def _split_monitor(self) -> BoundaryMonitor | None: """Return the monitor a host backend's finish adds the split's removal for. - Returns - ------- - solprop.monitor.BoundaryMonitor or None - The attached monitor under the ``"split_step"`` scheme, else ``None``. + - Returns: the attached monitor under the ``"split_step"`` scheme, else + ``None``. """ monitor = self._monitor return monitor if monitor is not None and monitor.split else None @@ -2447,21 +2085,6 @@ def _boundary_energies(self, state: _StateT) -> BoundaryEnergies: reduces on the host (:meth:`solprop.monitor.BoundaryMonitor.energies`), the reference; a backend whose state lives elsewhere reduces it there and returns the five numbers, without a copy of the field. - - Parameters - ---------- - state : object - The backend's protected state representation. - - Returns - ------- - solprop.monitor.BoundaryEnergies - The reading. - - Raises - ------ - BackendError - If no monitor is attached. """ monitor = self._monitor if monitor is None: @@ -2483,12 +2106,8 @@ def _check_boundaries( ) -> None: """Account the committed step at the current position with the monitor. - Parameters - ---------- - monitor : solprop.monitor.BoundaryMonitor - The attached monitor. - plane : bool, optional - True for a saved plane's check of a step the monitor already skipped. + - ``plane``: true for a saved plane's check of a step the monitor already + skipped. """ self._check_at( monitor, @@ -2509,18 +2128,7 @@ def _check_at( ) -> None: """Book a reading of the step committed at `z` with the monitor (SEP 45 4.10). - Parameters - ---------- - monitor : solprop.monitor.BoundaryMonitor - The attached monitor. - energies : solprop.monitor.BoundaryEnergies - The reading of the committed state. - z : float - The step's end, m. - cursor : int - The replay cursor after the step. - plane : bool, optional - True for a saved plane's check of a step the monitor already skipped. + - ``z``: the step's end, m. """ monitor.step(energies, z, plane=plane) self._set_collar_quiet(monitor.collar_quiet) @@ -2543,19 +2151,9 @@ def _refuse_scheduled_crossing( longest of those is held to it. Read at the launch too, so that a resumed schedule is held to the state it resumes from. - Parameters - ---------- - monitor : solprop.monitor.BoundaryMonitor - The attached monitor, just read. - cursor : int or None, optional - The replay cursor after the step read; ``None`` for the driver's own. - z : float or None, optional - The step's end, m; ``None`` for the driver's position. - - Raises - ------ - BoundaryError - Naming the cap and the step. + - ``cursor``: the replay cursor after the step read; ``None`` for the driver's + own. + - ``z``: the step's end, m; ``None`` for the driver's position. """ schedule = self._stepper.schedule first = self._schedule_index if cursor is None else cursor @@ -2585,11 +2183,6 @@ def _set_collar_quiet(self, quiet: bool) -> None: first-same-as-last stage, which was evaluated under the other setting (:meth:`_collar_changed`); a backend whose stages run elsewhere passes the flag on there. - - Parameters - ---------- - quiet : bool - The monitor's verdict. """ quiet = quiet and self._collar_can_rest() if self._monitor is not None: @@ -2608,15 +2201,9 @@ def _quiet_capped(self, proposal: float) -> float: guide's length over :data:`solprop.monitor.CHECKS`), so light has to walk about twenty windows over the run to cross the collar between two checks. - Parameters - ---------- - proposal : float - The controller's proposal, m. - - Returns - ------- - float - The proposal, or the monitor's interval while the collar is left out, m. + - ``proposal``: the controller's proposal, m. + - Returns: the proposal, or the monitor's interval while the collar is left out, + m. """ monitor = self._monitor if not self._collar_quiet or monitor is None: @@ -2633,10 +2220,7 @@ def _collar_can_rest(self) -> bool: planar routes, whose stages keep the term whatever the monitor says, keep the collar in every stage (SEP 21 D15). - Returns - ------- - bool - True when the term may be left out while the collar is quiet. + - Returns: True when the term may be left out while the collar is quiet. """ problem = self._problem responses = problem.responses @@ -2724,19 +2308,12 @@ def _statistics_step(self) -> None: def _record(self, z: float, dz: float, norm: float, accepted: bool) -> None: """Append one step to the history since the last observation. - Parameters - ---------- - z : float - Position at the end of the step, m. - dz : float - Length of the step, m. - norm : float - The scaled error norm the attempt scored, as executed: a landing step - clipped short records its own norm, not the proposal's equivalent. A - diverged attempt, whose norm overflowed or was NaN, is recorded as - infinity; the controller never accepts one. - accepted : bool - Whether the step was kept. + - ``z``: position at the end of the step, m. + - ``dz``: length of the step, m. + - ``norm``: the scaled error norm as executed: a landing step clipped short + records its own, not the proposal's equivalent. A diverged attempt's + (overflowed or NaN) is recorded as infinity, which the controller never + accepts. """ if not math.isfinite(norm): norm = math.inf @@ -2752,15 +2329,7 @@ def _noise_field( ) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"] | None: """Return the noise realisation to carry, in the canonical layout. - Parameters - ---------- - noise : solprop.noise.NoiseRealisation or None - The realisation passed to :meth:`set_initial`. - - Returns - ------- - numpy.ndarray or None - The noise spectrum, or ``None`` when no noise model is active. + - Returns: the noise spectrum, or ``None`` when no noise model is active. """ if self._problem.initial.noise_model == "none": return None @@ -2778,15 +2347,8 @@ def _noise_at( the same terms as the linear flow, so the noise costs one multiply-add per element per stage and no transform. - Parameters - ---------- - bundle : solprop.translate.stage.StageScalars - The stage's host scalars. - - Returns - ------- - numpy.ndarray or None - The advanced noise, or ``None`` when the modified model is not in use. + - Returns: the advanced noise, or ``None`` when the modified model is not in + use. """ if self._noise is None: return None @@ -2797,10 +2359,7 @@ def _noise_phase( ) -> Float[np.ndarray, "n_modes n_pol n_omega"] | None: """Return ``Im Phi`` at the current position, or ``None`` without a noise model. - Returns - ------- - numpy.ndarray or None - The phase in radians, in the canonical layout. + - Returns: the phase in radians, in the canonical layout. """ if self._problem.initial.noise_model == "none": return None @@ -2822,24 +2381,12 @@ def _axis_statistics( observation's node scan; otherwise the axis row is scanned once (:func:`solprop.backends.populations.axis_populations`). - Parameters - ---------- - field : numpy.ndarray or None - One batch member at this plane, shape (n_modes, n_pol, n_omega), or ``None`` - from a backend whose :meth:`_axis_row_populations` reads its own state. - scan : tuple or None - This observation's channel fractions and peak rates at every node, when - the node statistics already scanned them. - - Returns - ------- - dict - The four ``on_axis_*`` statistics. - - Raises - ------ - BackendError - If the backend was built without its mode set. + - ``field``: one batch member at this plane, shape (n_modes, n_pol, n_omega), or + ``None`` from a backend whose :meth:`_axis_row_populations` reads its own + state. + - ``scan``: this observation's channel fractions and peak rates at every node, + when the node statistics already scanned them. + - Returns: the four ``on_axis_*`` statistics. """ if self._modes is None: msg = ( @@ -2885,23 +2432,10 @@ def _axis_row_populations( A device backend overrides this hook to read only the row from its state. - Parameters - ---------- - field : numpy.ndarray or None - One batch member at this plane, shape (n_modes, n_pol, n_omega). - axis : numpy.ndarray - The mode set's synthesis on the axis at this position - (:meth:`_on_axis_synthesis`). - - Returns - ------- - tuple of numpy.ndarray - The final ionised fraction and the largest rate on the axis, per channel. - - Raises - ------ - BackendError - If this backend needs the field and was not given it. + - ``axis``: the mode set's synthesis on the axis at this position + (:meth:`_on_axis_synthesis`). + - Returns: the final ionised fraction and the largest rate on the axis, per + channel. """ if field is None: raise BackendError("the on-axis statistics scan needs the field") @@ -2925,15 +2459,8 @@ def _on_axis_synthesis( Rebuilding the matrix at every call was what kept ``peak_intensity`` out of the between-planes channel. - Parameters - ---------- - modes : collections.abc.Sequence - The mode set the backend was built with. - - Returns - ------- - numpy.ndarray - Shape ``(n_rows, n_modes)``, one row per Cartesian component on the axis. + - Returns: shape ``(n_rows, n_modes)``, one row per Cartesian component on the + axis. """ if self._on_axis is None: rule = on_axis_rule(modes[0].dimlimits(0.0)[0]) @@ -2945,18 +2472,7 @@ def _on_axis_synthesis( return self._on_axis * float(taper(self._z)) def _require_grid(self) -> TimeGrid: - """Return the grid the state lives on, or say why statistics cannot be taken. - - Returns - ------- - solprop.grid.TimeGrid - The grid given at construction. - - Raises - ------ - BackendError - If none was given. - """ + """Return the grid the state lives on, or say why statistics cannot be taken.""" if self._grid is None: msg = ( "statistics need the grid the state lives on, which the kernel " @@ -2967,19 +2483,7 @@ def _require_grid(self) -> TimeGrid: return self._grid def _require_state(self) -> _StateT: - """Return the current state, refusing a closed or unloaded backend. - - Returns - ------- - numpy.ndarray - The state. - - Raises - ------ - BackendError - If the backend is closed, no initial state was loaded, or a low-storage - step was abandoned in flight. - """ + """Return the current state, refusing a closed or unloaded backend.""" self._check_open() if self._state is None: msg = "no initial state: call set_initial before propagating or observing" @@ -2994,11 +2498,6 @@ def _refuse_in_flight(self) -> None: the step's start to its commit the state is neither the step's start nor its end, so an exception or a signal between them leaves nothing a reader may use and nothing a march may continue from. Only :meth:`set_initial` clears it. - - Raises - ------ - BackendError - Naming the step that was abandoned. """ if self._in_flight is None: return @@ -3012,24 +2511,12 @@ def _refuse_in_flight(self) -> None: raise BackendError(msg) @property - def closed(self) -> bool: - """Return whether the backend has released its buffers, as a failure may. - - Returns - ------- - bool - ``True`` when it can run no other input. - """ + def closed(self) -> bool: # numpydoc ignore=RT01 + """Whether the backend has released its buffers, as a failure may.""" return self._closed def _check_open(self) -> None: - """Refuse a backend that has been closed. - - Raises - ------ - BackendError - If ``close`` has been called. - """ + """Refuse a backend that has been closed.""" if self._closed: msg = "this backend is closed; its buffers were released" raise BackendError(msg) @@ -3037,20 +2524,7 @@ def _check_open(self) -> None: def _checked_position(self, z: float) -> float: """Return a position the state may be loaded at, refusing one off the table. - Parameters - ---------- - z : float - The requested position, m. - - Returns - ------- - float - The same position. - - Raises - ------ - BackendError - If it is not finite or lies outside the operator table. + - ``z``: the requested position, m. """ low, high = self._problem.linear.z_span if not (math.isfinite(z) and low <= z <= high): @@ -3064,24 +2538,10 @@ def _checked_position(self, z: float) -> float: def _schedule_index_at(self, z: float) -> int: """Return how many steps of a replayed schedule a position has consumed. - Parameters - ---------- - z : float - The position the state is loaded at, m. - - Returns - ------- - int - The index of the schedule's edge at `z`, and 0 without a schedule. - - Raises - ------ - BackendError - If a schedule is being replayed and `z` is not one of its edges. Every save - plane is an edge, which - :meth:`~solprop.translate.problem.PropagationProblem.validate` has checked, - so this fires only for a position the run never stopped at -- from which a - replay could not continue in step. + - ``z``: the position the state is loaded at, m. On a replay it must be an edge; + every save plane is one (``validate`` checks it), so the refusal fires only + for a position the run never stopped at, from which a replay could not + continue in step. """ schedule = self._stepper.schedule if schedule is None: @@ -3099,15 +2559,7 @@ def _schedule_index_at(self, z: float) -> int: def _check_target(self, target: float) -> None: """Refuse a save plane behind the current position or outside the table. - Parameters - ---------- - target : float - The requested position, m. - - Raises - ------ - BackendError - Saying why the position cannot be reached. + - ``target``: the requested position, m. """ if not math.isfinite(target): msg = f"the save plane must be finite, got {target!r}" @@ -3138,19 +2590,7 @@ def _clean_launch_columns( noise alone off it, as a supplied realisation needn't be masked. Formed from the parts, so that no state has to come back from where it lives to subtract the noise. - Parameters - ---------- - field : numpy.ndarray - The launch before the noise is added. - noise : numpy.ndarray - The realisation added to it. - active : numpy.ndarray - The active band's mask, shape ``(n_omega,)``. - - Returns - ------- - numpy.ndarray - The unweighted energies, shape ``(n_modes,)``. + - ``active``: the active band's mask, shape ``(n_omega,)``. """ inside = np.abs(field[..., active]) ** 2 outside = np.abs(noise[..., ~active]) ** 2 @@ -3160,15 +2600,7 @@ def _clean_launch_columns( def _declared_discard(problem: PropagationProblem) -> bool: """Return whether a run declared content above ``discard_above`` (SEP 40 5.3). - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The run's problem. - - Returns - ------- - bool - True when its transverse specification sets ``discard_above``. + - Returns: True when its transverse specification sets ``discard_above``. """ design = transverse_of(problem.route) return design is not None and design.specification.discard_above is not None @@ -3179,16 +2611,9 @@ def _history_record( ) -> BackendHistory: """Return the attempts since the last observation as a history record. - Parameters - ---------- - entries : sequence of tuple - One ``(z, dz, error_norm, accepted, wall_time)`` per attempt. - - Returns - ------- - solprop.backends.protocol.BackendHistory - The attempts. Its arrays are made here and held, so the record takes them - without a copy. + - `entries`: one ``(z, dz, error_norm, accepted, wall_time)`` per attempt. + - Returns: the attempts, in arrays made here and held, so the record takes them + without a copy. """ columns = list(zip(*entries, strict=True)) if entries else [()] * 5 return BackendHistory( diff --git a/src/solprop/backends/host/general.py b/src/solprop/backends/host/general.py index fb652d658..5e1013338 100644 --- a/src/solprop/backends/host/general.py +++ b/src/solprop/backends/host/general.py @@ -65,23 +65,9 @@ def start( # noqa: PLR0917 - called positionally ) -> "_Trial": """Form the predictor of a step from the history, newest derivative first. - Parameters - ---------- - history : list - The accepted derivatives, oldest first, in the anchor's frame. - order : int - The order asked for; fewer derivatives during startup lower it. - z, dz : float - The anchor and the step, m. - state : numpy.ndarray - The accepted state at the anchor. - backend : solprop.backends.host.driver.HostDriver - The backend, for the step's propagator and norm. - - Returns - ------- - _Trial - The trial, with its predictor formed. + - ``history``: the accepted derivatives, oldest first, in the anchor's frame. + - ``order``: the order asked for; fewer derivatives during startup lower it. + - ``z``, ``dz``: the anchor and the step, m. """ recent = history[::-1] nodes = np.array([(entry.z - z) / dz for entry in recent]) @@ -106,30 +92,16 @@ def start( # noqa: PLR0917 - called positionally def lab(self, increment: _State) -> _State: """Return the lab-frame field at the end of the step for an increment. - Parameters - ---------- - increment : numpy.ndarray - ``v - a_n`` in the anchor's frame. - - Returns - ------- - numpy.ndarray - ``U(z + dz, z) (a_n + increment)``. + ``U(z + dz, z) (a_n + increment)``, the increment being ``v - a_n`` in the + anchor's frame. """ return self.propagator * (self.state + increment) def norm(self, defect: _State) -> float: """Return the weak norm of a defect, carried to the end of the step. - Parameters - ---------- - defect : numpy.ndarray - A local-defect estimate in the anchor's frame. - - Returns - ------- - float - The scaled norm; one is the tolerance. + The defect is a local estimate in the anchor's frame; a norm of one is the + tolerance. """ return self.scale(self.propagator * defect) @@ -375,22 +347,6 @@ def _defect_at(trial: _Trial, source: _State, order: int, helpers: Helpers) -> f The order's predictor and its one-order-higher corrector are formed from the same history and the same predicted-state source, so scoring a neighbour costs no call. - - Parameters - ---------- - trial : _Trial - The accepted step. - source : numpy.ndarray - Its predicted-state source, in the step's anchor frame. - order : int - The neighbouring order, no more than the derivatives the trial holds. - helpers : solprop._helpers.Helpers - The owning backend's implementation of the Adams basis. - - Returns - ------- - float - The scaled predictor-corrector difference at that order. """ nodes, values = trial.nodes[:order], trial.values[:order] predicted = _combine( @@ -419,17 +375,7 @@ def _norm_function( from 1e7 to 1e236 in five accepted steps with every scaled norm below 0.6. The state the step starts from is the scale that cannot grow with the step it judges. - Parameters - ---------- - backend : solprop.backends.host.driver.HostDriver - The backend, for the controller and the in-band mask. - state : numpy.ndarray - The accepted state before the step. - - Returns - ------- - callable - ``defect -> norm`` for a defect carried to the end of the step. + - Returns: ``defect -> norm`` for a defect carried to the end of the step. """ problem = backend._problem mask, controller = problem.grids.norm_mask, backend._stepper.controller @@ -441,18 +387,7 @@ def _norm_function( divisor = scale[mask] if isinstance(scale, np.ndarray) else scale def scaled(defect: _State) -> float: - """Return the scaled norm of a defect at the end of the step. - - Parameters - ---------- - defect : numpy.ndarray - The defect, carried to the end of the step. - - Returns - ------- - float - The scaled norm. - """ + """Return the scaled norm of a defect at the end of the step.""" return float(np.linalg.norm(defect[..., mask] / divisor)) return scaled diff --git a/src/solprop/backends/host/group.py b/src/solprop/backends/host/group.py index 6126e7f6b..e2939f996 100644 --- a/src/solprop/backends/host/group.py +++ b/src/solprop/backends/host/group.py @@ -41,19 +41,6 @@ def _refuse_invalid_norm(norms: Sequence[float], backend: str) -> None: alone, so this is not a batch-only rule; what the batch owes the caller is the member's index, rather than an anonymous history error one plane later that the reader cannot attribute to a point. - - Parameters - ---------- - norms : collections.abc.Sequence - Each member's own scaled error norm for the step just evaluated, - dimensionless, in member order. - backend : str - Name of the batch runner, for the message. - - Raises - ------ - BackendError - Naming the first member with an invalid norm and the value it returned. """ for index, norm in enumerate(norms): if not (np.isfinite(norm) and norm >= 0.0): @@ -111,35 +98,16 @@ def __init__(self, members: Sequence[HostDriver[object]]) -> None: def _apply_mask( self, state: Complex[np.ndarray, "n_batch n_modes n_pol n_omega"] ) -> tuple[object, ...]: - """Refuse loading a shared field in place of independently loaded members. - - Parameters - ---------- - state : numpy.ndarray - Unused shared initial field. - - Raises - ------ - BackendError - Always; member owners must be initialised before construction. - """ + """Refuse loading a shared field in place of independently loaded members.""" del state raise BackendError("HostGroup requires independently loaded members") def _trial_step(self, z: float, dz: float) -> tuple[float, tuple[object, ...]]: """Evaluate independent trial states at one common fixed step. - Parameters - ---------- - z : float - Start position, m. - dz : float - Positive step length, m. - - Returns - ------- - tuple - Maximum diagnostic norm and independently owned trial states. + - ``z``: start position, m. + - ``dz``: positive step length, m. + - Returns: maximum diagnostic norm and independently owned trial states. """ trials = tuple(member._trial_step(z, dz) for member in self._members) self._member_norms = tuple(norm for norm, _ in trials) @@ -158,17 +126,7 @@ def _replay_to(self, schedule: FixedSchedule, target: float) -> None: the shared itinerary is recorded once, with each member's own norm. A member left in flight by an abandoned step refuses the next one. - Parameters - ---------- - schedule : solprop.stepper.schedule.FixedSchedule - The common schedule. - target : float - Position to reach, m. - - Raises - ------ - BackendError - If the schedule runs out before the target is reached. + - ``target``: position to reach, m. """ marches = [ member._adams if member._adams is not None else member._low_storage @@ -223,17 +181,7 @@ def _replay_to(self, schedule: FixedSchedule, target: float) -> None: def _finish(self, state: tuple[object, ...], dz: float) -> tuple[object, ...]: """Apply each member's accepted-step boundary handling. - Parameters - ---------- - state : tuple - Trial states in member order. - dz : float - Accepted step length, m. - - Returns - ------- - tuple - Independently finished states, also installed on their owners. + - ``dz``: accepted step length, m. """ finished = tuple( member._finish(value, dz) @@ -246,16 +194,8 @@ def _finish(self, state: tuple[object, ...], dz: float) -> tuple[object, ...]: def _record(self, z: float, dz: float, norm: float, accepted: bool) -> None: """Record the common itinerary and each member's own error estimate. - Parameters - ---------- - z : float - End position, m. - dz : float - Step length, m. - norm : float - Maximum member diagnostic norm. - accepted : bool - Whether the shared step was accepted. + - ``z``: end position, m. + - ``dz``: step length, m. """ super()._record(z, dz, norm, accepted) for member, own_norm in zip(self._members, self._member_norms, strict=True): @@ -302,30 +242,28 @@ def attach_monitors(self, monitors: Sequence[BoundaryMonitor | None]) -> None: self._watched(index, functools.partial(member.attach_monitor, monitor)) @property - def member_stops(self) -> tuple[BoundaryError | BoundaryWarning | None, ...]: - """Return what stopped each member's watch, in member order. - - Returns - ------- - tuple - The :class:`~solprop.exceptions.BoundaryError` that stopped a member, the - :class:`~solprop.exceptions.BoundaryWarning` raised as an error for it - under an ``"error"`` warnings filter, or ``None`` for one still watched - or never watched. + def member_stops( + self, + ) -> tuple[BoundaryError | BoundaryWarning | None, ...]: # numpydoc ignore=RT01 + """What stopped each member's watch, in member order. + + The :class:`~solprop.exceptions.BoundaryError` that stopped a member, the + :class:`~solprop.exceptions.BoundaryWarning` raised as an error for it + under an ``"error"`` warnings filter, or ``None`` for one still watched + or never watched. """ return tuple(self._stops) @property - def member_records(self) -> tuple[Mapping[str, object] | None, ...]: - """Return each member's monitor record, as it closed, in member order. + def member_records( + self, + ) -> tuple[Mapping[str, object] | None, ...]: # numpydoc ignore=RT01 + """Each member's monitor record, as it closed, in member order. A stopped member's is its record at the stop; a watched one's is read now. - Returns - ------- - tuple - The :meth:`~solprop.monitor.BoundaryMonitor.summary` of each member, or - ``None`` for one never watched. + The :meth:`~solprop.monitor.BoundaryMonitor.summary` of each member, or + ``None`` for one never watched. """ return tuple( record if member._monitor is None else member._monitor.summary() @@ -348,13 +286,6 @@ def _watched(self, index: int, call: Callable[[], None]) -> None: The member's march goes on with the others, unwatched and with its collar in every stage; its result is the caller's to discard. - - Parameters - ---------- - index : int - The member's position. - call : collections.abc.Callable - The call, which may raise the member's stop. """ member = self._members[index] try: @@ -507,18 +438,7 @@ def close(self) -> None: def _validate_members(members: tuple[HostDriver[object], ...]) -> None: - """Require one backend and a shared entrance-to-end replay contract. - - Parameters - ---------- - members : tuple of solprop.backends.host.driver.HostDriver - Loaded workspaces whose ownership has not yet transferred. - - Raises - ------ - BackendError - If any workspace lies outside the lockstep contract. - """ + """Require one backend and a shared entrance-to-end replay contract.""" if not members: raise BackendError("lockstep execution requires at least one member") first = members[0] @@ -578,16 +498,10 @@ def _validate_members(members: tuple[HostDriver[object], ...]) -> None: def _refuse_mixed_precisions(members: tuple[HostDriver[object], ...]) -> None: """Refuse members whose problems do not share one precision. - Parameters - ---------- - members : tuple of solprop.backends.host.driver.HostDriver - Loaded workspaces, at least one. + Each member's trial keeps its own format, and the runner's common step and norms + would mix them. - Raises - ------ - BackendError - If a member's precision differs from the first's: each member's trial keeps - its own format, and the runner's common step and norms would mix them. + - ``members``: loaded workspaces, at least one. """ first = members[0]._problem.precision if any(member._problem.precision != first for member in members): @@ -597,16 +511,8 @@ def _refuse_mixed_precisions(members: tuple[HostDriver[object], ...]) -> None: def _general_leaves(problem: PropagationProblem) -> object: """Return what general modal guide a problem carries, comparably. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - A member's problem. - - Returns - ------- - object - A scalar-and-string description, or ``None`` where the problem carries no - general modal route at all. + - Returns: a scalar-and-string description, or ``None`` where the problem carries no + general modal route at all. """ tables = problem.transform.modal_tables if tables is None: @@ -640,18 +546,6 @@ def _refuse_mixed_guides( check that does not depend on a caller having grouped carefully, written against the leaves a backend actually sees rather than against the driver's descriptor. - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The member being admitted. - reference : solprop.translate.problem.PropagationProblem - Member zero's problem, whose guide the group is. - - Raises - ------ - BackendError - If the two carry different general modal guides, or only one carries one. """ if _general_leaves(problem) == _general_leaves(reference): return diff --git a/src/solprop/backends/host/group_session.py b/src/solprop/backends/host/group_session.py index 88f38883f..1086739f9 100644 --- a/src/solprop/backends/host/group_session.py +++ b/src/solprop/backends/host/group_session.py @@ -179,36 +179,21 @@ def __init__( # -- protocol ------------------------------------------------------------------- @property - def n_members(self) -> int: - """Return the number of members. - - Returns - ------- - int - The members. - """ + def n_members(self) -> int: # numpydoc ignore=RT01 + """Number of members.""" return len(self._members) @property - def finished(self) -> bool: - """Return whether the group's terminal batch was issued. + def finished(self) -> bool: # numpydoc ignore=RT01 + """Whether the group's terminal batch was issued. - Returns - ------- - bool - True once every member has ended. + True once every member has ended. """ return self._ledger.finished @property - def closed(self) -> bool: - """Return whether the group was closed. - - Returns - ------- - bool - True once closed. - """ + def closed(self) -> bool: # numpydoc ignore=RT01 + """Whether the group was closed.""" return self._ledger.state is SessionState.CLOSED def advance(self) -> GroupBatch: @@ -398,45 +383,21 @@ def __exit__( # -- internals ------------------------------------------------------------------ def _member(self, member: int) -> int: - """Return a valid member position. - - Parameters - ---------- - member : int - The position asked for. - - Returns - ------- - int - The same position. - - Raises - ------ - solprop.backends.session.SessionError - If the group has no such member. - """ + """Return a valid member position.""" if not 0 <= member < len(self._members): msg = f"the group has {len(self._members)} members, not a member {member}" raise SessionError(msg) return member def _running(self) -> list[int]: - """Return the members that have not ended. - - Returns - ------- - list of int - Their positions. - """ + """Return the members that have not ended.""" return [index for index, ended in enumerate(self._outcomes) if ended is None] def _chunks(self) -> list[tuple[BackendHistory, StepStats]]: """Drain every member's attempts and rows since the previous window. - Returns - ------- - list of tuple - Each member's attempts and rows (without a batch axis), in member order. + - Returns: each member's attempts and rows (without a batch axis), in member + order. """ chunks: list[tuple[BackendHistory, StepStats]] = [] for index in range(len(self._members)): @@ -460,15 +421,8 @@ def _final_chunk(self, index: int) -> tuple[BackendHistory, StepStats] | None: the abandoned step are drained from its record instead (:meth:`~solprop.backends.host.driver.HostDriver.drain_attempts`). - Parameters - ---------- - index : int - The member's position. - - Returns - ------- - tuple or None - Its attempts and rows, or ``None`` when its state cannot be read at all. + - Returns: its attempts and rows, or ``None`` when its state cannot be read at + all. """ member = self._hosts[index] try: @@ -486,15 +440,8 @@ def _final_chunk(self, index: int) -> tuple[BackendHistory, StepStats] | None: def _ticket(self, z: float | None = None) -> ExportTicket: """Return an unbound ticket for a committed state of the current generation. - Parameters - ---------- - z : float or None, optional - The member's committed position, m; the window's end by default. - - Returns - ------- - solprop.backends.events.ExportTicket - The ticket, its receipt filled in when the batch is issued. + - ``z``: the member's committed position, m; the window's end by default. + - Returns: the ticket, its receipt filled in when the batch is issued. """ return ExportTicket( Receipt(self._ledger.session, 0), @@ -507,17 +454,7 @@ def _member_stopped( ) -> Terminal | None: """Return a stopped member's terminal event, or ``None`` if it runs on. - Parameters - ---------- - index : int - The member's position. - chunk : tuple - Its attempts and rows since its previous event. - - Returns - ------- - solprop.backends.events.Terminal or None - The boundary stop that ended it, which ends it alone. + - ``chunk``: its attempts and rows since its previous event. """ stop = self._runner.member_stops[index] if stop is None: @@ -534,13 +471,7 @@ def _member_stopped( ) def _launch(self) -> GroupBatch: - """Give each member its monitor and report the launch planes. - - Returns - ------- - solprop.backends.events.GroupBatch - Each member's launch plane, or the boundary stop that refused it. - """ + """Give each member its monitor and report the launch planes.""" try: self._runner.attach_monitors( [request.boundary.monitor for request in self._requests] @@ -552,22 +483,7 @@ def _launch(self) -> GroupBatch: def _window( self, kind: ObservationKind, plane: int, summary: StepSummary ) -> GroupBatch: - """Report a window's end: each running member's plane, or its boundary stop. - - Parameters - ---------- - kind : solprop.backends.events.ObservationKind - The launch or a save plane. - plane : int - The save plane, ``-1`` for the launch. - summary : solprop.backends.protocol.StepSummary - The common march to it; its maximum norm is a group diagnostic. - - Returns - ------- - solprop.backends.events.GroupBatch - The window's events. - """ + """Report a window's end: each running member's plane, or its boundary stop.""" try: chunks = self._chunks() stats = self._runner.stats(self._requests[0].observation.stats) @@ -605,13 +521,7 @@ def _window( return self._issue(events) def _complete(self) -> GroupBatch: - """Run each watched member's final check and report every outcome. - - Returns - ------- - solprop.backends.events.GroupBatch - Each running member's completion, or the final warning that failed it. - """ + """Run each watched member's final check and report every outcome.""" try: chunks = self._chunks() self._runner.finish_monitors() @@ -648,18 +558,6 @@ def _ended(self, outcome: Outcome, error: BaseException) -> GroupBatch: a member ahead of the failing one stands at the step's end, its step neither validated nor recorded -- in flight, and so poisoned -- and the others at the step's start; each reports its own position. - - Parameters - ---------- - outcome : solprop.backends.events.Outcome - Stopped or failed. - error : BaseException - The primary failure or the stop request. - - Returns - ------- - solprop.backends.events.GroupBatch - Each running member's terminal event at its own committed position. """ self._generation += 1 self._broken = True @@ -706,19 +604,7 @@ def _issue( ) -> GroupBatch: """Issue a window's events under a new receipt. - Parameters - ---------- - members : list - Each member's events; tickets are bound to the receipt here. - outcome : solprop.backends.events.Outcome or None, optional - The group's outcome, when the batch ends it. - error : BaseException or None, optional - A group-wide failure's primary error. - - Returns - ------- - solprop.backends.events.GroupBatch - The batch. + - ``outcome``: the group's outcome, when the batch ends it. """ receipt = self._ledger.issue(outcome=outcome) # Every drained attempt is delivered by this batch. @@ -734,18 +620,6 @@ def _fielded(self, ticket: ExportTicket, member: int) -> Observation: A window's fields are observed together, once; after a group-wide failure each member is observed alone, since a member left in flight refuses observation. - - Parameters - ---------- - ticket : solprop.backends.events.ExportTicket - A valid ticket. - member : int - The member's position. - - Returns - ------- - solprop.backends.protocol.Observation - The member's observation with its field. """ if self._observed_generation != ticket.generation: self._observed = {} @@ -789,13 +663,10 @@ def __init__( self._requests = tuple(requests) @property - def backend(self) -> str: - """Return the registered name of the backend that prepared it. + def backend(self) -> str: # numpydoc ignore=RT01 + """Registered name of the backend that prepared it. - Returns - ------- - str - The first member's registered name. + The first member's registered name. """ return self._members[0].name diff --git a/src/solprop/backends/host/low_storage.py b/src/solprop/backends/host/low_storage.py index 867647257..9e81114fa 100644 --- a/src/solprop/backends/host/low_storage.py +++ b/src/solprop/backends/host/low_storage.py @@ -73,14 +73,8 @@ def __init__(self, backend: HostDriver[Any], method: LowStorageRungeKutta) -> No self.source_evaluations = 0 @property - def engine(self) -> HostLowStorage: - """Return the engine that holds the registers. - - Returns - ------- - solprop.backends.low_storage.HostLowStorage - The host engine. - """ + def engine(self) -> HostLowStorage: # numpydoc ignore=RT01 + """Engine that holds the registers.""" return self._engine def reset(self) -> None: @@ -185,12 +179,7 @@ def replay_step(self, dz: float, end: float, slack: float) -> float: def _move(self, finished: object, end: float) -> None: """Make the finished state the backend's at the step's end; clear the flag. - Parameters - ---------- - finished : object - The state after the backend's ``_finish``. - end : float - The schedule's edge the step ends on, m. + - ``end``: the schedule's edge the step ends on, m. """ backend = self._backend backend._state = finished @@ -201,24 +190,8 @@ def _move(self, finished: object, end: float) -> None: def _accept(self, z: float, dz: float, squared: float) -> object: """Refuse a diverged step, or apply the backend's finish to the state. - Parameters - ---------- - z : float - Where the step started, m. - dz : float - The step, m. - squared : float - The accepted state's squared norm the engine returned. - - Returns - ------- - object - The state after the backend's ``_finish``. - - Raises - ------ - ConvergenceError - If `squared` is not finite. + - ``z``: where the step started, m. + - ``dz``: the step, m. """ backend = self._backend if not math.isfinite(squared): @@ -237,12 +210,8 @@ def _accept(self, z: float, dz: float, squared: float) -> object: def _check_stiffness(self, z: float, dz: float) -> None: """Warn, once per replay, when a step's stiffness passes RK4's interval. - Parameters - ---------- - z : float - Where the step started, m. - dz : float - The step, m. + - ``z``: where the step started, m. + - ``dz``: the step, m. """ stiffness = self._engine.stiffness interval = self._method.real_stability_interval diff --git a/src/solprop/backends/host/session.py b/src/solprop/backends/host/session.py index ce55ab5e5..6df9cdc0a 100644 --- a/src/solprop/backends/host/session.py +++ b/src/solprop/backends/host/session.py @@ -189,36 +189,18 @@ def __init__( # -- protocol ------------------------------------------------------------------- @property - def driver(self) -> HostStepping: - """Return the reference backend, whose facts the service loop records. - - Returns - ------- - solprop.backends.protocol.HostStepping - The backend, through the host stepping seam. - """ + def driver(self) -> HostStepping: # numpydoc ignore=RT01 + """Reference backend, whose facts the service loop records.""" return self._driver @property - def finished(self) -> bool: - """Return whether the session yielded its terminal event. - - Returns - ------- - bool - True once finished. - """ + def finished(self) -> bool: # numpydoc ignore=RT01 + """Whether the session yielded its terminal event.""" return self._ledger.finished @property - def closed(self) -> bool: - """Return whether the session was closed. - - Returns - ------- - bool - True once closed. - """ + def closed(self) -> bool: # numpydoc ignore=RT01 + """Whether the session was closed.""" return self._ledger.state is SessionState.CLOSED def status(self) -> SessionStatus: @@ -501,13 +483,10 @@ def march_export_refusal(self) -> str | None: return self._driver.march_export_refusal() @property - def march_refusal(self) -> str | None: - """Return why the start's method history could not be installed. + def march_refusal(self) -> str | None: # numpydoc ignore=RT01 + """Why the start's method history could not be installed. - Returns - ------- - str or None - The reason, or ``None`` when it was installed or the start carried none. + The reason, or ``None`` when it was installed or the start carried none. """ return self._march_refusal @@ -585,13 +564,7 @@ def _attach_monitor(self) -> None: self._driver.attach_monitor(self._monitor) def _account(self, summary: StepSummary) -> None: - """Advance the committed position and the state generation. - - Parameters - ---------- - summary : solprop.backends.protocol.StepSummary - What the propagation did. - """ + """Advance the committed position and the state generation.""" self._stepped = self._stepped or summary.n_steps > summary.n_rejected self._z = float(summary.z) self._generation += 1 @@ -600,32 +573,11 @@ def _drain(self, *, field: bool) -> Observation: """Observe the driver, draining its attempts and rows since the last drain. Every attempt is drained exactly once, so the status counts them here. - - Parameters - ---------- - field : bool - Whether the field crosses too. - - Returns - ------- - solprop.backends.protocol.Observation - The observation. """ return self._counted(self._driver.observe(field=field)) def _counted(self, observed: Observation) -> Observation: - """Count a drained observation's attempts into the status. - - Parameters - ---------- - observed : solprop.backends.protocol.Observation - Attempts drained from the driver, once. - - Returns - ------- - solprop.backends.protocol.Observation - The same observation. - """ + """Count a drained observation's attempts into the status.""" self._attempts += observed.n_steps self._rejected += observed.n_rejected return observed @@ -637,11 +589,8 @@ def _chunk(self) -> tuple[BackendHistory, StepStats]: abandoned step are drained from the driver's record instead (:meth:`~solprop.backends.host.driver.HostDriver.drain_attempts`). - Returns - ------- - tuple - The attempts and the rows (without a batch axis), delivered by exactly one - event (:mod:`solprop.backends.events`). + - Returns: the attempts and the rows (without a batch axis), delivered by + exactly one event (:mod:`solprop.backends.events`). """ drained = ( self._counted(self._host.drain_attempts()) @@ -655,10 +604,7 @@ def _chunk(self) -> tuple[BackendHistory, StepStats]: def _at_endpoint(self) -> bool: """Return whether the driver stands at an accepted endpoint. - Returns - ------- - bool - False while a dense observation holds the state between endpoints. + - Returns: False while a dense observation holds the state between endpoints. """ # The starting position is no accepted endpoint of this session: an Adams # march holds no history there to snapshot. @@ -669,17 +615,7 @@ def _issue( ) -> EventBatch: """Issue a batch of events under a new receipt. - Parameters - ---------- - events : list - The events; tickets are bound to the receipt here. - outcome : solprop.backends.events.Outcome or None, optional - The terminal outcome, when the batch ends the session. - - Returns - ------- - solprop.backends.events.EventBatch - The batch. + - ``outcome``: the terminal outcome, when the batch ends the session. """ receipt = self._ledger.issue(outcome=outcome) return EventBatch(receipt, tuple(bind(event, receipt) for event in events)) @@ -687,32 +623,14 @@ def _issue( def _ticket(self) -> ExportTicket: """Return an unbound ticket for the current committed state. - Returns - ------- - solprop.backends.events.ExportTicket - The ticket, its receipt filled in when the batch is issued. + - Returns: the ticket, its receipt filled in when the batch is issued. """ return ExportTicket(Receipt(self._ledger.session, 0), self._generation, self._z) def _observation( self, kind: ObservationKind, plane: int, summary: StepSummary ) -> EventBatch: - """Observe the driver field-free and report it. - - Parameters - ---------- - kind : solprop.backends.events.ObservationKind - Why it was observed. - plane : int - The save plane it answers. - summary : solprop.backends.protocol.StepSummary - The propagation that reached it. - - Returns - ------- - solprop.backends.events.EventBatch - One observation. - """ + """Observe the driver field-free and report it.""" history, rows = self._chunk() self._fields.release() try: @@ -737,11 +655,6 @@ def _snapshot(self) -> EventBatch: It delivers the attempts and rows since the previous event; the consumer joins them to those it holds since the previous observation for the running checkpoint, and keeps them for the next plane (SEP 47 section 5.5). - - Returns - ------- - solprop.backends.events.EventBatch - The snapshot request. """ history, rows = self._chunk() self._host.clear_halt() @@ -760,13 +673,7 @@ def _snapshot(self) -> EventBatch: ) def _complete(self) -> EventBatch: - """Run the final checks and report completion. - - Returns - ------- - solprop.backends.events.EventBatch - The terminal event. - """ + """Run the final checks and report completion.""" chunk = self._chunk() try: if self._monitor is not None and self._attached: @@ -795,20 +702,9 @@ def _terminal( ) -> EventBatch: """Report a stop or failure at the last committed position. - Parameters - ---------- - outcome : solprop.backends.events.Outcome - Stopped or failed. - error : BaseException - The primary failure or the stop request. - chunk : tuple or None, optional - The attempts and rows since the previous event, when already drained for - an event the failure replaced; drained here otherwise. - - Returns - ------- - solprop.backends.events.EventBatch - The terminal event, with a ticket when the state is valid. + - ``chunk``: the attempts and rows since the previous event, when already + drained for an event the failure replaced; drained here otherwise. + - Returns: the terminal event, with a ticket when the state is valid. """ poisoned = self._host.in_flight is not None history, rows = self._chunk() if chunk is None else chunk @@ -867,14 +763,8 @@ def __init__( self._closed = False @property - def backend(self) -> str: - """Return the registered name of the backend that prepared it. - - Returns - ------- - str - The backend's registered name. - """ + def backend(self) -> str: # numpydoc ignore=RT01 + """Registered name of the backend that prepared it.""" return self._driver.name def start( diff --git a/src/solprop/backends/low_storage.py b/src/solprop/backends/low_storage.py index 17bfb3e77..5096097bc 100644 --- a/src/solprop/backends/low_storage.py +++ b/src/solprop/backends/low_storage.py @@ -386,19 +386,11 @@ def step(self, z: float, dz: float) -> float: def _flows(self, z: float, dz: float) -> tuple[_State | None, _State | None]: """Read the step's bundles and return the flows held for it, ``P`` and ``Q``. - Parameters - ---------- - z : float - Where the step starts, m. - dz : float - The step, m, positive. - - Returns - ------- - tuple - ``P`` and ``Q`` when they are held for the step -- the table on a constant - operator, the pair on a varying one -- or ``None`` each when recomputed in - the finishes that apply them. + - ``z``: where the step starts, m. + - ``dz``: the step, m, positive. + - Returns: ``P`` and ``Q`` when they are held for the step -- the table on a + constant operator, the pair on a varying one -- or ``None`` each when + recomputed in the finishes that apply them. """ backend = self._backend midpoint, end = z + 0.5 * dz, z + dz @@ -435,15 +427,8 @@ def _flows(self, z: float, dz: float) -> tuple[_State | None, _State | None]: def _registers(self, state: _State) -> tuple[_State, _State]: """Return ``B`` and ``C``, allocated like the state at the first step. - Parameters - ---------- - state : numpy.ndarray - The state, whose shape and type the registers take. - - Returns - ------- - tuple of numpy.ndarray - The accumulator and the stage register. + - ``state``: the state, whose shape and type the registers take. + - Returns: the accumulator and the stage register. """ if self._accumulator is None or self._stage is None: self._accumulator = np.empty_like(state) @@ -452,34 +437,14 @@ def _registers(self, state: _State) -> tuple[_State, _State]: def _bytes(values: np.ndarray | None) -> int: - """Return an optional array's bytes, zero for ``None``. - - Parameters - ---------- - values : numpy.ndarray or None - The array. - - Returns - ------- - int - Its ``nbytes``, or 0. - """ + """Return an optional array's bytes, zero for ``None``.""" return 0 if values is None else int(values.nbytes) def _masked_norm(values: _State, mask: np.ndarray) -> float: """Return the 2-norm of an array over the norm mask's bins. - Parameters - ---------- - values : numpy.ndarray - An array of the state's layout. - mask : numpy.ndarray - The norm mask, boolean of shape ``(n_omega,)``. - - Returns - ------- - float - The norm, in the array's units. + - ``values``: an array of the state's layout. + - ``mask``: the norm mask, boolean of shape ``(n_omega,)``. """ return float(np.linalg.norm(values[..., mask])) diff --git a/src/solprop/backends/memory.py b/src/solprop/backends/memory.py index 1fb50500c..db0957b24 100644 --- a/src/solprop/backends/memory.py +++ b/src/solprop/backends/memory.py @@ -198,22 +198,8 @@ def _stepping_rows( ) -> dict[str, int]: """Return the march's own field-sized rows: stages, accumulators, registers, flows. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The translated problem. - stepper : solprop.translate.problem.StepperSettings - The run's stepper section (``RunRequest.stepper``), whose method decides the - rows. - field_bytes : int - One state's bytes. - tabulating : bool - Whether the propagators are tabulated. - - Returns - ------- - dict - Buffer name to bytes, every row named even when it is zero. + - ``field_bytes``: one state's bytes. + - Returns: buffer name to bytes, every row named even when it is zero. """ if isinstance(stepper.integrator, LowStorageRungeKutta): return _low_storage_rows(problem, stepper, field_bytes) @@ -238,20 +224,8 @@ def _low_storage_rows( ``exp(h D / 2)`` on a constant operator, a varying one's ``P`` and ``Q`` formed once per step, nothing when recomputing. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The translated problem. - stepper : solprop.translate.problem.StepperSettings - The run's stepper section (``RunRequest.stepper``), whose method decides the - rows. - field_bytes : int - One state's bytes. - - Returns - ------- - dict - Buffer name to bytes, every row of :func:`_stepping_rows` named. + - ``field_bytes``: one state's bytes. + - Returns: buffer name to bytes, every row of :func:`_stepping_rows` named. """ return { "stages": 0, @@ -505,14 +479,6 @@ def z_table_slots(stepper: StepperSettings) -> int: def _distinct_fractions(stepper: StepperSettings) -> int: """Return the tableau's distinct stage fractions, the step's end included. - Parameters - ---------- - stepper : solprop.translate.problem.StepperSettings - The run's stepper section. - - Returns - ------- - int - The count, positive. + - Returns: the count, positive. """ return int(np.unique(np.append(stepper.tableau.nodes, 1.0)).size) diff --git a/src/solprop/backends/multistep.py b/src/solprop/backends/multistep.py index 62a7cf88d..33ed23ab8 100644 --- a/src/solprop/backends/multistep.py +++ b/src/solprop/backends/multistep.py @@ -230,13 +230,10 @@ def from_record(cls, record: Mapping[str, object]) -> "CallLedger": return ledger @property - def total(self) -> int: - """Return every charged source evaluation. + def total(self) -> int: # numpydoc ignore=RT01 + """Every charged source evaluation. - Returns - ------- - int - Startup, predictor and corrected evaluations together. + Startup, predictor and corrected evaluations together. """ return self.startup + self.predictor + self.corrected @@ -286,24 +283,7 @@ def budget_error( def _decoded_orders(runs: object, steps: int) -> list[int]: """Return the order of every accepted step from its run-length record. - Parameters - ---------- - runs : object - The record's ``orders``: ``[order, count]`` pairs of positive integers. - steps : int - The record's accepted steps, each of which recorded one order. - - Returns - ------- - list of int - The orders, one per accepted step. - - Raises - ------ - BackendError - If ``runs`` is not a list of such pairs, naming the flat list of orders an - earlier development build wrote under the same output schema, or if its - counts do not add up to `steps`. + - ``runs``: the record's ``orders``: ``[order, count]`` pairs of positive integers. """ if ( isinstance(runs, list) @@ -374,17 +354,7 @@ def _hermite(step: _DenseStep, theta: float) -> _State: The generic option (a2) of SEP 13 section 6.4: the two end states and the two end derivatives, all in the step's anchor frame, with local error ``O(h^4)``. - Parameters - ---------- - step : _DenseStep - The accepted step. - theta : float - The observation as a fraction of the step, in ``[0, 1]``. - - Returns - ------- - numpy.ndarray - The interaction variable at `theta`, in the step's anchor frame. + - ``theta``: the observation as a fraction of the step, in ``[0, 1]``. """ theta2 = theta * theta theta3 = theta2 * theta @@ -639,19 +609,8 @@ def _product( ) -> _State: """Return the scalar product off the block and the matrix product on it. - Parameters - ---------- - columns : numpy.ndarray - The block's matrices, ``(n_modes, n_modes, n_bins)``. - scalar : numpy.ndarray - The diagonal factor, in the state's layout. - values : numpy.ndarray - The array, in the state's layout. - - Returns - ------- - numpy.ndarray - The product. + - ``scalar``: the diagonal factor, in the state's layout. + - ``values``: the array, in the state's layout. """ out = scalar * values selected = values[..., self.active] @@ -667,17 +626,7 @@ def _product( def apply(self, k: int, values: _State) -> _State: """Return ``M_k`` applied to a state-shaped array. - Parameters - ---------- - k : int - The moment. - values : numpy.ndarray - The array, in the state's layout. - - Returns - ------- - numpy.ndarray - ``M_k values``. + - ``values``: the array, in the state's layout. """ columns = self._columns return self._product(columns[k], self.scalar[k], values) @@ -685,15 +634,7 @@ def apply(self, k: int, values: _State) -> _State: def flow(self, values: _State) -> _State: """Return the step's flow ``exp(h B)`` applied to a state-shaped array. - Parameters - ---------- - values : numpy.ndarray - The array, in the state's layout. - - Returns - ------- - numpy.ndarray - ``exp(h B) values``. + - ``values``: the array, in the state's layout. """ columns = self._flow_columns return self._product(columns, self.flow_scalar, values) @@ -704,17 +645,8 @@ def _through( ) -> Complex[np.ndarray, "n_bins n_modes_out n_modes_in"]: """Return ``W diag(values) Y`` per bin, for an eigenbasis pair ``(W, Y)``. - Parameters - ---------- - pair : tuple of numpy.ndarray - ``W`` and ``Y``, shape ``(n_bins, n_modes, n_modes)``. - values : numpy.ndarray - The function's values at the eigenvalues, shape ``(n_bins, n_modes)``. - - Returns - ------- - numpy.ndarray - The matrices, shape ``(n_bins, n_modes, n_modes)``. + - ``pair``: ``W`` and ``Y``, shape ``(n_bins, n_modes, n_modes)``. + - ``values``: the function's values at the eigenvalues, shape ``(n_bins, n_modes)``. """ left, right = pair return (left * values[:, np.newaxis, :]) @ right @@ -730,18 +662,8 @@ def _eigen_functions( # noqa: PLR0917 - called through a reference ) -> None: """Write the flow and moments of the bins on an eigenbasis route, in NumPy. - Parameters - ---------- - shift : numpy.ndarray - ``Delta Phi`` on each of the block's bins. - dz : float - The step, m. - count : int - The moments formed. - basis : tuple of numpy.ndarray - ``(W, Y, lambda, route)`` of :attr:`CoupledBlock.basis`. - matrices, flow_matrix : numpy.ndarray - Written on the route's bins. + - ``dz``: the step, m. + - ``basis``: ``(W, Y, lambda, route)`` of :attr:`CoupledBlock.basis`. """ left, right, eigenvalues, route = basis if not np.any(route): @@ -893,36 +815,14 @@ def coupled(self) -> "CoupledExponentialKernels": def _times_moment(self, moments: Any, k: int, values: _State) -> _State: """Return ``M_k values``: elementwise here, through a block in a subclass. - Parameters - ---------- - moments : numpy.ndarray - The trial's moments. - k : int - The moment. - values : numpy.ndarray - The array, in the state's layout. - - Returns - ------- - numpy.ndarray - The product. + - ``values``: the array, in the state's layout. """ return moments[k] * values def _times_flow(self, flow: Any, values: _State) -> _State: """Return the step's flow applied to an array, elementwise here. - Parameters - ---------- - flow : numpy.ndarray - The trial's flow. - values : numpy.ndarray - The array, in the state's layout. - - Returns - ------- - numpy.ndarray - The product. + - ``values``: the array, in the state's layout. """ return flow * values @@ -1043,21 +943,7 @@ def _applied( ) -> _State: """Return :meth:`apply`'s increment; shared with the coupled passes. - Parameters - ---------- - moments : numpy.ndarray or _MatrixMoments - The trial's moments. - coefficients : numpy.ndarray - ``c_jk``. - values : list of numpy.ndarray - The samples. - dz : float - The step, m. - - Returns - ------- - numpy.ndarray - The increment. + - ``dz``: the step, m. """ total = np.zeros_like(values[0]) for k in range(coefficients.shape[1]): @@ -1125,27 +1011,7 @@ def _predicted( # noqa: PLR0917 - buffers are a kernel's parameters ) -> _State: """Return :meth:`predict`'s field; shared with the coupled passes. - Parameters - ---------- - state : numpy.ndarray - The accepted state. - flow, moments : numpy.ndarray or _MatrixMoments - The trial's flow and moments. - store : numpy.ndarray - The history rows. - slots : numpy.ndarray - The rows combined. - coefficients : numpy.ndarray - The predictor's coefficients. - dz : float - The step, m. - factors : numpy.ndarray or None, optional - The rows' carrier-frame rotations, in the slots' order. - - Returns - ------- - numpy.ndarray - The predicted field. + - ``dz``: the step, m. """ values = _rows_read(store, slots, factors) return self._times_flow(flow, state) + self._applied( @@ -1259,30 +1125,12 @@ def _corrected( # noqa: PLR0917 - buffers are a kernel's parameters ) -> tuple[_State, np.ndarray]: """Return :meth:`correct`'s field and norms; shared with the coupled passes. - Parameters - ---------- - state : numpy.ndarray - The accepted state. - flow, moments : numpy.ndarray or _MatrixMoments - The trial's flow and moments. - source : numpy.ndarray - The new sample. - history : tuple - The store, the rows after the source and their carrier-frame rotations - (``None`` for none). - tables : tuple of numpy.ndarray - The corrector's coefficients and the defect rows. - dz : float - The step, m. - scoring : tuple of numpy.ndarray - The in-band mask and the norm's scale per bin. - extra : tuple or None - A varying operator's compensation terms. - - Returns - ------- - tuple - The corrected field and one norm per row. + - ``history``: the store, the rows after the source and their carrier-frame + rotations (``None`` for none). + - ``tables``: the corrector's coefficients and the defect rows. + - ``dz``: the step, m. + - ``scoring``: the in-band mask and the norm's scale per bin. + - Returns: the corrected field and one norm per row. """ store, slots, factors = history corrector, rows = tables @@ -1355,36 +1203,14 @@ def coupled_moments( def _times_moment(self, moments: Any, k: int, values: _State) -> _State: """Return ``M_k values`` through the trial's block. - Parameters - ---------- - moments : _MatrixMoments - The coupled trial's moments. - k : int - The moment. - values : numpy.ndarray - The array, in the state's layout. - - Returns - ------- - numpy.ndarray - The product. + - ``values``: the array, in the state's layout. """ return cast("_MatrixMoments", moments).apply(k, values) def _times_flow(self, flow: Any, values: _State) -> _State: """Return the coupled trial's flow applied to an array. - Parameters - ---------- - flow : _MatrixMoments - The coupled trial's flow and moments. - values : numpy.ndarray - The array, in the state's layout. - - Returns - ------- - numpy.ndarray - The product. + - ``values``: the array, in the state's layout. """ return cast("_MatrixMoments", flow).flow(values) @@ -1626,14 +1452,8 @@ def add_endpoint( ) -> None: """Add the new sample's compensation at the predicted state; no transport. - Parameters - ---------- - rate : numpy.ndarray - ``D(z_n + h)`` from the step's side of the end. - state : numpy.ndarray - The predicted state the source was evaluated at. - residual : numpy.ndarray or None, optional - A frozen-residual flow's ``R_s(z_n + h)`` applied to that state. + - ``rate``: ``D(z_n + h)`` from the step's side of the end. + - ``residual``: a frozen-residual flow's ``R_s(z_n + h)`` applied to `state`. """ self.endpoint_rate = rate self.endpoint = (rate - self.exponent / self.dz) * state @@ -1648,11 +1468,6 @@ def rotate(self, shift: _Shift) -> None: the ordinary Adams weights. The untransported witness integrates its terms against the trial's moments, which are the shifted ones, so its history terms take the samples' factors ``exp(sigma (1 - theta_j))``; the endpoint's is one. - - Parameters - ---------- - shift : numpy.ndarray - The trial's ``sigma``. """ if self.treatment == "transported": return @@ -1666,19 +1481,8 @@ def rotate(self, shift: _Shift) -> None: def _sum(self, nodes: list[float], first: _State | None, count: int) -> _State: """Return ``h`` times the treatment's integral over the given samples. - Parameters - ---------- - nodes : list of float - The formula's nodes, the endpoint's first when `first` is given. - first : numpy.ndarray or None - The endpoint sample's compensation, or ``None`` for a predictor. - count : int - How many carried history rows the formula reads. - - Returns - ------- - numpy.ndarray - The increment. + - ``nodes``: the formula's nodes, the endpoint's first when `first` is given. + - ``first``: the endpoint sample's compensation, or ``None`` for a predictor. """ rows = self.carried[:count] if self.treatment == "transported": @@ -1702,48 +1506,15 @@ def _sum(self, nodes: list[float], first: _State | None, count: int) -> _State: ) def predictor(self, order: int) -> _State: - """Return the predictor's compensation over the ``order`` newest samples. - - Parameters - ---------- - order : int - The predictor's order. - - Returns - ------- - numpy.ndarray - The increment. - """ + """Return the predictor's compensation over the ``order`` newest samples.""" return self._sum(self.nodes[:order], None, order) def corrector(self, order: int) -> _State: - """Return the corrector's, through the endpoint sample. - - Parameters - ---------- - order : int - The step's order; the corrector has one node more. - - Returns - ------- - numpy.ndarray - The increment. - """ + """Return the corrector's, through the endpoint sample.""" return self._sum([1.0, *self.nodes[:order]], self.endpoint, order) def defects(self, orders: list[int]) -> list[_State]: - """Return each scored order's corrector less predictor compensation. - - Parameters - ---------- - orders : list of int - The orders scored, the step's own first. - - Returns - ------- - list of numpy.ndarray - One defect term per order. - """ + """Return each scored order's corrector less predictor compensation.""" return [self.corrector(k) - self.predictor(k) for k in orders] def observation( @@ -1755,17 +1526,7 @@ def observation( the history's terms are formed again from the stored rows by the caller, rather than carried back by an inverse flow that a damped bin would overflow. - Parameters - ---------- - order : int - The accepted step's order. - history : list of numpy.ndarray - ``(D(z_j) - B_n) u_j`` of its ``order`` history samples, newest first. - - Returns - ------- - tuple - The nodes ``(1, theta_0, ...)`` and the matching compensations. + - Returns: the nodes ``(1, theta_0, ...)`` and the matching compensations. """ assert self.endpoint is not None return [1.0, *self.nodes[:order]], [self.endpoint.copy(), *history] @@ -1825,7 +1586,7 @@ class AdamsEngine(Protocol): removes its phase from the samples it reads.""" @property - def n_slots(self) -> int: + def n_slots(self) -> int: # numpydoc ignore=RT01 """The history store's rows.""" ... @@ -2123,14 +1884,8 @@ def reset(self) -> None: self._exponential_trial = None @property - def n_slots(self) -> int: - """The history store's rows: the method's order and two more. - - Returns - ------- - int - ``order + 2``. - """ + def n_slots(self) -> int: # numpydoc ignore=RT01 + """The history store's rows: the method's order and two more.""" return self._method.order + 2 # --- the accepted state, held across a dense observation -------------------------- @@ -2652,19 +2407,8 @@ def _history_terms( ) -> list[_State]: """Return ``(D(z_j) - B_n) u_j`` of history samples, untransported. - Parameters - ---------- - exponent : numpy.ndarray - ``x = h B_n``. - dz : float - The step, m. - slots : numpy.ndarray - The samples, newest first. - - Returns - ------- - list of numpy.ndarray - One compensation per sample. + - ``exponent``: ``x = h B_n``. + - ``dz``: the step, m. """ assert self._states is not None and self._rates is not None reference = exponent / dz @@ -2681,26 +2425,11 @@ def _compensation_terms( # noqa: PLR0917 - called positionally ) -> "_Compensation": """Return the trial's linear compensation of its history samples. - Parameters - ---------- - exponent : numpy.ndarray - ``x = h B_n``, the step's chord exponent. - moments : numpy.ndarray - The trial's moments, for the untransported witness. - nodes : list of float - The history's normalised positions, newest first. - slots : numpy.ndarray - The history rows the trial reads, newest first. - dz : float - The step, m. - block : CoupledBlock or None, optional - A coupled step's frozen block: the samples' residuals against it join - their compensation, carried by its forward flow. - - Returns - ------- - _Compensation - The samples' ``(D(z_j) - B_n) u_j`` and the trial's weights over them. + - ``exponent``: ``x = h B_n``, the step's chord exponent. + - ``nodes``: the history's normalised positions, newest first. + - ``dz``: the step, m. + - ``block``: a coupled step's frozen block: the samples' residuals against it + join their compensation, carried by its forward flow. """ assert self._states is not None and self._rates is not None assert self._exponential is not None @@ -2779,15 +2508,7 @@ def _observe_exponential(self, position: float) -> object: factor is a forward flow, so a strongly damped bin needs no guard; no source is evaluated and nothing enters the history. - Parameters - ---------- - position : float - The observation, m, inside the last accepted step. - - Returns - ------- - object - The masked lab-frame field at `position`, as the backend holds a state. + - ``position``: the observation, m, inside the last accepted step. """ step = self._exponential_dense assert step is not None @@ -2839,25 +2560,11 @@ def _trial_propagators( # noqa: PLR0917 - called positionally ) -> tuple[Any, Any]: """Return a trial's moments and flow, shifted by `shift` when it is given. - Parameters - ---------- - kernels : Any - The backend's exponential kernels, already the coupled ones for a block. - exponent : Any - The step's increment of Phi, the true linear exponent. - width : int - The number of moments, one more than the highest order scored. - block : CoupledBlock or None - The section's frozen block, or None off a coupled section. - dz : float - The step. - shift : numpy.ndarray or None - The carrier shift sigma per member, or None when no frame is active. - - Returns - ------- - tuple - The moments at the shifted exponent and the true flow exp(x). + - ``exponent``: the step's increment of Phi, the true linear exponent. + - ``width``: the number of moments, one more than the highest order scored. + - ``block``: the section's frozen block, or None off a coupled section. + - ``shift``: the carrier shift sigma per member, or None when no frame is active. + - Returns: the moments at the shifted exponent and the true flow exp(x). """ moments: Any flow: Any @@ -2889,19 +2596,8 @@ def _carrier_factors(nodes: list[float], count: int, shift: _Shift) -> _Factors: factor as they read it, so the store keeps the untransported samples and a rejection, a resume or a later step with its own shift reads them unchanged. - Parameters - ---------- - nodes : list of float - The history's normalised positions ``theta_j``, newest first. - count : int - The rows read. - shift : numpy.ndarray - The trial's ``sigma``. - - Returns - ------- - numpy.ndarray - One factor per row, ``(count, *shift.shape)``. + - ``nodes``: the history's normalised positions ``theta_j``, newest first. + - Returns: one factor per row, ``(count, *shift.shape)``. """ return np.stack([np.exp(shift * (1.0 - nodes[index])) for index in range(count)]) @@ -2913,19 +2609,8 @@ def _rows_read( ) -> list[_State]: """Return the history rows a pass combines, rotated when a carrier frame asks. - Parameters - ---------- - store : numpy.ndarray - The history rows. - slots : numpy.ndarray - The rows read, newest first. - factors : numpy.ndarray or None - Their rotations, in the slots' order; ``None`` for the rows themselves. - - Returns - ------- - list of numpy.ndarray - The rows; rotated ones are new arrays. + - ``factors``: their rotations, in the slots' order; ``None`` for the rows + themselves. """ if factors is None: return [store[slot] for slot in slots] @@ -2941,17 +2626,7 @@ def _divisor( See :func:`solprop.backends.host.general._norm_function` for why the state before the step is the scale. - Parameters - ---------- - backend : solprop.backends.host.driver.HostDriver - The backend, for the controller and the in-band mask. - state : numpy.ndarray - The accepted state before the step. - - Returns - ------- - numpy.ndarray - The scale of every bin, shape ``(n_omega,)``. + - Returns: the scale of every bin, shape ``(n_omega,)``. """ mask = backend._problem.grids.norm_mask controller = backend._norm_controller @@ -2972,20 +2647,9 @@ def _defect_rows( Over ``(source, newest, ...)``, order ``k``'s defect is its ``k + 1``-node corrector through the new source less its ``k``-node predictor. - Parameters - ---------- - kernels : AdamsKernels - The fused path's arithmetic, for its weights. - nodes : list of float - The history's normalised positions, newest first. - orders : list of int - The orders to score, the step's own first. - - Returns - ------- - tuple - The defect rows, shape ``(len(orders), 1 + max(orders))``, and the step's own - corrector weights, zero past its nodes. + - ``nodes``: the history's normalised positions, newest first. + - Returns: the defect rows, shape ``(len(orders), 1 + max(orders))``, and the step's + own corrector weights, zero past its nodes. """ width = 1 + max(orders) rows = np.zeros((len(orders), width)) @@ -3228,21 +2892,8 @@ def _exponential_rows( ``k``-node predictor, as for :func:`_defect_rows`; the weights are the Lagrange coefficients, which the trial's moments turn into exponential weights. - Parameters - ---------- - nodes : list of float - The history's normalised positions, newest first. - orders : list of int - The orders to score, the step's own first. - width : int - ``1 + max(orders)``: the samples and the moments the trial forms. - helpers : solprop._helpers.Helpers - The owning backend's implementation of the Adams basis. - - Returns - ------- - numpy.ndarray - The defect rows, shape ``(len(orders), width, width)``. + - ``nodes``: the history's normalised positions, newest first. + - Returns: the defect rows, shape ``(len(orders), width, width)``. """ rows = np.zeros((len(orders), width, width)) for index, order in enumerate(orders): @@ -3261,23 +2912,9 @@ def _exponential_corrector( ) -> np.ndarray: """Return the step's own corrector's Lagrange coefficients, padded to `width`. - Parameters - ---------- - nodes : list of float - The history's normalised positions, newest first. - order : int - The step's order; the corrector runs through the new source and the - ``order`` newest samples. - width : int - ``1 + max(orders)``: the samples and the moments the trial forms. - helpers : solprop._helpers.Helpers - The owning backend's implementation of the Adams basis. - - Returns - ------- - numpy.ndarray - The coefficients over ``X = (source, newest, ...)``, shape ``(width, width)``, - zero past its nodes. + - ``nodes``: the history's normalised positions, newest first. + - Returns: the coefficients over ``X = (source, newest, ...)``, shape ``(width, + width)``, zero past its nodes. """ corrector = np.zeros((width, width)) corrector[: order + 1, : order + 1] = lagrange_coefficients( @@ -3289,19 +2926,7 @@ def _exponential_corrector( def _combine(weights: np.ndarray, values: list[_State], dz: float) -> _State: """Return ``dz * sum_j w_j g_j``. - Parameters - ---------- - weights : numpy.ndarray - The integrated weights. - values : list - The derivatives, one per weight. - dz : float - The step, m. - - Returns - ------- - numpy.ndarray - The increment. + - ``dz``: the step, m. """ total = np.zeros_like(values[0]) for weight, value in zip(weights, values, strict=True): diff --git a/src/solprop/backends/node_chunk.py b/src/solprop/backends/node_chunk.py index 4bb018e2e..10bd881ea 100644 --- a/src/solprop/backends/node_chunk.py +++ b/src/solprop/backends/node_chunk.py @@ -145,13 +145,10 @@ class NodeChunkPlan: requested: int | None = None @property - def automatic(self) -> bool: - """Return whether no budget or chunk was given, so a machine rule may apply. + def automatic(self) -> bool: # numpydoc ignore=RT01 + """Whether no budget or chunk was given, so a machine rule may apply. - Returns - ------- - bool - ``True`` when `source` is ``"default"``. + ``True`` when `source` is ``"default"``. """ return self.source == "default" @@ -247,17 +244,7 @@ def plan_node_chunk( def _host_chunk(problem: PropagationProblem, budget: int) -> int: """Return the host rule's chunk within `budget`: the base chunk, then admissions. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The validated problem. - budget : int - Node workspace budget, bytes, positive. - - Returns - ------- - int - The chunk. + - ``budget``: node workspace budget, bytes, positive. """ transform = problem.transform if transform.modal_tables is not None: @@ -279,26 +266,7 @@ def _host_chunk(problem: PropagationProblem, budget: int) -> int: def _base_chunk(transform: Transform, n_pol: int, n_to: int, budget: int) -> int: """Return the chunk a node's temporal columns admit, before the responses. - Parameters - ---------- - transform : solprop.translate.modal.Transform - The transform. - n_pol : int - State polarisation components. - n_to : int - Fine time samples. - budget : int - Node workspace budget, bytes. - - Returns - ------- - int - Between 1 and the node count. - - Raises - ------ - solprop.exceptions.BackendError - If a Cartesian or planar column does not fit. + - ``budget``: node workspace budget, bytes. """ geometry = transform.geometry if transform.mode_averaged: @@ -317,18 +285,7 @@ def _base_chunk(transform: Transform, n_pol: int, n_to: int, budget: int) -> int def _cartesian(transform: Transform) -> bool: - """Return whether the transform's nodes are Cartesian or planar columns. - - Parameters - ---------- - transform : solprop.translate.modal.Transform - The transform. - - Returns - ------- - bool - ``True`` on the ``"free"`` and ``"slab"`` kinds. - """ + """Return whether the transform's nodes are Cartesian or planar columns.""" return transform.geometry is not None and transform.geometry.kind in ( "free", "slab", @@ -338,15 +295,7 @@ def _cartesian(transform: Transform) -> bool: def _row_nodes(transform: Transform) -> int: """Return the physical nodes of one planar transverse row, one elsewhere. - Parameters - ---------- - transform : solprop.translate.modal.Transform - The transform. - - Returns - ------- - int - ``n_x`` on the planar kind, else one. + - Returns: ``n_x`` on the planar kind, else one. """ geometry = transform.geometry return geometry.shape[0] if geometry is not None and geometry.kind == "slab" else 1 @@ -355,23 +304,9 @@ def _row_nodes(transform: Transform) -> int: def _even_chunk(count: int, limit: int) -> int: """Return the smallest chunk that covers `count` items in as few passes as `limit`. - Parameters - ---------- - count : int - Items to cover (positive). - limit : int - Largest admitted chunk (positive). - - Returns - ------- - int - ``ceil(count / ceil(count / limit))``, at most `limit` and `count`. - - Examples - -------- - >>> from solprop.backends.node_chunk import _even_chunk - >>> _even_chunk(33, 32), _even_chunk(33, 33), _even_chunk(100, 30) - (17, 33, 25) + - ``count``: items to cover (positive). + - ``limit``: largest admitted chunk (positive). + - Returns: ``ceil(count / ceil(count / limit))``, at most `limit` and `count`. """ passes = -(-count // limit) return -(-count // passes) @@ -386,18 +321,6 @@ def _balanced_chunk(chunk: int, transform: Transform) -> int: of one 33-node pass. The same number of passes of near-equal size (17 and 16) never exceeds the admitted chunk, so every budget still holds. A planar chunk of whole transverse rows stays whole rows; one shorter than a row is kept. - - Parameters - ---------- - chunk : int - The admitted chunk. - transform : solprop.translate.modal.Transform - The transform. - - Returns - ------- - int - The smallest chunk that needs no more passes. """ row_nodes = _row_nodes(transform) rows = transform.n_nodes // row_nodes @@ -412,29 +335,10 @@ def _envelope_chunk( ) -> int: """Admit envelope plasma's current streams and histories into the chunk. - Parameters - ---------- - chunk : int - The chunk admitted so far. - transform : solprop.translate.modal.Transform - The transform. - n_pol : int - State polarisation components. - n_to : int - Fine temporal samples per node. - budget : int - Node workspace budget in bytes. - - Returns - ------- - int - The bounded chunk. Modal projected arrays are accounted separately by the - backends' ledgers because they do not scale with the chunk. + Modal projected arrays are accounted separately by the backends' ledgers, because + they do not scale with the chunk. - Raises - ------ - solprop.exceptions.BackendError - If one complete node history and its sources do not fit. + - ``budget``: node workspace budget in bytes. """ components = transform.n_pol * n_pol free = _cartesian(transform) @@ -464,30 +368,7 @@ def _held_current_chunk( # noqa: PLR0917 - called positionally transform layout (radial), the band scan, Raman and dispersive buffers together, so a mixed response cannot consume the same budget twice. - Parameters - ---------- - chunk : int - The chunk admitted so far. - transform : solprop.translate.modal.Transform - The transform. - responses : solprop.translate.responses.CompiledResponses - Compiled sources; the chunk is unchanged without field plasma. - n_pol : int - State components. - n_to : int - Fine temporal samples. - budget : int - Node workspace budget, bytes. - - Returns - ------- - int - The chunk with all node-dependent source buffers admitted. - - Raises - ------ - solprop.exceptions.BackendError - If one complete node, or planar transverse row, does not fit. + - ``budget``: node workspace budget, bytes. """ if not responses.plasma_channels: return chunk @@ -554,30 +435,7 @@ def _raman_chunk( # noqa: PLR0917 - called positionally ) -> int: """Admit complete Raman histories, including combined envelope plasma storage. - Parameters - ---------- - chunk : int - The chunk admitted so far. - transform : solprop.translate.modal.Transform - The transform. - responses : solprop.translate.responses.CompiledResponses - Declared convolution channels and optional plasma streams. - n_pol : int - State polarisation components, one or two. - n_to : int - Fine temporal samples per physical node. - budget : int - Node workspace budget in bytes. - - Returns - ------- - int - A complete-node chunk within the workspace budget. - - Raises - ------ - solprop.exceptions.BackendError - If one complete Raman node history does not fit. + - ``budget``: node workspace budget in bytes. """ if not responses.raman_terms: return chunk @@ -656,30 +514,7 @@ def _dispersive_chunk( # noqa: PLR0917 - called positionally ) -> int: """Admit the dispersive response's node buffers into the chunk's budget. - Parameters - ---------- - chunk : int - The chunk admitted so far. - transform : solprop.translate.modal.Transform - The transform; a general modal guide never reaches here. - responses : solprop.translate.responses.CompiledResponses - The compiled responses; nothing changes without the dispersive leaf. - n_pol : int - State polarisation components, one or two. - n_to : int - Fine temporal samples per node. - budget : int - Node workspace budget in bytes. - - Returns - ------- - int - A chunk whose every buffer fits the budget. - - Raises - ------ - solprop.exceptions.BackendError - If one node and the response's buffers do not fit. + - ``budget``: node workspace budget in bytes. """ if responses.dispersive_kerr is None: return chunk diff --git a/src/solprop/backends/numpy_ref/admission.py b/src/solprop/backends/numpy_ref/admission.py index 7735848cb..15743bb2c 100644 --- a/src/solprop/backends/numpy_ref/admission.py +++ b/src/solprop/backends/numpy_ref/admission.py @@ -222,22 +222,7 @@ def _refuse_flow_representation( backend: str, flow_methods: Collection[SectionMethod], ) -> None: - """Refuse a coupled flow whose representation the backend does not execute. - - Parameters - ---------- - flow : solprop.translate.coupled.CoupledFlowTables - The leaf. - backend : str - The registry name each refusal names. - flow_methods : collection of str - The section methods the backend executes. - - Raises - ------ - BackendError - Naming the method, the representation, the section count or the connection. - """ + """Refuse a coupled flow whose representation the backend does not execute.""" methods = set(flow.section_methods) frozen = methods <= {"frozen_residual", "cf4"} if flow.section_methods != ("constant_schur",) and not frozen: @@ -323,17 +308,7 @@ def _refuse_flow_problem(problem: PropagationProblem, *, backend: str) -> None: ``TM_0m`` or ``EH`` set synthesises E_x and E_y): the synthesis is read by the responses alone, never by the flow, so it is not refused. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem, whose coupled flow is not ``None``. - backend : str - The registry name each refusal names. - - Raises - ------ - BackendError - Naming what the problem carries beside the flow. + - ``problem``: the problem, whose coupled flow is not ``None``. """ _, n_pol, _ = problem.shape if n_pol != 1: @@ -420,15 +395,7 @@ def _mode_independent(linear: LinearOperator) -> bool: constant array, the expansion's fixed terms, or the table's coefficients -- because a varying guide takes whichever of them its declared parameters allow. - Parameters - ---------- - linear : solprop.translate.linop.LinearOperator - Section 3 of the contract. - - Returns - ------- - bool - True when every channel of every term carries the same values. + - Returns: True when every channel of every term carries the same values. """ held: list[np.ndarray] = [] if linear.phi_constant is not None: @@ -467,20 +434,7 @@ def _refuse_general_metric( declares that row, and a backend does not rely on a caller having been careful (the same rule the rest of this admission path follows). - Parameters - ---------- - tables : solprop.translate.modal_tables.ModalTransformTables - The reference the metric must describe. - metric : solprop.translate.coupled.ModalMetricTables - The admitted identity or dense metric. - backend : str - The registry name the refusal names. - - Raises - ------ - BackendError - If a dense metric is on another basis than the reference, or is further from - orthonormal than ``numpy_ref`` will call the identity. + - ``tables``: the reference the metric must describe. """ block = metric.dense if block is None: @@ -538,21 +492,6 @@ def _refuse_modal_route( channels, the Raman terms and the spectral responses, so any of them would be carried in the record and silently not propagated. Every backend refuses it through this one list. - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem to check. - backend : str - The registry name each refusal names. - moving_maps : bool, optional - Whether the backend executes a moving guide's maps - (:func:`refuse_general_route`). - - Raises - ------ - BackendError - Naming what is unsupported. """ tables = problem.transform.modal_tables metric = problem.linear.modal_metric @@ -656,22 +595,6 @@ def _refuse_moving_maps( on paper -- its Gauss-node exponentials read ``flow_weights`` and the source reads ``transform_weights`` at stage positions -- but is not built or measured on this route, so it is refused rather than run. - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem, whose maps carry more than one coefficient along z. - tables : solprop.translate.modal_tables.ModalTransformTables - Its modal tables. - backend : str - The registry name each refusal names. - moving_maps : bool - Whether the backend executes moving maps at all. - - Raises - ------ - BackendError - Naming what is not executed. """ assert tables.synthesis is not None intervals, degrees = (int(one) for one in tables.synthesis.shape[:2]) @@ -707,20 +630,7 @@ def _refuse_moving_maps( def _refuse_legacy_responses(problem: PropagationProblem, *, backend: str) -> None: - """Refuse a legacy response a general modal guide would carry and not propagate. - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - A problem carrying ``transform.modal_tables``. - backend : str - The registry name the refusal names. - - Raises - ------ - BackendError - Naming every legacy response the problem carries. - """ + """Refuse a legacy response a general modal guide would carry and not propagate.""" responses = problem.responses legacy = [ name diff --git a/src/solprop/backends/numpy_ref/backend.py b/src/solprop/backends/numpy_ref/backend.py index 179f66c9d..8f51dc1aa 100644 --- a/src/solprop/backends/numpy_ref/backend.py +++ b/src/solprop/backends/numpy_ref/backend.py @@ -356,16 +356,6 @@ def _propagator( The single-precision path rounds the binary64 exponential once (SEP 33 section 4.2); here it is the exponential itself. - - Parameters - ---------- - exponent : numpy.ndarray - ``dPhi`` or its negative, complex128. - - Returns - ------- - numpy.ndarray - ``exp(exponent)``. """ return np.exp(exponent) @@ -377,17 +367,7 @@ def _envelope_sources( ]: """Return the envelope-grid plasma's windowed drive and loss at some nodes. - Parameters - ---------- - field_t : numpy.ndarray - The field at the nodes, V/m. - bundle : solprop.translate.stage.StageScalars - The stage's host scalars. - - Returns - ------- - tuple of numpy.ndarray - :func:`solprop.backends.numpy_ref.envelope_plasma.envelope_sources`. + - ``field_t``: the field at the nodes, V/m. """ envelope = self._problem.responses.envelope_plasma assert envelope is not None @@ -406,17 +386,7 @@ def _envelope_populations( ) -> tuple[np.ndarray, np.ndarray]: """Return each envelope channel's final fraction and peak rate at the nodes. - Parameters - ---------- - nodes : numpy.ndarray - The physical envelope at the nodes, V/m. - initials : tuple of float - The channels' pre-ionised fractions here. - - Returns - ------- - tuple of numpy.ndarray - :func:`solprop.backends.numpy_ref.envelope_plasma.envelope_populations`. + - ``nodes``: the physical envelope at the nodes, V/m. """ envelope = self._problem.responses.envelope_plasma assert envelope is not None @@ -430,22 +400,7 @@ def _error_norm( state: Complex[np.ndarray, "n_batch n_modes n_pol n_omega"], advanced: Complex[np.ndarray, "n_batch n_modes n_pol n_omega"], ) -> float: - """Return the controller's scaled norm of a trial step's embedded difference. - - Parameters - ---------- - error : numpy.ndarray - The embedded difference carried to the end of the step. - state : numpy.ndarray - The state at the anchor. - advanced : numpy.ndarray - The trial state at the end of the step. - - Returns - ------- - float - :func:`solprop.backends.numpy_ref.passes.error_norm` of the three. - """ + """Return the controller's scaled norm of a trial step's embedded difference.""" return passes.error_norm( error, state, @@ -460,16 +415,6 @@ def _rounded( """Return a binary64 array a pass multiplies by, in the run's precision. The identity here; the single-precision path rounds it once (SEP 33). - - Parameters - ---------- - values : numpy.ndarray - A binary64 coefficient array evaluated on the host. - - Returns - ------- - numpy.ndarray - `values`. """ return values @@ -505,21 +450,8 @@ def _stacked_statistics(self, spec: StatsSpec) -> dict[str, np.ndarray]: ionisation rate, the on-axis intensity, the quadrature error -- and are formed here from the backend's own buffers. - Parameters - ---------- - spec : solprop.stats.StatsSpec - Which statistics and which windows. - - Returns - ------- - dict - Small host arrays keyed by statistic name, each with a leading axis of one. - - Raises - ------ - BackendError - If the backend is closed, no initial state was loaded, no grid was given at - construction, or a backend statistic needs something this run does not have. + - Returns: small host arrays keyed by statistic name, each with a leading axis + of one. """ state = self._require_state() grid = self._require_grid() @@ -658,17 +590,9 @@ def _backend_statistics( ) -> dict[str, np.ndarray]: """Share each fresh envelope population scan among the requested statistics. - Parameters - ---------- - names : tuple of str - Backend-owned statistic names. - state : numpy.ndarray - Accepted state at this plane with its leading axis of one. - - Returns - ------- - dict - Requested values with a leading axis of one and no retained field cache. + - ``state``: accepted state at this plane with its leading axis of one. + - Returns: requested values with a leading axis of one and no retained field + cache. """ population_names = ( tuple( @@ -741,17 +665,7 @@ def _backend_stat( ) -> np.ndarray: """Compute one backend-owned statistic over the batch. - Parameters - ---------- - name : str - The catalogue name. - state : numpy.ndarray - The state at this plane. - - Returns - ------- - numpy.ndarray - The value with a leading axis of one. + - Returns: the value with a leading axis of one. """ if name == "quadrature_error": return np.full(state.shape[0], self._alarm) @@ -779,20 +693,9 @@ def _species_statistics( ) -> dict[str, np.ndarray]: """Evaluate independent populations on the actual nonlinear nodes. - Parameters - ---------- - field : numpy.ndarray - One batch member at this plane, shape (n_modes, n_pol, n_omega). - scans : dict or None, optional - Where to leave this observation's channel fractions and peak rates at - every node, keyed by `member`, for the on-axis statistics to reuse. - member : int, optional - The batch member, the key into `scans`. - - Returns - ------- - dict - The five species statistics and the two total plasma statistics. + - ``scans``: where to leave this observation's channel fractions and peak rates + at every node, keyed by `member`, for the on-axis statistics to reuse. + - Returns: the five species statistics and the two total plasma statistics. """ responses = self._problem.responses transform, grids = self._problem.transform, self._problem.grids @@ -851,20 +754,7 @@ def _integration_diagnostics( node and field plasma channel (:data:`solprop.stats.PLASMA_INTEGRATION_STATISTICS`). - Parameters - ---------- - field : numpy.ndarray - One batch member's state, shape ``(n_modes, n_pol, n_omega)``. - - Returns - ------- - dict - The three statistics, each zero-dimensional and dimensionless. - - Raises - ------ - BackendError - If the problem carries no field plasma channel. + - Returns: the three statistics, each zero-dimensional and dimensionless. """ responses = self._problem.responses channels = responses.plasma_channels @@ -902,15 +792,7 @@ def _plasma_nodes( The same synthesis as :meth:`_ionisation`: the physical nodes of a separable geometry, chunk by chunk, or the modal quadrature nodes at once. - Parameters - ---------- - field : numpy.ndarray - One batch member's state, shape ``(n_modes, n_pol, n_omega)``. - - Yields - ------ - numpy.ndarray - Node fields on the fine time grid, V/m. + - Yields: node fields on the fine time grid, V/m. """ grids = self._problem.grids if self._problem.linear.separable is None: @@ -950,21 +832,8 @@ def _ionisation( The electron density is ``density(z)`` times the fraction at the **end** of the window: what the pulse leaves behind, not what it reaches in passing. - Parameters - ---------- - field : numpy.ndarray - One batch member's state, shape ``(n_modes, n_pol, n_omega)``. - - Returns - ------- - tuple of numpy.ndarray - The electron density in m^-3 and the peak rate in s^-1, both - zero-dimensional. - - Raises - ------ - BackendError - If the problem declares no plasma, so there is no rate to integrate. + - Returns: the electron density in m^-3 and the peak rate in s^-1, both + zero-dimensional. """ responses = self._problem.responses plasma = responses.plasma_coefficients @@ -1044,21 +913,6 @@ def _peak_intensity( that factor. The on-axis matrix is built once and rescaled with the taper (:meth:`_on_axis_synthesis`), which is what makes this statistic cheap enough for the between-planes channel. - - Parameters - ---------- - field : numpy.ndarray - One batch member's state, shape ``(n_modes, n_pol, n_omega)``. - - Returns - ------- - numpy.ndarray - The peak intensity, zero-dimensional. - - Raises - ------ - BackendError - If the backend was built without the mode set the axis needs. """ if self._modes is None: msg = ( @@ -1080,17 +934,9 @@ def _nodes_at( ) -> Num[np.ndarray, "n_nodes n_components n_to"]: """Return the physical field at every quadrature node on the fine time grid. - Parameters - ---------- - field : numpy.ndarray - One batch member's state, shape ``(n_modes, n_pol, n_omega)``. - - Returns - ------- - numpy.ndarray - The field in V/m, shape ``(n_nodes, n_components, n_to)``. Here - ``n_components = transform.n_pol * n_pol`` counts physical components, - independently of the state's polarisation axis. + - Returns: the field in V/m, shape ``(n_nodes, n_components, n_to)``. Here + ``n_components = transform.n_pol * n_pol`` counts physical components, + independently of the state's polarisation axis. """ grids, transform = self._problem.grids, self._problem.transform taper = ( @@ -1282,10 +1128,7 @@ def set_initial( def _march_to(self, target: float) -> None: """Step to `target` with the Adams method when one is set, else Runge-Kutta. - Parameters - ---------- - target : float - Position to reach, m. + - ``target``: position to reach, m. """ if self._adams is None: super()._march_to(target) @@ -1293,13 +1136,7 @@ def _march_to(self, target: float) -> None: self._adams.march_to(target) def _reserve_source_calls(self, count: int) -> None: - """Reserve every atomic chunk required by one complete Adams trial. - - Parameters - ---------- - count : int - Number of logical source evaluations required by the trial. - """ + """Reserve every atomic chunk required by one complete Adams trial.""" binding = self._tdse_binding if binding is None: return @@ -1333,19 +1170,10 @@ def _adams_source( ]: """Evaluate the source once through the reference passes. - Parameters - ---------- - lab : numpy.ndarray - The lab-frame field at `position`. - anchor, position : float - The step's anchor and the evaluation position, m. - purpose : str, optional - Atomic-call accounting label; ignored when no atomic service is bound. - - Returns - ------- - tuple - ``U(anchor, position) N(lab, position)`` and ``U(position, anchor)``. + - ``anchor``, ``position``: the step's anchor and the evaluation position, m. + - ``purpose``: Atomic-call accounting label; ignored when no atomic service is + bound. + - Returns: ``U(anchor, position) N(lab, position)`` and ``U(position, anchor)``. """ held = self._adams_held if held is not None and held[0] == anchor and held[1] == position: @@ -1366,28 +1194,17 @@ def _adams_source( def _dense_stages(self) -> list[np.ndarray]: """Return the last trial's stage derivatives, in its anchor's frame. - Returns - ------- - list of numpy.ndarray - One per tableau stage, in the canonical layout. + - Returns: one per tableau stage, in the canonical layout. """ return list(self._last_stages) def _adams_block(self, z: float, dz: float) -> object: """Return the frozen block of the section a step from `z` is anchored in. - Parameters - ---------- - z : float - Where the step starts, m. - dz : float - The step, m. - - Returns - ------- - object - The step's :class:`~solprop.backends.multistep.CoupledBlock`, or ``None`` - without a coupled flow. + - ``z``: where the step starts, m. + - ``dz``: the step, m. + - Returns: the step's :class:`~solprop.backends.multistep.CoupledBlock`, or + ``None`` without a coupled flow. """ from solprop.backends.multistep import CoupledBlock # noqa: PLC0415 - cycle @@ -1425,19 +1242,9 @@ def _adams_lab_source( lab-frame source bit for bit, and every route's evaluation stays the one the Runge-Kutta march uses. - Parameters - ---------- - lab : numpy.ndarray - The lab-frame field at `position`. - position : float - The evaluation position, m. - purpose : str, optional - Atomic-call accounting label; ignored when no atomic service is bound. - - Returns - ------- - numpy.ndarray - ``F(lab, position)`` in the lab frame. + - ``position``: the evaluation position, m. + - ``purpose``: Atomic-call accounting label; ignored when no atomic service is + bound. """ bundle = self._stage_scalars(position, position) noise = self._noise_at(bundle) @@ -1455,15 +1262,7 @@ def _adams_propagator( run holds it: the low-storage march applies its flows at once, and a held one would be a volume its ledger does not name. - Parameters - ---------- - anchor, position : float - The step's anchor and its end, m. - - Returns - ------- - numpy.ndarray - ``exp(Phi(position) - Phi(anchor))``. + - ``anchor``, ``position``: the step's anchor and its end, m. """ bundle = self._stage_scalars(anchor, position) propagator = self._propagator(self._delta_phi(bundle)) @@ -1506,17 +1305,9 @@ def _trial_step( response alone and the flow's own error is outside what the embedded pair measures: a known limitation of the controller on a CF4 section. - Parameters - ---------- - z : float - Position at the start of the step, m. - dz : float - Length of the trial step, m, positive. - - Returns - ------- - tuple - The scaled error norm and the state at the end of the step. + - ``z``: position at the start of the step, m. + - ``dz``: length of the trial step, m, positive. + - Returns: the scaled error norm and the state at the end of the step. """ state = self._require_state() self._hoist(z) @@ -1587,18 +1378,9 @@ def _channels( ) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"]: """Mix the channel axis by one coupled-flow action, or return the values. - Parameters - ---------- - values : numpy.ndarray - Any array of the state's layout. - action : numpy.ndarray or None - The action on the active bins, or ``None`` for a problem with no coupled - flow, which is every route but the coherent wall. - - Returns - ------- - numpy.ndarray - The values, mixed or untouched. + - ``values``: any array of the state's layout. + - ``action``: the action on the active bins, or ``None`` for a problem with no + coupled flow, which is every route but the coherent wall. """ if self._flow is None or action is None: return values @@ -1618,19 +1400,7 @@ def _residual( anchored in a ``"cf4"`` section has none either, its whole generator being in the exponential. - Parameters - ---------- - lab : numpy.ndarray - The lab-frame stage input. - section : int - The step's section, from the step bundle's ``flow_section``. - bundle : solprop.translate.stage.StageScalars - The stage's own bundle, whose ``flow_weights`` the terms are combined over. - - Returns - ------- - numpy.ndarray or None - ``R_s(z_1) u`` in the state's layout, or ``None``. + - Returns: ``R_s(z_1) u`` in the state's layout, or ``None``. """ flow = self._flow if flow is None or flow.residual_terms is None or flow.cf4[section]: @@ -1680,20 +1450,10 @@ def _flow_actions( is checked against the section's declared pair bound, factor by factor (:func:`_check_cf4_growth`). - Parameters - ---------- - z : float - Where the step starts, m. - section : int - Which section the step is anchored in, from the stage bundle's - ``flow_section``. - dz : float - Length of the trial step, m. - - Returns - ------- - callable - ``fraction -> (U, U^-1)``, each ``None`` when no coupled flow is carried. + - ``z``: where the step starts, m. + - ``dz``: length of the trial step, m. + - Returns: ``fraction -> (U, U^-1)``, each ``None`` when no coupled flow is + carried. """ flow = self._flow if flow is None: @@ -1711,16 +1471,9 @@ def action( ) -> tuple[np.ndarray | None, np.ndarray | None]: """Return the pair at a stage fraction, computing it at most once. - Parameters - ---------- - fraction : float - The tableau node ``c_i``, dimensionless, in ``[0, 1]``. - - Returns - ------- - tuple - The forward and inverse actions, or ``(None, None)`` at fraction zero, - where both are the identity. + - ``fraction``: the tableau node ``c_i``, dimensionless, in ``[0, 1]``. + - Returns: the forward and inverse actions, or ``(None, None)`` at fraction + zero, where both are the identity. """ if fraction == 0.0: return None, None @@ -1745,15 +1498,7 @@ def _apply_mask( ) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"]: """Zero the state outside the active band. - Parameters - ---------- - state : numpy.ndarray - The state in the canonical layout. - - Returns - ------- - numpy.ndarray - The masked state. + - ``state``: the state in the canonical layout. """ return passes.apply_hard_mask(state, self._problem.grids.active_mask) @@ -1775,16 +1520,8 @@ def _record(self, z: float, dz: float, norm: float, accepted: bool) -> None: replayed step is recorded after its reservation is released; it is still the latest trial. Nothing about the step or its accounting changes. - Parameters - ---------- - z : float - Position at the end of the step, m. - dz : float - Length of the step, m. - norm : float - The scaled error norm the attempt scored. - accepted : bool - Whether the step was kept. + - ``z``: position at the end of the step, m. + - ``dz``: length of the step, m. """ super()._record(z, dz, norm, accepted) if self._tdse_binding is not None: @@ -1797,19 +1534,10 @@ def _finish( ) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"]: """Apply the once-per-accepted-step boundary treatments to a kept step. - Parameters - ---------- - state : numpy.ndarray - The state at the end of the step. - dz : float - Length of the step, m. - - Returns - ------- - numpy.ndarray - The state with the hard band mask and, when one is present, the temporal - absorber applied. The ``"split_step"`` absorber is followed by the mask, so - the accepted state never holds content outside the active band. + - ``dz``: length of the step, m. + - Returns: the state with the hard band mask and, when one is present, the + temporal absorber applied. The ``"split_step"`` absorber is followed by the + mask, so the accepted state never holds content outside the active band. """ masked = self._apply_mask(state) responses = self._problem.responses @@ -1949,24 +1677,6 @@ def _nonlinear( the projection GEMM accumulated over the chunks, then one batched forward transform, the crop and the prefactor. Mode-averaged propagation is the one-node, one-column case of exactly this code. - - Parameters - ---------- - lab : numpy.ndarray - The lab-frame stage input from - :func:`solprop.backends.numpy_ref.passes.stage_input`. - bundle : solprop.translate.stage.StageScalars - The stage's host scalars: the prefactor's weights and every scalar path the - responses read, all at the stage's own position. - inverse_propagator : numpy.ndarray - ``exp(-dPhi_i)``. - purpose : str, optional - Source-evaluation label for atomic accounting and diagnostics. - - Returns - ------- - numpy.ndarray - The stage derivative in the interaction variable. """ responses = self._problem.responses grids = self._problem.grids @@ -2043,24 +1753,7 @@ def _nonlinear_tdse( inverse_propagator: Complex[np.ndarray, "n_batch n_modes n_pol n_omega"], purpose: TDSEPurpose, ) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"]: - """Evaluate the declared atomic binding and combine independent P/J sources. - - Parameters - ---------- - lab : numpy.ndarray - Lab-frame modal input, shape ``(n_batch, n_modes, n_pol, n_omega)``. - bundle : StageScalars - Stage density, modal weights and absolute position. - inverse_propagator : numpy.ndarray - Inverse linear propagation from this stage to its anchor. - purpose : str - Evaluation label used by the atomic call ledger. - - Returns - ------- - numpy.ndarray - Modal derivative in the anchor frame, with the in-stage collar. - """ + """Evaluate the declared atomic binding and combine independent P/J sources.""" problem = self._problem grids, transform = problem.grids, problem.transform binding = self._tdse_binding @@ -2117,20 +1810,6 @@ def _nonlinear_general( A guide with no responding region is short-circuited, as the legacy path short-circuits a problem whose `response_order` is the accumulation alone. - - Parameters - ---------- - lab : numpy.ndarray - The lab-frame stage input. - bundle : solprop.translate.stage.StageScalars - The stage's host scalars. - inverse_propagator : numpy.ndarray - ``exp(-dPhi_i)``. - - Returns - ------- - numpy.ndarray - The stage derivative in the interaction variable. """ assert self._general is not None grids = self._problem.grids @@ -2174,19 +1853,10 @@ def _column_sources( ) -> passes.NonlinearSources: """Evaluate one column; the historical integral is benchmark-only. - Parameters - ---------- - field_t : numpy.ndarray - Physical node fields in V/m, with time last. - bundle : StageScalars - Densities, populations and other response scalars at this position. - raman : numpy.ndarray or None - Damped Raman convolution spectrum, when required by the response. - - Returns - ------- - NonlinearSources - Collared local polarisation in C/m² and ungated current in A/m². + - ``field_t``: physical node fields in V/m, with time last. + - ``raman``: damped Raman convolution spectrum, when required by the response. + - Returns: the collared local polarisation in C/m² and the ungated current in + A/m². """ responses, grids = self._problem.responses, self._problem.grids sources = passes.nonlinear_sources( @@ -2213,15 +1883,10 @@ def _column_sources( def _held_streams(self, gated: np.ndarray, edge: np.ndarray) -> np.ndarray: """Transform and combine the current products in polarisation units. - Parameters - ---------- - gated, edge : numpy.ndarray - Fine-time gJ and g'J rows, optionally projected before this call. - - Returns - ------- - numpy.ndarray - Cropped raw-DFT polarisation-equivalent source, zero on inactive bins. + - ``gated``, ``edge``: fine-time ``gJ`` and ``g'J`` rows, optionally projected + before this call. + - Returns: cropped raw-DFT polarisation-equivalent source, zero on inactive + bins. """ grids = self._problem.grids return passes.held_plasma_spectrum( @@ -2231,15 +1896,8 @@ def _held_streams(self, gated: np.ndarray, edge: np.ndarray) -> np.ndarray: def _held_current(self, current: np.ndarray) -> np.ndarray: """Form and transform the two collared products of a nodal current. - Parameters - ---------- - current : numpy.ndarray - Ungated nodal current in A/m², with fine time last. - - Returns - ------- - numpy.ndarray - Cropped raw-DFT polarisation-equivalent source at the nodes. + - ``current``: the ungated nodal current in A/m², fine time last. + - Returns: cropped raw-DFT polarisation-equivalent source at the nodes. """ return self._held_streams( self._tables.t_window * current, self._tables.t_window_derivative * current @@ -2250,20 +1908,7 @@ def _radial_plasma_spectrum( modal_t: np.ndarray, bundle: StageScalars, ) -> np.ndarray: - """Transform three nodal streams, combine, then project once in frequency. - - Parameters - ---------- - modal_t : numpy.ndarray - Radial spectral-column fields on the fine time grid. - bundle : StageScalars - Densities, modal weights and other stage response scalars. - - Returns - ------- - numpy.ndarray - Combined projected non-plasma and held-current source spectrum. - """ + """Transform three nodal streams, combine, then project once in frequency.""" problem = self._problem transform, grids = problem.transform, problem.grids # The field's own precision: complex128, or complex64 under SEP 33. @@ -2302,19 +1947,7 @@ def _nonlinear_free( ) -> np.ndarray: """Apply transverse FFTs before bounded temporal response transforms. - Parameters - ---------- - lab : numpy.ndarray - Lab-frame stage spectrum, with the state's leading axis of one. - bundle : solprop.translate.stage.StageScalars - Response scalars at the stage position. - inverse_propagator : numpy.ndarray - Interaction-picture inverse factor. - - Returns - ------- - numpy.ndarray - Stage derivative in the interaction picture. + - ``lab``: Lab-frame stage spectrum, with the state's leading axis of one. """ problem = self._problem transform, grids, responses = ( @@ -2415,19 +2048,7 @@ def _nonlinear_slab( (kernel schema 15). The y collar's in-stage rate depends on y alone, so it acts on the rows directly. - Parameters - ---------- - lab : numpy.ndarray - Lab-frame stage spectrum, with the state's leading axis of one. - bundle : solprop.translate.stage.StageScalars - Response scalars at the stage position. - inverse_propagator : numpy.ndarray - Interaction-picture inverse factor. - - Returns - ------- - numpy.ndarray - Stage derivative in the interaction picture. + - ``lab``: Lab-frame stage spectrum, with the state's leading axis of one. """ problem = self._problem transform, grids, responses = ( @@ -2530,17 +2151,8 @@ def _slab_absorber( no temporal rate in-stage it commutes with the temporal transforms, and the pass needs no temporal pair (SEP 40 section 4.7). - Parameters - ---------- - rows_f : numpy.ndarray - The stage's rows in frequency. - rows_t : numpy.ndarray or None - The same rows in time; required when a temporal rate applies in-stage. - - Returns - ------- - numpy.ndarray - The term, masked to the active band. + - ``rows_t``: the same rows in time; required when a temporal rate applies + in-stage. """ problem = self._problem transform, grids, responses = ( @@ -2578,19 +2190,9 @@ def _absorber_term( :data:`solprop.monitor.QUIET`, and its transform is the stage's largest cost after the response's. - Parameters - ---------- - modal_t : numpy.ndarray - The lab-frame modal field on the fine time grid. - may_quiet : bool, optional - Whether this stage honours the monitor's quiet collar. - - Returns - ------- - numpy.ndarray or None - The absorber's term on the coarse grid, or ``None`` when the - ``"split_step"`` scheme is active and the rate is applied to the state - after the step instead. + - Returns: the absorber's term on the coarse grid, or ``None`` when the + ``"split_step"`` scheme is active and the rate is applied to the state after + the step instead. """ rate = self._tables.temporal_rate if not self._in_stage_absorber: @@ -2627,21 +2229,9 @@ def _over_nodes( what makes the quadrature alarm free: the two rules share every node the coarse one has. - Parameters - ---------- - modal_t : numpy.ndarray - The modal field on the fine time grid, shape ``(n_modes, n_pol, n_to)``. - bundle : solprop.translate.stage.StageScalars - The stage's host scalars. - - Returns - ------- - projected : numpy.ndarray - The modal polarisation on the fine time grid, shape - ``(n_modes, n_pol, n_to)``. - coarse : numpy.ndarray or None - The same under the embedded coarse rule, or ``None`` when the quadrature - has no embedded pair and the estimate would be identically zero. + - Returns: the modal polarisation on the fine time grid, and the same under the + embedded coarse rule, or ``None`` when the quadrature has no embedded pair + and the estimate would be identically zero. """ responses, grids = self._problem.responses, self._problem.grids transform = self._problem.transform @@ -2741,21 +2331,13 @@ def _raman_spectrum( most recent set is kept, which is the right choice here -- a growing cache would hold thousands of spectra and hit none. - Parameters - ---------- - rates : tuple of float - The decay rate of each term at the stage's position, s^-1, off the bundle. - - densities : tuple of float, optional - Component amplitude densities in m^-3, in Raman-component order. Multiple - components include every amplitude in the cache key; one keeps density - outside the convolution and retains the original arithmetic. - - Returns - ------- - numpy.ndarray or None - The spectrum of shape ``(n_to + 1,)``, or ``None`` when no Raman kernel was - compiled. + - ``rates``: the decay rate of each term at the stage's position, s^-1, off the + bundle. + - ``densities``: component amplitude densities in m^-3, in Raman-component + order. Multiple components include every amplitude in the cache key; one keeps + density outside the convolution and retains the original arithmetic. + - Returns: the spectrum of shape ``(n_to + 1,)``, or ``None`` when no Raman + kernel was compiled. """ components = self._problem.responses.raman_terms if not components: @@ -2804,17 +2386,9 @@ def _exponents( itself is one linear combination of the terms over the bundle's weights, whatever the representation (:mod:`solprop.translate.stage`). - Parameters - ---------- - z : float - Position at the start of the step, m. - dz : float - Length of the step, m. - - Returns - ------- - callable - ``(fraction, bundle) -> dPhi``, of the state's broadcast shape. + - ``z``: position at the start of the step, m. + - ``dz``: length of the step, m. + - Returns: ``(fraction, bundle) -> dPhi``, of the state's broadcast shape. """ del z, dz if self._propagator_mode == "recompute": @@ -2826,17 +2400,7 @@ def exponent( ) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"]: """Return ``dPhi`` at a stage fraction, computing it at most once. - Parameters - ---------- - fraction : float - The tableau node ``c_i``, dimensionless, in ``[0, 1]``. - bundle : solprop.translate.stage.StageScalars - The stage's host scalars, whose weights the terms are combined over. - - Returns - ------- - numpy.ndarray - ``Phi(z + c_i h) - Phi(z)``, dimensionless. + - ``fraction``: the tableau node ``c_i``, dimensionless, in ``[0, 1]``. """ if fraction not in cache: cache[fraction] = self._delta_phi(bundle) @@ -2915,18 +2479,6 @@ def _refuse_adams(problem: PropagationProblem, stepper: StepperSettings) -> None the state only inside the collar, where its first-order splitting error already lies (``docs/dev/stepper.md`` section 6), and it removes the collar's stability limit, which binds the Adams methods long before Runge-Kutta's (SEP 13 phase 2). - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem to check. - stepper : solprop.translate.problem.StepperSettings - The run's stepper section, whose method is checked. - - Raises - ------ - BackendError - Naming what is unsupported. """ integrator = stepper.integrator if not isinstance(integrator, Multistep): @@ -2949,20 +2501,7 @@ def _refuse_adams(problem: PropagationProblem, stepper: StepperSettings) -> None def _refuse_unsupported(problem: PropagationProblem, stepper: StepperSettings) -> None: - """Refuse, by name, every problem this backend does not execute. - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem to check. - stepper : solprop.translate.problem.StepperSettings - The run's stepper section, whose method is checked. - - Raises - ------ - BackendError - Naming what is unsupported. - """ + """Refuse, by name, every problem this backend does not execute.""" # The route first, by its variant (SEP 47 section 6.2): the reference executes # every route of the union and the modal route's TDSE extension. match problem.route: @@ -3051,18 +2590,6 @@ def _refuse_inadmissible(problem: PropagationProblem, spec: StatsSpec) -> None: statistics directly, and a general guide's static synthesis matrix is a checked **zero** placeholder, so an on-axis intensity taken from one would be zero rather than obviously wrong. - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem this backend was built from. - spec : solprop.stats.StatsSpec - The statistics asked for. - - Raises - ------ - BackendError - Naming the statistic and the basis, with the replacement to use. """ refusal = refusal_for( declared_capability( @@ -3092,17 +2619,8 @@ def _general_maps( nothing to evaluate per stage. The chunk is the backend's own memory strategy within the plan's budget, or the plan's exact chunk when one was forced. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - A validated problem this backend has already admitted. - plan : solprop.backends.node_chunk.NodeChunkPlan or None, optional - The backend's node-chunk plan; ``None`` plans the automatic one. - - Returns - ------- - GeneralMaps or None - The derived maps, or ``None`` when the problem carries no modal tables. + - ``plan``: the backend's node-chunk plan; ``None`` plans the automatic one. + - Returns: the derived maps, or ``None`` when the problem carries no modal tables. """ tables = problem.transform.modal_tables if tables is None: @@ -3240,23 +2758,9 @@ class _CoupledFlow: def _flow_squarings(largest: float, *, backend: str = "numpy_ref") -> int: """Return how many squarings an exponent of this row-sum norm needs, or refuse. - Parameters - ---------- - largest : float - The largest row-sum norm of the exponent over the stack, dimensionless. - backend : str, optional - The executing backend's registry name, used in the refusal: the compiled - backends refuse a step on this rule too, whichever route forms their actions. - - Returns - ------- - int - The number of squarings that brings the norm below :data:`_TAYLOR_SCALE`. - - Raises - ------ - BackendError - If it exceeds :data:`_MAX_FLOW_SQUARINGS`. + The count brings the norm below :data:`_TAYLOR_SCALE`; past + :data:`_MAX_FLOW_SQUARINGS` the step is refused, as the compiled backends refuse it + whichever route forms their actions. """ squarings = 0 if largest > _TAYLOR_SCALE: @@ -3278,22 +2782,7 @@ def _triangular_exponential( ) -> Complex[np.ndarray, "n_active_x n_modes_x_out n_modes_x_in"]: """Return ``exp(A)`` for a stack of blocks, by scaling, a series and squaring. - Parameters - ---------- - blocks : numpy.ndarray - The stack to exponentiate, shape ``(n_active, n_modes, n_modes)``. - backend : str, optional - The executing backend's registry name, used in the refusal. - - Returns - ------- - numpy.ndarray - ``exp(A)`` in the same layout. - - Raises - ------ - BackendError - If a block needs more than :data:`_MAX_FLOW_SQUARINGS` squarings. + - Returns: ``exp(A)`` in the same layout. """ largest = float(np.max(np.sum(abs(blocks), axis=-1), initial=0.0)) squarings = _flow_squarings(largest, backend=backend) @@ -3323,21 +2812,7 @@ def _flow_action( not inherit the conditioning of a solve (``docs/dev/stepper.md`` section 4: no unbounded inverse propagation). - Parameters - ---------- - flow : _CoupledFlow - The held Schur factors. - section : int - Which section's frozen block the step is anchored in. - length : float - Distance in m, positive for the forward action. - backend : str, optional - The executing backend's registry name, used in a refusal. - - Returns - ------- - tuple of numpy.ndarray - The two actions, shape ``(n_active, n_modes, n_modes)``. + - ``length``: distance in m, positive for the forward action. """ vectors = flow.vectors[section] exponent = float(length) * flow.triangular[section] @@ -3370,23 +2845,11 @@ def _cf4_action( step's section, combined over the held terms. Every node lies strictly inside the step, so no edge rule is ever exercised by it. - Parameters - ---------- - flow : _CoupledFlow - The held terms and the leaf. - z : float - Where the step starts, m. - length : float - The stage's own length ``c_i h``, m, positive. - section : int - The step's section. - - Returns - ------- - tuple - The two actions, shape ``(n_active, n_modes, n_modes)``, and the product of - the four factors' spectral norms per active bin -- the quantity the CF4 - factor-norm guard is declared on. + - ``z``: where the step starts, m. + - ``length``: the stage's own length ``c_i h``, m, positive. + - Returns: the two actions, shape ``(n_active, n_modes, n_modes)``, and the product + of the four factors' spectral norms per active bin -- the quantity the CF4 + factor-norm guard is declared on. """ assert flow.residual_terms is not None early, late = ( @@ -3428,21 +2891,7 @@ def _check_cf4_growth( than trusted. It bounds the forward product's excess over the exact action and the inverse action the source is carried back by, together. - Parameters - ---------- - flow : _CoupledFlow - The held declaration. - section : int - The step's section. - growth : numpy.ndarray - The measured product per active bin, dimensionless. - length : float - The stage length it was formed over, m, used in the refusal. - - Raises - ------ - BackendError - If the measured product exceeds the declared bound at any bin. + - ``length``: the stage length it was formed over, m, used in the refusal. """ declared = flow.condition_bound[section] # The same allowance as the frozen check: the bound is on the exact factors, and @@ -3474,19 +2923,7 @@ def _residual_action( is the step's, because the exponential the interaction picture is built on is, and freezing is a rearrangement of the same equation whichever block it names. - Parameters - ---------- - flow : _CoupledFlow - The held terms and frozen blocks. - section : int - The step's section, from the stage bundle's ``flow_section``. - weights : numpy.ndarray - The stage's flow weights, float64, shape ``(n_intervals * n_deg,)``. - - Returns - ------- - numpy.ndarray - The residual in 1/m, shape ``(n_active, n_modes, n_modes)``. + - Returns: the residual in 1/m, shape ``(n_active, n_modes, n_modes)``. """ assert flow.residual_terms is not None return combine(weights, flow.residual_terms) - flow.frozen[section] @@ -3572,22 +3009,8 @@ def _residual_drive( it; only the nonlinear source couples them in, at the size of the collar's content. - Parameters - ---------- - lab : numpy.ndarray - The lab-frame stage input. - action : numpy.ndarray - ``R_s(z_1)`` on the active bins, 1/m. - active : numpy.ndarray - Which bins those are, bool, shape ``(n_omega,)``. - scored : numpy.ndarray - Which bins the residual is applied at, bool, shape ``(n_omega,)``: the active - bins inside the error norm's band. - - Returns - ------- - numpy.ndarray - ``R_s(z_1) u`` in the state's layout, zero off the scored active bins. + - ``action``: ``R_s(z_1)`` on the active bins, 1/m. + - Returns: ``R_s(z_1) u`` in the state's layout, zero off the scored active bins. """ drive = np.zeros_like(lab) drive[:, :, :, scored] = np.einsum( @@ -3603,21 +3026,12 @@ def _apply_flow( ) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"]: """Apply a per-frequency channel action to the canonical state. - Parameters - ---------- - state : numpy.ndarray - The state, or any array of its layout. - action : numpy.ndarray - The action on the active bins. The identity case is the caller's: a problem - with no coupled flow, or the zero stage fraction, never reaches here. - active : numpy.ndarray - Which bins the action covers, bool, shape ``(n_omega,)``. + The identity case is the caller's: a problem with no coupled flow, or the zero + stage fraction, never reaches here. - Returns - ------- - numpy.ndarray - The state with its channel axis mixed on the active bins and untouched off - them, where the block is zero and the action is the identity. + - ``state``: the state, or any array of its layout. + - Returns: the state with its channel axis mixed on the active bins and untouched + off them, where the block is zero and the action is the identity. """ carried = state.copy() carried[:, :, :, active] = np.einsum( @@ -3652,23 +3066,8 @@ def _check_flow_conditioning( bounded at all: the picture is re-anchored at every accepted step, so no inverse action is ever taken over more than one of them. - Parameters - ---------- - flow : _CoupledFlow - The held factors and the declared row. - section : int - Which section the step is anchored in. - pair : tuple of numpy.ndarray - The forward and inverse actions over the whole step. - dz : float - Length of the step, m, used in the refusal. - backend : str, optional - The executing backend's registry name, used in the refusal. - - Raises - ------ - BackendError - If the measured conditioning exceeds the declared bound at any bin. + - ``pair``: the forward and inverse actions over the whole step. + - ``dz``: length of the step, m, used in the refusal. """ forward, inverse = pair if not forward.size: @@ -3695,23 +3094,7 @@ def _refuse_flow_conditioning( compares them with the declared row by the same allowance and refuses in the same words. - Parameters - ---------- - flow : _CoupledFlow - The held factors and the declared row. - section : int - Which section the step is anchored in. - measured : numpy.ndarray - ``norm(U)_2 norm(U^-1)_2`` of the whole step's actions per active bin. - dz : float - Length of the step, m, used in the refusal. - backend : str, optional - The executing backend's registry name, used in the refusal. - - Raises - ------ - BackendError - If the measured conditioning exceeds the declared bound at any bin. + - ``dz``: length of the step, m, used in the refusal. """ declared = flow.condition_bound[section] # A bin whose block is normal has a bound of exactly one and a measured product @@ -3740,17 +3123,8 @@ def _refuse_flow_conditioning( def _coupled_flow_maps(problem: PropagationProblem) -> _CoupledFlow | None: """Decompose the coupled flow this backend will execute, once. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem, already admitted by - :func:`solprop.backends.numpy_ref.admission.refuse_coupled_flow`. - - Returns - ------- - _CoupledFlow or None - The Schur factors and the certificate, or ``None`` for a problem carrying no - coupled flow. + - Returns: the Schur factors and the certificate, or ``None`` for a problem carrying + no coupled flow. """ flow = problem.linear.coupled_flow if flow is None: @@ -3809,16 +3183,8 @@ def _source_record(problem: PropagationProblem) -> dict[str, object]: The steady-current certificate (SEP 26 section 4.4) is refused above its limit at translation. It is run metadata, never part of the descriptor or any identity. - Parameters - ---------- - problem : PropagationProblem - A TDSE problem. - - Returns - ------- - dict - ``gated``, and for a gated source the certificate, its limit - and the taper breakpoints in s. + - Returns: ``gated``, and for a gated source the certificate, its limit and the + taper breakpoints in s. """ descriptor = tdse_descriptor(problem.route) assert descriptor is not None @@ -3844,23 +3210,11 @@ def _harmonic_carrier( axis and a backend built it from ``omega0``, ``n_to`` and ``dt_fine`` -- one piece of physics inside a backend, against ``docs/dev/architecture.md`` section 3, which is why it now arrives as ``grids.carrier_2w0``. The sign is the FFT convention's - (``docs/guide/media/nonlinear.md``). - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem, whose compiled Kerr term says whether the harmonic is wanted. + (``docs/guide/media/nonlinear.md``). ``validate`` refuses a harmonic term without + the carrier, so only a hand-built problem reaches the refusal here. - Returns - ------- - numpy.ndarray or None - The carrier of shape ``(n_to,)``, or ``None`` when no harmonic term is present. - - Raises - ------ - BackendError - If a harmonic term is compiled and the contract carries no carrier for it. - ``validate`` refuses that pairing, so only a hand-built problem reaches here. + - Returns: the carrier of shape ``(n_to,)``, or ``None`` when no harmonic term is + present. """ if not problem.responses.harmonic_carrier: return None @@ -3885,15 +3239,7 @@ def _embedded_projection( (:class:`solprop.modes.quadrature.ModeMatrices`). The alarm reports ``nan`` there instead, and the second projection is not computed at all. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem, whose transform carries both projections. - - Returns - ------- - numpy.ndarray or None - The coarse projection, or ``None`` when it is the fine one. + - Returns: the coarse projection, or ``None`` when it is the fine one. """ transform = problem.transform if np.array_equal(transform.projection, transform.projection_coarse): diff --git a/src/solprop/backends/numpy_ref/batch.py b/src/solprop/backends/numpy_ref/batch.py index b900a95d7..2ff88ba61 100644 --- a/src/solprop/backends/numpy_ref/batch.py +++ b/src/solprop/backends/numpy_ref/batch.py @@ -117,17 +117,9 @@ def __init__(self, members: Sequence[NumpyRefBackend]) -> None: def _trial_step(self, z: float, dz: float) -> tuple[float, tuple[object, ...]]: """Evaluate vector stage arithmetic with independent member responses. - Parameters - ---------- - z : float - Start position, m. - dz : float - Positive fixed step length, m. - - Returns - ------- - tuple - Maximum diagnostic norm and member trial states. + - ``z``: start position, m. + - ``dz``: positive fixed step length, m. + - Returns: maximum diagnostic norm and member trial states. """ members = self._reference_members state = np.concatenate([member._require_state() for member in members]) diff --git a/src/solprop/backends/numpy_ref/envelope_plasma.py b/src/solprop/backends/numpy_ref/envelope_plasma.py index da4dad2ae..43d97e1ae 100644 --- a/src/solprop/backends/numpy_ref/envelope_plasma.py +++ b/src/solprop/backends/numpy_ref/envelope_plasma.py @@ -69,20 +69,9 @@ def _cycle_quantities( ) -> tuple[Float[np.ndarray, " n_to"], Complex[np.ndarray, "n_pol n_to"]]: """Evaluate rate and optional directional loss by independent SVD axes. - Parameters - ---------- - field : numpy.ndarray - Physical V/m envelope, shape (n_pol, n_to). - cycle : CycleIntegralData - Validated instantaneous rate and quadrature rules. - loss : bool - Whether to form the directional loss moments beside the averaged rate. - - Returns - ------- - tuple of numpy.ndarray - Rate in s^-1 and loss moment per unit ionisation work, with shapes - (n_to,) and (n_pol, n_to). + - ``field``: physical V/m envelope, shape (n_pol, n_to). + - Returns: rate in s^-1 and loss moment per unit ionisation work, with shapes + (n_to,) and (n_pol, n_to). """ rate = np.zeros(field.shape[1]) current = np.zeros_like(field) @@ -118,19 +107,10 @@ def _population( ) -> tuple[Float[np.ndarray, " n_to"], Float[np.ndarray, " n_to"]]: """Retain survival separately from a cancellation-free population scan. - Parameters - ---------- - rate : numpy.ndarray - Cycle-averaged rates in s^-1, shape (n_to,). - initial : float - Initial ionised fraction in [0, 1]. - dt : float - Fine time spacing in s. - - Returns - ------- - tuple of numpy.ndarray - Ionised fraction and surviving neutral fraction, each shape (n_to,). + - ``rate``: Cycle-averaged rates in s^-1, shape (n_to,). + - ``initial``: initial ionised fraction in [0, 1]. + - ``dt``: fine time spacing in s. + - Returns: ionised fraction and surviving neutral fraction, each shape (n_to,). """ with np.errstate(over="ignore"): increments = (0.5 * rate[:-1] + 0.5 * rate[1:]) * dt diff --git a/src/solprop/backends/numpy_ref/general_modal.py b/src/solprop/backends/numpy_ref/general_modal.py index 54caeef4f..3f3219712 100644 --- a/src/solprop/backends/numpy_ref/general_modal.py +++ b/src/solprop/backends/numpy_ref/general_modal.py @@ -255,14 +255,11 @@ class GeneralMaps: node_region: Int32[np.ndarray, " n_nodes_region"] | None = None @property - def moving(self) -> bool: - """Return whether the maps move along z. + def moving(self) -> bool: # numpydoc ignore=RT01 + """Whether the maps move along z. - Returns - ------- - bool - True when the synthesis and projection are Chebyshev terms a stage - combines over its ``transform_weights``. + True when the synthesis and projection are Chebyshev terms a stage + combines over its ``transform_weights``. """ return self.synthesis_terms is not None @@ -313,35 +310,26 @@ def at(self, weights: Float[np.ndarray, " n_map_weights"] | None) -> "GeneralMap ) @property - def n_terms(self) -> int: - """Return how many delayed channels the maps carry. + def n_terms(self) -> int: # numpydoc ignore=RT01 + """How many delayed channels the maps carry. - Returns - ------- - int - Zero when no region declares a delayed response. + Zero when no region declares a delayed response. """ return 0 if self.delayed_tensor is None else int(self.delayed_tensor.shape[1]) @property - def n_nodes(self) -> int: - """Return how many quadrature nodes the guide's rule carries. + def n_nodes(self) -> int: # numpydoc ignore=RT01 + """How many quadrature nodes the guide's rule carries. - Returns - ------- - int - The node axis of ``synthesis``. + The node axis of ``synthesis``. """ return int(self.synthesis.shape[0]) @property - def n_modes(self) -> int: - """Return how many channels the guide propagates. + def n_modes(self) -> int: # numpydoc ignore=RT01 + """How many channels the guide propagates. - Returns - ------- - int - The channel axis of ``synthesis``. + The channel axis of ``synthesis``. """ return int(self.synthesis.shape[2]) diff --git a/src/solprop/backends/numpy_ref/passes.py b/src/solprop/backends/numpy_ref/passes.py index c12c1c13c..94483d00d 100644 --- a/src/solprop/backends/numpy_ref/passes.py +++ b/src/solprop/backends/numpy_ref/passes.py @@ -609,23 +609,11 @@ def _vector_raman_polarisation( ) -> Num[np.ndarray, "n_nodes_chunk 2 n_to"]: """Convolve trace, difference and coherence drives with compiled kernels. - Parameters - ---------- - field_t : numpy.ndarray - Cartesian field in V/m, shape ``(n_nodes_chunk, 2, n_to)``. - spectrum : numpy.ndarray - One or two effective kernels, shape ``(n_raman_kernels, n_to + 1)``. - kind : {'field', 'envelope'} - Temporal representation. - thg : bool - Keep the real-field harmonic drive; ignored for envelopes. - density : float - Outside amplitude density in m^-3, or one for a density-weighted mixture. - - Returns - ------- - numpy.ndarray - Cartesian polarisation in C/m^2 with the field's shape. + - ``field_t``: Cartesian field in V/m, shape ``(n_nodes_chunk, 2, n_to)``. + - ``thg``: keep the real-field harmonic drive; ignored for envelopes. + - ``density``: outside amplitude density in m^-3, or one for a density-weighted + mixture. + - Returns: Cartesian polarisation in C/m^2 with the field's shape. """ n_to = field_t.shape[-1] if spectrum.ndim != 2 or spectrum.shape not in ((1, n_to + 1), (2, n_to + 1)): @@ -801,15 +789,7 @@ def plasma_scan( def _rule(integral: RunningIntegral | None) -> RunningIntegral: """Return the running-integral rule a plasma pass uses. - Parameters - ---------- - integral : solprop.nonlinear.ionisation.RunningIntegral or None - The problem's rule, or ``None`` for the trapezoid. - - Returns - ------- - solprop.nonlinear.ionisation.RunningIntegral - `integral`, or the trapezoid. + - ``integral``: the problem's rule, or ``None`` for the trapezoid. """ return RunningIntegral.trapezoid() if integral is None else integral @@ -901,23 +881,10 @@ def _plasma_total_current( ) -> Float[np.ndarray, "n_nodes_chunk n_pol n_to"]: """Add the guarded loss current to the free current, per unit density. - Parameters - ---------- - field_t : numpy.ndarray - Real field in V/m, shape ``(n_nodes_chunk, n_pol, n_to)``. - rate : numpy.ndarray - Ionisation rate in s⁻¹, shape ``(n_nodes_chunk, n_to)``. - fraction : numpy.ndarray - Dimensionless ionised population, with the same shape as the rate. - free_current : numpy.ndarray - Free current per unit number density, with the same shape as the field. - coefficients : PlasmaCoefficients - Ionisation potential in J and the compiled single-channel model. - - Returns - ------- - numpy.ndarray - Total current per unit number density in A m, with the field's shape. + - ``field_t``: real field in V/m, shape ``(n_nodes_chunk, n_pol, n_to)``. + - ``rate``: ionisation rate in s⁻¹, shape ``(n_nodes_chunk, n_to)``. + - ``coefficients``: ionisation potential in J and the compiled single-channel model. + - Returns: total current per unit number density in A m, with the field's shape. """ # The squared magnitude rather than the field itself: it is the divisor the guard # has to test, and it underflows to zero below about 1e-162 V/m. @@ -1137,30 +1104,13 @@ def _plasma_channels_current( # noqa: PLR0917 - called through a reference current is pointwise. The original one-channel pass retains its density outside the integrals. - Parameters - ---------- - field_t : numpy.ndarray - Real node fields in V/m, shape (n_nodes_chunk, n_pol, n_to). - channels : tuple of PlasmaChannel - Ordered independent channel coefficients. - densities : tuple of float - Number density of each population in m^-3. - preionisation : tuple of float - Initial fraction of each population, dimensionless in [0, 1]. - dt : float - Fine-grid sample spacing in s. - integral : solprop.nonlinear.ionisation.RunningIntegral or None, optional - The running-integral rule shared by every channel; ``None`` is the trapezoid. - - Returns - ------- - numpy.ndarray - Total plasma current density in A/m², with the field shape. - - Raises - ------ - BackendError - If there is not one density per channel. + - ``field_t``: real node fields in V/m, shape (n_nodes_chunk, n_pol, n_to). + - ``densities``: number density of each population in m^-3. + - ``preionisation``: initial fraction of each population, dimensionless in [0, 1]. + - ``dt``: Fine-grid sample spacing in s. + - ``integral``: the running-integral rule shared by every channel; ``None`` is the + trapezoid. + - Returns: the total plasma current density in A/m², with the field's shape. """ if len(densities) != len(channels): raise BackendError("plasma channels require one density each") @@ -1695,15 +1645,7 @@ def _magnitude( ) -> Float[np.ndarray, "n_nodes_chunk n_to"]: """Return the field magnitude the ionisation rate and the loss term take. - Parameters - ---------- - field_t : numpy.ndarray - The real field at the chunk's nodes, shape ``(n_nodes_chunk, n_pol, n_to)``. - - Returns - ------- - numpy.ndarray - ``|E|`` in V/m, shape ``(n_nodes_chunk, n_to)``. + - Returns: ``|E|`` in V/m, shape ``(n_nodes_chunk, n_to)``. """ if field_t.shape[-2] == 1: return np.abs(field_t[..., 0, :]) @@ -1711,23 +1653,7 @@ def _magnitude( def _kerr(responses: CompiledResponses) -> KerrCoefficients: - """Return the compiled Kerr coefficients a declared Kerr stage promised. - - Parameters - ---------- - responses : solprop.translate.responses.CompiledResponses - The compiled responses. - - Returns - ------- - solprop.translate.responses.KerrCoefficients - The coefficients. - - Raises - ------ - BackendError - If the order names the stage and the coefficients are absent. - """ + """Return the compiled Kerr coefficients a declared Kerr stage promised.""" if responses.kerr_coefficients is None: msg = "response_order names the Kerr stage but no coefficients were compiled" raise BackendError(msg) @@ -1735,23 +1661,7 @@ def _kerr(responses: CompiledResponses) -> KerrCoefficients: def _plasma(responses: CompiledResponses) -> PlasmaCoefficients: - """Return the compiled plasma group a declared plasma stage promised. - - Parameters - ---------- - responses : solprop.translate.responses.CompiledResponses - The compiled responses. - - Returns - ------- - solprop.translate.responses.PlasmaCoefficients - The group. - - Raises - ------ - BackendError - If the order names a plasma stage and the group is absent. - """ + """Return the compiled plasma group a declared plasma stage promised.""" if responses.plasma_coefficients is None: msg = "response_order names a plasma stage but no coefficients were compiled" raise BackendError(msg) @@ -1769,20 +1679,7 @@ def _raman_thg(responses: CompiledResponses) -> bool: now carried, so it is read where it belongs. An envelope grid's drive is ``|A|^2/2`` whatever it says, which is why the value is not checked there. - Parameters - ---------- - responses : solprop.translate.responses.CompiledResponses - The compiled responses. - - Returns - ------- - bool - True when the drive is ``E^2`` rather than ``|E_a|^2/2``. - - Raises - ------ - BackendError - If no Raman leaf was compiled, so there is no drive to describe. + - Returns: True when the drive is ``E^2`` rather than ``|E_a|^2/2``. """ compiled = responses.raman_terms if not compiled: @@ -1798,23 +1695,7 @@ def _raman_thg(responses: CompiledResponses) -> bool: def _raman( spectrum: Complex[np.ndarray, "... n_omega_doubled"] | None, ) -> Complex[np.ndarray, "... n_omega_doubled"]: - """Return the Raman kernel spectrum a declared Raman stage promised. - - Parameters - ---------- - spectrum : numpy.ndarray or None - What the caller formed for this position. - - Returns - ------- - numpy.ndarray - The same spectrum. - - Raises - ------ - BackendError - If the order names the stage and no spectrum was formed. - """ + """Return the Raman kernel spectrum a declared Raman stage promised.""" if spectrum is None: msg = ( "response_order names the Raman stage but no kernel spectrum was given; " @@ -1825,23 +1706,7 @@ def _raman( def _plasma_rate(responses: CompiledResponses) -> RateTable: - """Return the rate table the ionisation stage reads. - - Parameters - ---------- - responses : solprop.translate.responses.CompiledResponses - The compiled responses. - - Returns - ------- - solprop.translate.ionisation.RateTable - The table. - - Raises - ------ - BackendError - If the order names the stage and no table was compiled. - """ + """Return the rate table the ionisation stage reads.""" if len(responses.ionisation_table) != 1: msg = "response_order names the ionisation stage but no rate table was compiled" raise BackendError(msg) @@ -1849,23 +1714,7 @@ def _plasma_rate(responses: CompiledResponses) -> RateTable: def _checked[T](value: T | None) -> T: - """Return a stage result the compiled order guarantees is present. - - Parameters - ---------- - value : object or None - What the earlier stage produced. - - Returns - ------- - object - The same value. - - Raises - ------ - BackendError - If a stage ran before the one that feeds it. - """ + """Return a stage result the compiled order guarantees is present.""" if value is None: msg = ( "a plasma stage ran before the one that feeds it; response_order is out of " diff --git a/src/solprop/backends/numpy_ref/tdse.py b/src/solprop/backends/numpy_ref/tdse.py index 9be8b6e3c..50290d68b 100644 --- a/src/solprop/backends/numpy_ref/tdse.py +++ b/src/solprop/backends/numpy_ref/tdse.py @@ -187,16 +187,8 @@ def atomic_edge_source( def _source_gate(problem: PropagationProblem) -> Float[np.ndarray, " n_to"]: """Return the temporal gate of a gated source. - Parameters - ---------- - problem : PropagationProblem - Problem whose descriptor may carry an atomic window. - - Returns - ------- - numpy.ndarray - The atomic window's gate when one is set, else the fine temporal collar, - shape ``(n_to,)``. + - Returns: the atomic window's gate when one is set, else the fine temporal collar, + shape ``(n_to,)``. """ descriptor = tdse_descriptor(problem.route) gate = None if descriptor is None else descriptor.source_gate() @@ -211,21 +203,8 @@ def _project_spectrum( ) -> Complex[np.ndarray, "n_modes 1 n_omega"]: """Apply the same modal projection and unscaled crop to either source. - Parameters - ---------- - values : numpy.ndarray - Nodal P or J source, shape ``(n_nodes, 1, n_to)``, in C/m² or A/m². - projection : numpy.ndarray - Modal weights, shape ``(n_modes, n_nodes)``. - problem : PropagationProblem - Crop indices of the field grid. - bundle : StageScalars - Stage taper scale. - - Returns - ------- - numpy.ndarray - Raw FFT source, shape ``(n_modes, 1, n_omega)`` before source factors. + - ``values``: nodal P or J source, shape ``(n_nodes, 1, n_to)``, in C/m² or A/m². + - Returns: raw FFT source, shape ``(n_modes, 1, n_omega)`` before source factors. """ projected = passes.project(values, projection, bundle.taper_scale, 1) return passes.to_frequency(projected, problem.grids.crop_index, kind="field") @@ -241,25 +220,12 @@ def _roundoff_floor( ) -> float: """Bound operand cancellation through absolute projection and Fourier sums. - Parameters - ---------- - magnitudes : numpy.ndarray - Sum of absolute microscopic operands after density and calibration, - shape ``(n_nodes, 1, n_to)``, in C/m² or A/m². - projection : numpy.ndarray - Modal weights, shape ``(n_modes, n_nodes)``. - factor : numpy.ndarray - P or J spectral factor including crop scale, shape - ``(n_modes, 1, n_omega)``. - problem : PropagationProblem - Spectral collar and transform size. - bundle : StageScalars - Stage taper scale. - - Returns - ------- - float - Nonnegative absolute rounding floor in spectral derivative norm units. + - ``magnitudes``: the sum of absolute microscopic operands after density and + calibration, in C/m² or A/m². + - ``factor``: P or J spectral factor including crop scale, shape ``(n_modes, 1, + n_omega)``. + - Returns: the nonnegative absolute rounding floor, in spectral derivative norm + units. """ scaled, accumulation = _roundoff_terms( magnitudes, projection, factor, problem, bundle @@ -275,27 +241,9 @@ def _roundoff_terms( problem: PropagationProblem, bundle: StageScalars, ) -> tuple[Float[np.ndarray, "n_modes 1 n_omega"], float]: - """Return the per-bin operand bound and the accumulation of :func:`_roundoff_floor`. - - Parameters - ---------- - magnitudes : numpy.ndarray - Absolute operands, shape ``(n_nodes, 1, n_to)``, as :func:`_roundoff_floor`. - projection : numpy.ndarray - Modal weights, shape ``(n_modes, n_nodes)``. - factor : numpy.ndarray - Spectral factor, shape ``(n_modes, 1, n_omega)``. - problem : PropagationProblem - Spectral collar and transform size. - bundle : StageScalars - Stage taper scale. + """Return the per-bin absolute operand bound and the relative accumulation. - Returns - ------- - scaled : numpy.ndarray - Per-bin absolute operand bound, shape ``(n_modes, 1, n_omega)``. - accumulation : float - Relative accumulation ``operations * eps``. + Both feed :func:`_roundoff_floor`; the accumulation is ``operations * eps``. """ absolute_projected = passes.project( magnitudes, np.abs(projection), abs(bundle.taper_scale), 1 @@ -319,17 +267,10 @@ def _roundoff_terms( def _inverse_i_omega(problem: PropagationProblem) -> Complex[np.ndarray, " n_omega"]: """Return ``1 / (i omega)`` on the active bins and zero elsewhere. - Parameters - ---------- - problem : PropagationProblem - Coarse frequencies in rad/s and the active mask; every active bin is above - ``omega_min / 2 > 0``, so no division by zero occurs. - - Returns - ------- - numpy.ndarray - ``-i / omega`` in s on active bins, exactly zero elsewhere, shape - ``(n_omega,)``. + - ``problem``: coarse frequencies in rad/s and the active mask; every active bin is + above ``omega_min / 2 > 0``, so no division by zero occurs. + - Returns: ``-i / omega`` in s on active bins, exactly zero elsewhere, shape + ``(n_omega,)``. """ grids = problem.grids inverse = np.zeros(grids.n_omega, dtype=np.complex128) @@ -405,24 +346,7 @@ def _current_part( problem: PropagationProblem, bundle: StageScalars, ) -> _CurrentPart: - """Project, transform and crop the current, and its edge term, under one map. - - Parameters - ---------- - operands : _CurrentOperands - The evaluation's nodal arrays and factors. - projection : numpy.ndarray - Modal weights, shape ``(n_modes, n_nodes)``: the fine or the embedded map. - problem : PropagationProblem - Crop, masks, collar and transform size. - bundle : StageScalars - Stage taper scale. - - Returns - ------- - _CurrentPart - Spectra, source and floors. - """ + """Project, transform and crop the current, and its edge term, under one map.""" factor = operands.factor truncating = _project_spectrum(operands.current, projection, problem, bundle) scaled, accumulation = _roundoff_terms( @@ -488,29 +412,12 @@ def _edge_rounding( # noqa: PLR0917 - called positionally The truncating part keeps :func:`_roundoff_floor`, unchanged; aliasing is not a rounding error and is governed by the certificate (section 4.4). - Parameters - ---------- - magnitudes : tuple of numpy.ndarray - ``|rho s| |g'| (|J| + |j_1|)`` and ``|rho s| delta g' (|J| + |j_1|)``, each - of shape ``(n_nodes, 1, n_to)``. - projection : numpy.ndarray - Modal weights, shape ``(n_modes, n_nodes)``. - factor : numpy.ndarray - Current factor, shape ``(n_modes, 1, n_omega)``. - spectra : tuple of numpy.ndarray - ``X``, the projected raw spectrum of the gated current, and ``E / (i - omega)``, of the edge array, each ``(n_modes, 1, n_omega)``. - problem : PropagationProblem - Coarse frequencies, masks, collar and transform size. - bundle : StageScalars - Stage taper scale. - - Returns - ------- - edge : numpy.ndarray - Bound of the edge transform and the derivative evaluation, per bin. - division : numpy.ndarray - Bound of the division, subtraction and factor, per bin. + - ``magnitudes``: ``|rho s| |g'| (|J| + |j_1|)`` and ``|rho s| delta g' (|J| + + |j_1|)``, each of shape ``(n_nodes, 1, n_to)``. + - ``spectra``: ``X``, the projected raw spectrum of the gated current, and ``E / (i + omega)``, of the edge array, each ``(n_modes, 1, n_omega)``. + - Returns: the bound of the edge transform and the derivative evaluation, and the + bound of the division, subtraction and factor. """ grids = problem.grids edge_magnitude, derivative_magnitude = magnitudes @@ -542,17 +449,7 @@ def _ratio( ) -> tuple[float | None, str | None]: """Return a ratio, or None with its reason when the denominator is noise. - Parameters - ---------- - numerator, denominator : float - Nonnegative norms. - floor : float - Rounding floor of the denominator. - - Returns - ------- - tuple - The ratio and None, or None and ``"denominator below rounding floor"``. + - Returns: the ratio and None, or None and ``"denominator below rounding floor"``. """ if denominator <= floor: return None, "denominator below rounding floor" @@ -573,22 +470,12 @@ def _band_entry( ) -> dict[str, object]: """Describe the edge term over one band, with explicit denominators and floors. - Parameters - ---------- - spectra : tuple of numpy.ndarray - ``c X`` and ``c E / (i omega)``, the truncating source and the edge term - after the current factor and the spectral collar. - bounds : tuple of numpy.ndarray - Per-bin rounding bounds of ``c X``, of the edge term and of the division and - subtraction. - band : numpy.ndarray - Coarse bins of the band. - - Returns - ------- - dict - Norms of the truncating, edge and corrected sources, their floors and the two - ratios, each None with a reason when its denominator is at or below its floor. + - ``spectra``: ``c X`` and ``c E / (i omega)``, the truncating source and the edge + term after the current factor and the spectral collar. + - ``bounds``: Per-bin rounding bounds of ``c X``, of the edge term and of the + division and subtraction. + - Returns: norms of the truncating, edge and corrected sources, their floors and the + two ratios, each None with a reason when its denominator is at or below its floor. """ truncating, edge = spectra truncating_bins, edge_bins, division_bins = bounds @@ -635,21 +522,9 @@ def _projection_comparison( ) -> dict[str, object]: """Describe one embedded comparison without dividing by cancellation noise. - Parameters - ---------- - fine : numpy.ndarray - Fine modal source after the spectral collar, shape - ``(n_modes, 1, n_omega)``. - coarse : numpy.ndarray or None - Embedded source on the same coarse spectral bins, or None if absent. - fine_floor, coarse_floor : float - Nonnegative absolute rounding bounds in spectral derivative norm units. - - Returns - ------- - dict - Norms, differences, combined absolute floor and a relative difference - only when a distinct embedded source exists above the floor. + - ``coarse``: embedded source on the same coarse spectral bins, or None if absent. + - Returns: norms, differences, combined absolute floor and a relative difference + only when a distinct embedded source exists above the floor. """ norm = float(np.linalg.norm(fine)) floor = fine_floor + coarse_floor @@ -686,23 +561,11 @@ def _leading_taper( above by the absolute projection of ``|rho s S|``: it bounds the leading edge's share of the edge term over the band. - Parameters - ---------- - result : TDSEResult - Unscaled microscopic histories. - problem : PropagationProblem - Gate breakpoints, lattice and projection. - bundle : StageScalars - Density and taper scale. - band_weights : dict - ``c_k / omega_k`` on each band's bins, zero elsewhere, by band name. - - Returns - ------- - dict - ``ratio`` and its ``reason`` (``"zero remainder"`` when ``max |S| = 0``, or - ``"empty leading taper"``), the sampled weight ``W_- / dt`` and the bound per - band. + - ``band_weights``: ``c_k / omega_k`` on each band's bins, zero elsewhere, by band + name. + - Returns: ``ratio`` and its ``reason`` (``"zero remainder"`` when ``max |S| = 0``, + or ``"empty leading taper"``), the sampled weight ``W_- / dt`` and the bound per + band. """ descriptor = tdse_descriptor(problem.route) assert descriptor is not None @@ -757,27 +620,12 @@ def _edge_diagnostic( # noqa: PLR0917 - called positionally ) -> dict[str, object]: """Describe the last evaluation's edge term on the active and in-band bins. - Parameters - ---------- - spectra : tuple of numpy.ndarray - Projected raw spectra ``X`` of the gated current and ``E / (i omega)`` of the - edge array, before the current factor. - bounds : tuple of numpy.ndarray - Per-bin rounding bounds, after the factor and collar, of ``c X``, of the edge - term and of the division and subtraction. - factor : numpy.ndarray - Current factor ``current_numerator / Re n_eff``. - result : TDSEResult - Unscaled microscopic histories, for ``rho_-``. - problem : PropagationProblem - Masks, collar, lattice and descriptor. - bundle : StageScalars - Density and taper scale. - - Returns - ------- - dict - JSON-compatible record of SEP 26 section 5. A diagnostic, never a gate. + - ``spectra``: projected raw spectra ``X`` of the gated current and ``E / (i + omega)`` of the edge array, before the current factor. + - ``bounds``: Per-bin rounding bounds, after the factor and collar, of ``c X``, of + the edge term and of the division and subtraction. + - Returns: the JSON-compatible record of SEP 26 section 5; a diagnostic, never a + gate. """ descriptor, grids = tdse_descriptor(problem.route), problem.grids assert descriptor is not None @@ -805,16 +653,8 @@ def _edge_diagnostic( # noqa: PLR0917 - called positionally def _smooth(samples: int) -> bool: """Return whether a transform size is 2-, 3- and 5-smooth. - Parameters - ---------- - samples : int - Positive transform size. - - Returns - ------- - bool - True when ``samples`` has no prime factor above 5, the sizes for which - ``FFT_ERROR_CONSTANT`` is assumed. + - Returns: True when ``samples`` has no prime factor above 5, the sizes for which + ``FFT_ERROR_CONSTANT`` is assumed. """ for prime in (2, 3, 5): while samples % prime == 0: @@ -966,32 +806,7 @@ def _current_operands( problem: PropagationProblem, bundle: StageScalars, ) -> _CurrentOperands: - """Gather one evaluation's current operands and check the edge array. - - Parameters - ---------- - current : numpy.ndarray - Scaled (gated) remainder current, shape ``(n_nodes, 1, n_to)``. - edge : numpy.ndarray or None - Scaled ``g' S``: required for a gated source, refused for an ungated one. - result : TDSEResult - Unscaled microscopic operands. - problem : PropagationProblem - Descriptor, transform and current numerator. - bundle : StageScalars - Density, scale, position and taper scale. - - Returns - ------- - _CurrentOperands - The operands shared by the fine and embedded projections. - - Raises - ------ - BackendError - If the edge array's presence, shape or finiteness is wrong, or a cubic - replacement lacks its third-order operand. - """ + """Gather one evaluation's current operands and check the edge array.""" source = tdse_of(problem.route) assert source is not None descriptor = source.descriptor diff --git a/src/solprop/backends/populations.py b/src/solprop/backends/populations.py index 49d88d205..f894ea970 100644 --- a/src/solprop/backends/populations.py +++ b/src/solprop/backends/populations.py @@ -280,19 +280,10 @@ def _scan_axis_row( ) -> tuple[Float[np.ndarray, " n_channels"], Float[np.ndarray, " n_channels"]]: """Scan one row of the field in time: every channel, with the node scan's tables. - Parameters - ---------- - problem : PropagationProblem - The translated problem. - row : numpy.ndarray - The field on the axis on the fine time grid in V/m. - z : float - Plane position in m. - - Returns - ------- - tuple of numpy.ndarray - The final ionised fraction and the largest rate on the axis, one per channel. + - ``row``: the field on the axis on the fine time grid in V/m. + - ``z``: plane position in m. + - Returns: the final ionised fraction and the largest rate on the axis, one per + channel. """ responses, grids = problem.responses, problem.grids preionisation = tuple( diff --git a/src/solprop/backends/protocol.py b/src/solprop/backends/protocol.py index d1ac0cc4d..1a737693e 100644 --- a/src/solprop/backends/protocol.py +++ b/src/solprop/backends/protocol.py @@ -164,13 +164,8 @@ def __post_init__(self) -> None: raise BackendError(msg) @property - def n_steps(self) -> int: - """Return the number of steps recorded, accepted and rejected together. - - Returns - ------- - int - The length of ``z``. + def n_steps(self) -> int: # numpydoc ignore=RT01 + """Number of steps recorded, accepted and rejected together. Examples -------- @@ -183,13 +178,8 @@ def n_steps(self) -> int: return int(self.z.size) @property - def n_rejected(self) -> int: - """Return the number of rejected steps. - - Returns - ------- - int - How many entries of ``accepted`` are false. + def n_rejected(self) -> int: # numpydoc ignore=RT01 + """Number of rejected steps. Examples -------- @@ -304,25 +294,13 @@ def empty(cls) -> "StepStats": return cls(hold(np.empty(0)), hold(np.empty(0, dtype=np.int64)), {}) @property - def n_rows(self) -> int: - """Return how many rows were taken since the previous observation. - - Returns - ------- - int - The row count. - """ + def n_rows(self) -> int: # numpydoc ignore=RT01 + """How many rows were taken since the previous observation.""" return int(self.z.size) @property - def names(self) -> tuple[str, ...]: - """Return the statistics the channel carries, sorted. - - Returns - ------- - tuple of str - The catalogue names. - """ + def names(self) -> tuple[str, ...]: # numpydoc ignore=RT01 + """The statistics the channel carries, sorted.""" return tuple(sorted(self.values)) @@ -333,15 +311,8 @@ def _all_finite(state: Complex[np.ndarray, "*shape n_omega"]) -> bool: pass over the rows. Only a sum that overflows takes the second look, which allows for finite entries too large to add. - Parameters - ---------- - state : numpy.ndarray - A contiguous complex state. - - Returns - ------- - bool - True when no entry is a NaN or an infinity. + - ``state``: a contiguous complex state. + - Returns: True when no entry is a NaN or an infinity. """ if state.size == 0: return True @@ -350,15 +321,7 @@ def _all_finite(state: Complex[np.ndarray, "*shape n_omega"]) -> bool: def finite(selected: slice) -> bool: """Return whether the selected rows are finite. - Parameters - ---------- - selected : slice - The rows. - - Returns - ------- - bool - True when none holds a NaN or an infinity. + - Returns: True when none holds a NaN or an infinity. """ part = rows[selected] # Per thread: a sum that overflows is what the second look is for. @@ -510,13 +473,7 @@ def require_field(self) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: return self.field def _check_counts(self) -> None: - """Check the step counts against the history and the rows against both. - - Raises - ------ - BackendError - Naming the disagreement. - """ + """Check the step counts against the history and the rows against both.""" if self.n_steps != self.step_history.n_steps: msg = ( f"n_steps is {self.n_steps} but the history records " @@ -696,8 +653,8 @@ class Backend(Protocol): reads_transverse_edge: bool @property - def request(self) -> "RunRequest": - """Return the run request the backend was built for. + def request(self) -> "RunRequest": # numpydoc ignore=RT01 + """Run request the backend was built for. Its method, error control, itinerary, planes and statistics; a run adapts it with :meth:`~solprop.translate.run_request.RunRequest.for_run`. @@ -848,14 +805,8 @@ def close(self) -> None: ... @property - def closed(self) -> bool: - """Return whether the backend has released its buffers, as a failure may. - - Returns - ------- - bool - ``True`` when it can run no other input. - """ + def closed(self) -> bool: # numpydoc ignore=RT01 + """Whether the backend has released its buffers, as a failure may.""" ... @@ -988,13 +939,10 @@ def observe(self, *, field: bool = True) -> Observation: ... @property - def in_flight(self) -> tuple[float, float] | None: - """Return the low-storage step a failure abandoned in flight, or ``None``. + def in_flight(self) -> tuple[float, float] | None: # numpydoc ignore=RT01 + """Low-storage step a failure abandoned in flight, or ``None``. - Returns - ------- - tuple of float or None - The step's start and length, m, while the state is neither of its ends. + The step's start and length, m, while the state is neither of its ends. """ ... @@ -1121,27 +1069,20 @@ def attach_monitors(self, monitors: Sequence[BoundaryMonitor | None]) -> None: ... @property - def member_stops(self) -> tuple[BoundaryError | BoundaryWarning | None, ...]: - """Return what stopped each member's watch, ``None`` where nothing did. + def member_stops( + self, + ) -> tuple[BoundaryError | BoundaryWarning | None, ...]: # numpydoc ignore=RT01 + """What stopped each member's watch, ``None`` where nothing did. A stop, or a warning raised as an error under an ``"error"`` filter. - - Returns - ------- - tuple - One entry per member, in member order. """ ... @property - def member_records(self) -> tuple[Mapping[str, object] | None, ...]: - """Return each member's monitor record as it closed, ``None`` if unwatched. - - Returns - ------- - tuple - One entry per member, in member order. - """ + def member_records( + self, + ) -> tuple[Mapping[str, object] | None, ...]: # numpydoc ignore=RT01 + """Each member's monitor record as it closed, ``None`` if unwatched.""" ... def finish_monitors(self) -> None: diff --git a/src/solprop/backends/registry.py b/src/solprop/backends/registry.py index 8efc6134a..374c5cc7d 100644 --- a/src/solprop/backends/registry.py +++ b/src/solprop/backends/registry.py @@ -175,15 +175,7 @@ class BackendEntry: def _load(path: str) -> object: """Import the object a registry path names. - Parameters - ---------- - path : str - ``"module:attribute"``, the attribute dotted for one inside a class. - - Returns - ------- - object - The object. + ``path`` is ``"module:attribute"``, the attribute dotted for one inside a class. """ module, _, attribute = path.partition(":") found: object = importlib.import_module(module) @@ -239,18 +231,10 @@ def statistics_refusal(name: str, names: Sequence[str]) -> str | None: def _plugin_far_field(name: str) -> frozenset[str] | None: """Return the far-field statistics an installed plugin backend declares. - Parameters - ---------- - name : str - A registry name. - - Returns - ------- - frozenset of str or None - What the plugin declares (its row's ``far_field``: the native backend's - ``NativeBackend.far_field_statistics``), empty for a build that predates the - attribute, or ``None`` when the name is not a known, installed plugin that - declares them. + - Returns: what the plugin declares (its row's ``far_field``: the native backend's + ``NativeBackend.far_field_statistics``), empty for a build that predates the + attribute, or ``None`` when the name is not a known, installed plugin that + declares them. """ entry = BACKENDS.get(name) if entry is None or entry.far_field is None or not _satisfied(entry): @@ -357,18 +341,7 @@ def resolve_name(name: str = "auto") -> str: def _device_build(name: str) -> bool: - """Return whether a backend steps on an accelerator in this installation. - - Parameters - ---------- - name : str - A backend's name, or ``"auto"``. - - Returns - ------- - bool - True for a backend built for a device: ``native`` built for CUDA or HIP. - """ + """Return whether a backend steps on an accelerator in this installation.""" entry = BACKENDS.get(resolve_name(name)) if entry is None or entry.device is None: return False @@ -378,15 +351,8 @@ def _device_build(name: str) -> bool: def _devices(entry: BackendEntry) -> tuple[str, int]: """Return where a backend's installed build runs, from its row's ``device``. - Parameters - ---------- - entry : BackendEntry - A row with a ``device``. - - Returns - ------- - tuple of (str, int) - The build's device backend, ``"none"`` on a CPU build, and the devices it sees. + - Returns: the build's device backend, ``"none"`` on a CPU build, and the devices it + sees. """ info = cast("Callable[[], object]", _load(cast(str, entry.device)))() backend = cast(str, getattr(info, "device_backend")) # noqa: B009 - any build's @@ -399,16 +365,6 @@ def _check_required_device(name: str) -> None: This reads the actual build, never a version label, and runs before any state is allocated: a job set up for a GPU should stop, not run for days on a CPU wheel that replaced its build. - - Parameters - ---------- - name : str - The resolved backend name. - - Raises - ------ - BackendError - If the value isn't an accepted one, or the backend or build doesn't meet it. """ value = os.environ.get(REQUIRE_DEVICE_SETTING, "").strip() if not value: @@ -466,11 +422,8 @@ def _native_baseline_shortfall() -> ImportError | None: ``__init__`` refuses a processor below the baseline and loads the extension), and asked the question the package asks at import. - Returns - ------- - ImportError or None - The package's own refusal, or ``None`` when the processor meets the baseline or - the build records none to check. + - Returns: the package's own refusal, or ``None`` when the processor meets the + baseline or the build records none to check. """ spec = importlib.util.find_spec("solprop_native") locations = [] if spec is None else (spec.submodule_search_locations or []) @@ -486,15 +439,7 @@ def _native_baseline_shortfall() -> ImportError | None: def _baseline_probe(source: Path) -> ModuleType: """Load the native package's processor check from its file, as its own module. - Parameters - ---------- - source : pathlib.Path - ``solprop_native/_cpu.py`` of the installed package. - - Returns - ------- - types.ModuleType - The module, under a private name so that it never stands in for the package's. + Under a private name, so that it never stands in for the package's own module. """ spec = importlib.util.spec_from_file_location("_solprop_native_cpu_probe", source) if spec is None or spec.loader is None: # pragma: no cover - a .py file loads @@ -670,15 +615,7 @@ def select_batch(owners: Sequence[Backend]) -> BatchBackend: def _satisfied(entry: BackendEntry) -> bool: """Return whether every module an entry requires can be found. - Parameters - ---------- - entry : BackendEntry - The table row. - - Returns - ------- - bool - True when nothing is missing. + - Returns: True when nothing is missing. """ return all( importlib.util.find_spec(module) is not None for module in entry.requires @@ -698,34 +635,8 @@ def _construct( ) -> Backend: """Import and build the named backend, whose requirements are already met. - Parameters - ---------- - name : str - The backend's registry name. - problem : solprop.translate.problem.PropagationProblem - The problem to step. - request : solprop.translate.run_request.RunRequest - The run's policies. - grid : solprop.grid.TimeGrid or None - The grid the state lives on. - modes : collections.abc.Sequence or None - The propagated modes. - profiles : collections.abc.Mapping or None - The z-profiles keyed by statistic name. - tdse_binding : TDSEBinding or None, optional - Driver-owned atomic runtime, passed only to the NumPy backend. - options : solprop.backends.session.ExecutionOptions or None, optional - The run's execution options. - - Returns - ------- - Backend - The constructed backend. - - Raises - ------ - ImportError - If the name is known and satisfied but no module implements it yet. + - ``profiles``: the z-profiles keyed by statistic name. + - ``tdse_binding``: Driver-owned atomic runtime, passed only to the NumPy backend. """ entry = BACKENDS.get(name) if entry is None: diff --git a/src/solprop/backends/session.py b/src/solprop/backends/session.py index dbc9c8101..39444a5fa 100644 --- a/src/solprop/backends/session.py +++ b/src/solprop/backends/session.py @@ -258,25 +258,13 @@ def __post_init__(self) -> None: object.__setattr__(self, name, positive_count(name, value)) @property - def budget(self) -> int | None: - """Return the explicit node budget as an ``int``, or ``None``. - - Returns - ------- - int or None - :attr:`node_budget`, which construction already made an ``int``. - """ + def budget(self) -> int | None: # numpydoc ignore=RT01 + """Explicit node budget as an ``int``, or ``None``.""" return None if self.node_budget is None else operator.index(self.node_budget) @property - def chunk(self) -> int | None: - """Return the exact node chunk as an ``int``, or ``None``. - - Returns - ------- - int or None - :attr:`node_chunk`, which construction already made an ``int``. - """ + def chunk(self) -> int | None: # numpydoc ignore=RT01 + """Exact node chunk as an ``int``, or ``None``.""" return None if self.node_chunk is None else operator.index(self.node_chunk) @@ -646,13 +634,13 @@ class RunSession(Protocol): """ @property - def finished(self) -> bool: - """Return whether a terminal event was yielded.""" + def finished(self) -> bool: # numpydoc ignore=RT01 + """Whether a terminal event was yielded.""" ... @property - def closed(self) -> bool: - """Return whether the session was closed.""" + def closed(self) -> bool: # numpydoc ignore=RT01 + """Whether the session was closed.""" ... def status(self) -> SessionStatus: @@ -841,18 +829,18 @@ class GroupSession(Protocol): """ @property - def n_members(self) -> int: - """Return the number of members.""" + def n_members(self) -> int: # numpydoc ignore=RT01 + """Number of members.""" ... @property - def finished(self) -> bool: - """Return whether every member has ended.""" + def finished(self) -> bool: # numpydoc ignore=RT01 + """Whether every member has ended.""" ... @property - def closed(self) -> bool: - """Return whether the group was closed.""" + def closed(self) -> bool: # numpydoc ignore=RT01 + """Whether the group was closed.""" ... def advance(self) -> GroupBatch: @@ -991,8 +979,8 @@ class PreparedExecution(Protocol): """A backend's lowering of one problem and request, reusable across sessions.""" @property - def backend(self) -> str: - """Return the registered name of the backend that prepared it.""" + def backend(self) -> str: # numpydoc ignore=RT01 + """Registered name of the backend that prepared it.""" ... def start( @@ -1047,8 +1035,8 @@ class PreparedGroup(Protocol): """ @property - def backend(self) -> str: - """Return the registered name of the backend that prepared it.""" + def backend(self) -> str: # numpydoc ignore=RT01 + """Registered name of the backend that prepared it.""" ... def start( @@ -1253,23 +1241,11 @@ def close(self) -> bool: return True @property - def pending(self) -> Receipt | None: - """Return the pending batch's receipt. - - Returns - ------- - solprop.backends.events.Receipt or None - The receipt, or ``None`` when nothing is pending. - """ + def pending(self) -> Receipt | None: # numpydoc ignore=RT01 + """Pending batch's receipt, or ``None``.""" return self._pending @property - def finished(self) -> bool: - """Return whether a terminal batch was issued. - - Returns - ------- - bool - True once the session yielded its terminal event. - """ + def finished(self) -> bool: # numpydoc ignore=RT01 + """Whether a terminal batch was issued.""" return self.outcome is not None diff --git a/src/solprop/backends/spatial.py b/src/solprop/backends/spatial.py index 43085dfce..d0463fb31 100644 --- a/src/solprop/backends/spatial.py +++ b/src/solprop/backends/spatial.py @@ -168,22 +168,10 @@ def _blockwise_fluence( ) -> Float[np.ndarray, " n_nodes"]: """Accumulate each node's fluence, up to a common factor, by block of frequency. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - A Cartesian or planar problem. - field : numpy.ndarray - One internal spatial spectrum. - dt : float - Coarse sample spacing, s. - node_chunk : int - The node chunk that bounds a block. - - Returns - ------- - numpy.ndarray - ``sum over polarisation and frequency of w(omega) |E|^2`` at each node, the - quantity :func:`solprop.fourier.energy_omega` sums, shape ``(n_nodes,)``. + - ``field``: one internal spatial spectrum. + - ``dt``: coarse sample spacing, s. + - Returns: ``sum over polarisation and frequency of w(omega) |E|^2`` at each node, + the quantity :func:`solprop.fourier.energy_omega` sums. """ transform = problem.transform n_omega = field.shape[-1] @@ -684,34 +672,11 @@ def _local_statistics( ) -> dict[str, np.ndarray]: """Form the peak local axial flux and the fluence widths, node chunk by chunk. - Parameters - ---------- - wanted : set of str - Requested catalogue names. - grid : solprop.grid.TimeGrid - Temporal transform and energy measure. - transform : solprop.translate.modal.Transform - The run's synthesis and geometry. - field : numpy.ndarray - One internal spectrum, shape ``(n_modes, n_pol, n_omega)``. - closure : numpy.ndarray - Its magnetic closure ``G / (eps0 c)``, same shape. - physical : numpy.ndarray or None - A compiled backend's borrowed physical-space field, or None. - axis_fluence : float - The on-axis fluence in J/m^2, for the radial widths. - chunk : int - The backend's node chunk. - - Returns - ------- - dict - ``peak_intensity``, ``beam_radius`` and ``fwhm_r`` where requested. - - Raises - ------ - BackendError - If a width is asked of a fluence negative beyond its rounding budget. + - ``field``: one internal spectrum, shape ``(n_modes, n_pol, n_omega)``. + - ``closure``: its magnetic closure ``G / (eps0 c)``, same shape. + - ``physical``: a compiled backend's borrowed physical-space field, or None. + - ``axis_fluence``: the on-axis fluence in J/m^2, for the radial widths. + - Returns: ``peak_intensity``, ``beam_radius`` and ``fwhm_r`` where requested. """ geometry = transform.geometry assert geometry is not None @@ -919,17 +884,7 @@ def plane_fluence_widths( def _negative_fluence(value: float, node: int) -> BackendError: """Return the refusal of a fluence negative beyond its rounding budget. - Parameters - ---------- - value : float - The fluence in J/m^2. - node : int - Its node. - - Returns - ------- - solprop.exceptions.BackendError - The refusal, to raise. + - ``value``: the fluence in J/m^2. """ return BackendError( "beam_radius and fwhm_r need a nonnegative fluence profile, and the " @@ -947,23 +902,10 @@ def _rows( ) -> np.ndarray: """Return one node chunk of a spectrum's physical field. - Parameters - ---------- - transform : solprop.translate.modal.Transform - The run's synthesis. - grid : solprop.grid.TimeGrid - The temporal grid, whose pad scale the synthesis carries. - source : numpy.ndarray - Internal spectrum, shape ``(n_modes, n_pol, n_omega)``. - whole : numpy.ndarray or None - Its already synthesised physical volume, or None to synthesise the rows. - rows : slice - The node chunk. - - Returns - ------- - numpy.ndarray - Physical spectra in V/m at the chunk's nodes. + - ``source``: internal spectrum, shape ``(n_modes, n_pol, n_omega)``. + - ``whole``: its already synthesised physical volume, or None to synthesise the + rows. + - Returns: physical spectra in V/m at the chunk's nodes. """ if whole is not None: return whole[rows] @@ -985,33 +927,8 @@ def _far_field( ) -> dict[str, np.ndarray]: """Form the requested far-field statistics in one chunked pass. - Parameters - ---------- - names : set of str - The statistics asked for; those that are not far-field statistics are left - to the other passes. - grid : solprop.grid.TimeGrid - The temporal grid. - field : numpy.ndarray - One internal spectrum, shape ``(n_x*n_y, n_pol, n_omega)``. - tables : solprop.spatial.ApertureTables or None - The far-field tables. - plane : solprop.translate.spatial.SeparableOperator or None - The plane's coefficients, whose root weighs every statistic. - operator : solprop.translate.spatial.SeparableOperator or None - The run's separable operator, whose rates ``absorbed_power`` reads. - chunk : int - The backend's node chunk, which bounds the pass's chunks. - - Returns - ------- - dict - The requested far-field statistics, empty when none is asked for. - - Raises - ------ - BackendError - If the tables, or the operator an operator statistic needs, are missing. + - ``field``: one internal spectrum, shape ``(n_x*n_y, n_pol, n_omega)``. + - Returns: the requested far-field statistics, empty when none is asked for. """ names = names & set(FAR_FIELD_STATISTICS) if not names: @@ -1034,15 +951,7 @@ def _far_field( def axial(columns: slice) -> tuple[np.ndarray, np.ndarray]: """Return the plane's admittance and the operator's rates for a chunk. - Parameters - ---------- - columns : slice - The transverse columns of the chunk. - - Returns - ------- - tuple of numpy.ndarray - The complex admittance and the four absorption rates. + - Returns: the complex admittance and the four absorption rates. """ _, rates = axial_factors(rated, grid.omega, columns) return axial_admittance(plane, grid.omega, columns), rates @@ -1060,22 +969,10 @@ def _axis_statistics( ) -> tuple[dict[str, np.ndarray], float]: """Reduce the requested statistics of the on-axis spectrum. - Parameters - ---------- - wanted : set of str - Requested catalogue names. - grid : solprop.grid.TimeGrid - Temporal transform and energy measure. - axis : numpy.ndarray - The on-axis amplitude, the on-axis weights contracted with the spectrum, - shape ``(n_pol, n_omega)``. - closure : numpy.ndarray - The on-axis magnetic closure ``G / (eps0 c)``, same shape. - - Returns - ------- - tuple of dict and float - Requested on-axis statistics and axial fluence in J/m^2. + - ``axis``: the on-axis amplitude, the on-axis weights contracted with the spectrum, + shape ``(n_pol, n_omega)``. + - ``closure``: the on-axis magnetic closure ``G / (eps0 c)``, same shape. + - Returns: requested on-axis statistics and axial fluence in J/m^2. """ conversion = 0.5 * EPS0 * C factor = fourier.spectral_density( @@ -1101,20 +998,10 @@ def _free_physical( ) -> np.ndarray | None: """Reconstruct one coarse spectral volume for Cartesian or planar statistics. - Parameters - ---------- - transform : solprop.translate.modal.Transform - Plain Cartesian axes and FFT shape, the planar slab matrices and y axis, or - another transform kind. - field : numpy.ndarray - Internal spectrum, with flattened transverse columns first. - pad_scale : float - The temporal pad scale a slab synthesis carries, divided out here. - - Returns - ------- - numpy.ndarray or None - Physical coarse spectra in V/m at the nodes, or None for a dense transform. + - ``transform``: plain Cartesian axes and FFT shape, the planar slab matrices and y + axis, or another transform kind. + - Returns: physical coarse spectra in V/m at the nodes, or None for a dense + transform. """ geometry = transform.geometry if geometry is None or geometry.kind not in ("free", "slab"): @@ -1238,19 +1125,10 @@ def slab_projection( def _slab_widths(geometry: SpatialGeometry, fluence: np.ndarray) -> tuple[float, ...]: """Measure a planar beam's widths in y from its fluence integrated over x. - Parameters - ---------- - geometry : solprop.translate.spatial.SpatialGeometry - ``"slab"`` geometry, whose node weights carry the x rule. - fluence : numpy.ndarray - Fluence in J/m^2 at the nodes ``ix + n_x*iy``. - - Returns - ------- - tuple of float - The line widths in y (:func:`solprop.spatial.cartesian_fluence_widths` on - one row): the moment half-width, the 1/e^2 half-width and the FWHM, in m, - of the fluence integrated across the gap. + - ``fluence``: fluence in J/m^2 at the nodes ``ix + n_x*iy``. + - Returns: the line widths in y (:func:`solprop.spatial.cartesian_fluence_widths` on + one row): the moment half-width, the 1/e^2 half-width and the FWHM, in m, of the + fluence integrated across the gap. """ n_x, n_y = geometry.shape across = np.sum((geometry.spatial_weights * fluence).reshape(n_y, n_x), axis=1) diff --git a/src/solprop/boundaries.py b/src/solprop/boundaries.py index 4199d0c63..653c610ba 100644 --- a/src/solprop/boundaries.py +++ b/src/solprop/boundaries.py @@ -346,13 +346,7 @@ def __post_init__(self) -> None: self._check_user_rate() def _check_user_rate(self) -> None: - """Check that the user's rate is a share of the total spectral rate. - - Raises - ------ - GridError - If it is misshaped, not finite, negative or above the total. - """ + """Check that the user's rate is a share of the total spectral rate.""" if self.user_rate is None: return share = np.asarray(self.user_rate, dtype=np.float64) @@ -370,13 +364,10 @@ def _check_user_rate(self) -> None: raise GridError(msg) @property - def in_stage_length(self) -> float: - """Return the length that sets the in-stage collars' strength. + def in_stage_length(self) -> float: # numpydoc ignore=RT01 + """Length that sets the in-stage collars' strength, m. - Returns - ------- - float - `collar_length`, or `reference_length` when it is ``None``, m. + `collar_length`, or `reference_length` when it is ``None``. Examples -------- @@ -394,17 +385,7 @@ def in_stage_length(self) -> float: def _check_length(name: str, length: float) -> None: """Refuse a collar length that is not positive and finite. - Parameters - ---------- - name : str - The field's name, for the message. - length : float - The length, m. - - Raises - ------ - GridError - If the length is not positive and finite. + - ``length``: the length, m. """ if not (math.isfinite(length) and length > 0.0): msg = ( @@ -534,27 +515,11 @@ def _reference_length( ) -> float: """Return the absorber reference length in metres. - Parameters - ---------- - grid : TimeGrid - The grid, for its propagation length. - boundary_n : float - Applications of the collar profile over the whole propagation. - boundary_length : float or None - An explicit reference length in metres, which wins if given. - - Returns - ------- - float - The reference length in metres. + The result is checked positive and finite here, so that every caller is covered: a + zero length makes the rate ``0/0`` in the interior and fills the grid with NaN, and + a negative one inverts the clamp, so the absorber amplifies everywhere. - Raises - ------ - GridError - If the result would not be positive and finite. This is checked here so that - every caller is covered: a zero length makes the rate ``0/0`` in the interior - and fills the grid with NaN, and a negative one inverts the clamp so that the - absorber amplifies everywhere rather than absorbing in the collar. + - ``boundary_length``: an explicit reference length in metres, which wins if given. """ if boundary_length is not None: if not (math.isfinite(boundary_length) and boundary_length > 0.0): @@ -574,22 +539,8 @@ def _check_collar( ) -> None: """Reject a collar whose edges are too narrow to be a taper on this axis. - Parameters - ---------- - name : str - The collar's name, for the message. - collar : Collar - The geometry to check. - axis : numpy.ndarray - The axis the collar is evaluated on, shape ``(n,)``. - units : str - The axis units, for the message. - - Raises - ------ - GridError - If either edge has non-positive width, or is resolved by fewer than - ``MIN_COLLAR_SAMPLES`` samples. + - ``axis``: the axis the collar is evaluated on, shape ``(n,)``. + - ``units``: the axis units, for the message. """ span = float(np.max(axis) - np.min(axis)) for edge, width, low, high in ( @@ -631,22 +582,8 @@ def _walkoff_collar_length(grid: TimeGrid, walkoff: float) -> float: grid. For two edges of width ``w`` the Planck taper's symmetry makes it ``w``, giving ``ell_c = 6.3 w L / (Gamma dtau)``. - Parameters - ---------- - grid : TimeGrid - The grid, for its temporal collar and its propagation length. - walkoff : float - The drift over ``grid.length``, s. - - Returns - ------- - float - The collar length in metres. - - Raises - ------ - GridError - If the drift is not positive and finite, or the length it asks for is not. + - ``walkoff``: the drift over ``grid.length``, s. + - Returns: the collar length in metres. """ if not (math.isfinite(walkoff) and walkoff > 0.0): msg = ( @@ -1090,19 +1027,8 @@ def occupancy( re, im = np.real(spectrum), np.imag(spectrum) weights = None if columns is None else np.asarray(columns, dtype=np.float64) - def block(rows: slice) -> Float[np.ndarray, " n_buckets"]: - """Return a block of modes' energy per bucket. - - Parameters - ---------- - rows : slice - The block of modes. - - Returns - ------- - numpy.ndarray - The block's energy in each bucket. - """ + def _block(rows: slice) -> Float[np.ndarray, " n_buckets"]: + """Return a block of modes' energy per bucket.""" energy = np.einsum("bmpw,bmpw->mw", re[:, rows], re[:, rows]) + np.einsum( "bmpw,bmpw->mw", im[:, rows], im[:, rows] ) @@ -1114,7 +1040,7 @@ def block(rows: slice) -> Float[np.ndarray, " n_buckets"]: minlength=SPLIT_RATE_BUCKETS, ).astype(np.float64) - return np.sum(parallel_rows(spectrum.shape[1], spectrum.nbytes, block), axis=0) + return np.sum(parallel_rows(spectrum.shape[1], spectrum.nbytes, _block), axis=0) def limit(self, occupancy: Float[np.ndarray, " n_buckets"]) -> float: """Return the crossing limit of an occupancy, m. @@ -1467,15 +1393,7 @@ def warn_walkoff( def _log_summary(grid: TimeGrid, boundary: Absorber) -> None: - """Record the absorber's strength and geometry at debug level. - - Parameters - ---------- - grid : TimeGrid - The grid the absorber was built from. - boundary : Absorber - The finished absorber. - """ + """Record the absorber's strength and geometry at debug level.""" if logger.isEnabledFor(logging.DEBUG): collar = grid.t_collar window = float(np.max(grid.t) - np.min(grid.t)) diff --git a/src/solprop/calibration/_native.py b/src/solprop/calibration/_native.py index d7b99f5d6..7d0251859 100644 --- a/src/solprop/calibration/_native.py +++ b/src/solprop/calibration/_native.py @@ -78,16 +78,6 @@ def packages() -> tuple[Path, ...]: An editable install keeps its sources and its built files (the extension, ``_build.json``) in separate directories; a wheel has one. - - Returns - ------- - tuple of pathlib.Path - The package's directories, in import order. - - Raises - ------ - BackendError - When solprop-native is not installed. """ spec = importlib.util.find_spec("solprop_native") if spec is None or not spec.submodule_search_locations: @@ -102,11 +92,6 @@ def variant() -> str: Asked of the directory that holds the package's sources, as its ``__init__`` asks it of its own. - - Returns - ------- - str - The extension's stem, ``"_core"`` for a build with one. """ for directory in packages(): if (directory / "_cpu.py").is_file(): @@ -115,23 +100,7 @@ def variant() -> str: def __getattr__(name: str) -> ModuleType: - """Load one of the package's pure modules under the private package. - - Parameters - ---------- - name : str - One of the pure modules, without its underscore. - - Returns - ------- - types.ModuleType - The module. - - Raises - ------ - AttributeError - For any other name. - """ + """Load one of the package's pure modules under the private package.""" if name not in _MODULES: raise AttributeError(f"module {__name__!r} has no attribute {name!r}") if _PACKAGE not in sys.modules: diff --git a/src/solprop/calibration/child.py b/src/solprop/calibration/child.py index abdac28df..2fc253c88 100644 --- a/src/solprop/calibration/child.py +++ b/src/solprop/calibration/child.py @@ -100,19 +100,9 @@ def backend( ) -> Callable[[], object]: """Return the trial step of problem `name` under `record`, built once. - Parameters - ---------- - name : str - A prepared problem. - record : Mapping[str, object] or None - A native execution record, or ``None`` for the process's own. - planning : Mapping[str, object] or None, optional - An FFT planning (``fft_planning``), or ``None`` for the plan policy. - - Returns - ------- - collections.abc.Callable - One trial step from the problem's initial state. + - ``record``: a native execution record, or ``None`` for the process's own. + - ``planning``: an FFT planning (``fft_planning``), or ``None`` for the plan + policy. """ key = self._key(name, record, planning) if key not in self.backends: @@ -152,33 +142,15 @@ def _key( ) -> tuple[str, str]: """Return the cache key of problem `name`'s backend under a setup. - Parameters - ---------- - name : str - A prepared problem. - record, planning : Mapping[str, object] or None - The setup. - - Returns - ------- - tuple of str - The problem and its setup as JSON. + - Returns: the problem and its setup as JSON. """ return name, json.dumps([record, planning], sort_keys=True) def prepare(self, problems: Mapping[str, float]) -> dict[str, object]: """Build each problem at its scale and time one step of each. - Parameters - ---------- - problems : Mapping[str, float] - Each calibration problem's scale. - - Returns - ------- - dict - Each problem's fastest step and shape, the process settings in force, its - CPUs, and its execution and built-in records. + - Returns: each problem's fastest step and shape, the process settings in force, + its CPUs, and its execution and built-in records. """ import solprop_native # noqa: PLC0415 # pyright: ignore[reportMissingImports] @@ -219,21 +191,9 @@ def time( Two untimed steps first bring the problem's arrays back into the caches, which another child's window has just used. - Parameters - ---------- - name : str - A prepared problem. - repeats : int - The trial steps timed. - record : Mapping[str, object] or None - A native execution record, or ``None`` for the process's own. - planning : Mapping[str, object] or None, optional - An FFT planning, or ``None`` for the plan policy. - - Returns - ------- - float - Seconds per step. + - ``record``: a native execution record, or ``None`` for the process's own. + - ``planning``: an FFT planning, or ``None`` for the plan policy. + - Returns: seconds per step. """ step = self.backend(name, record, planning) for _ in range(2): @@ -258,26 +218,15 @@ def capture( share what is left of it, at most `limit` each, and the problems it leaves no time for are skipped and listed. - Parameters - ---------- - effort : str - ``"measure"`` or ``"patient"``. - limit : float - FFTW's planning time limit per transform, in seconds. - record : Mapping[str, object] or None - The execution record the problems run under, whose widths the plans' - threads follow. - budget : float or None, optional - The seconds the whole capture may take; none bounds it but `limit`. - - Returns - ------- - dict - ``wisdom`` by plan key, each key's ``groups`` and the problems - containing it (``problems``), the problems skipped for want of time - (``skipped``), the seconds spent (``seconds``), and each plan's execution - alone (``alone``: the seconds of the captured plan and of the same - transform at estimate, ``_core.time_fft_plan``; solprop issue #595). + - ``effort``: ``"measure"`` or ``"patient"``. + - ``limit``: FFTW's planning time limit per transform, in seconds. + - ``budget``: the seconds the whole capture may take; none bounds it but + `limit`. + - Returns: ``wisdom`` by plan key, each key's ``groups`` and the problems + containing it (``problems``), the problems skipped for want of time + (``skipped``), the seconds spent (``seconds``), and each plan's execution + alone (``alone``: the seconds of the captured plan and of the same transform + at estimate, ``_core.time_fft_plan``; solprop issue #595). """ from solprop_native import ( # noqa: PLC0415 # pyright: ignore[reportMissingImports] _core, # pyright: ignore[reportUnknownVariableType] @@ -347,15 +296,8 @@ def capture( def sizing(self, record: Mapping[str, object] | None) -> dict[str, object]: """Return each problem's operations as its workspace sizes them. - Parameters - ---------- - record : Mapping[str, object] or None - A native execution record, or ``None`` for the process's own. - - Returns - ------- - dict - Each problem's ``execution_sizing``: work and width by operation. + - ``record``: a native execution record, or ``None`` for the process's own. + - Returns: each problem's ``execution_sizing``: work and width by operation. """ found: dict[str, object] = {} for name in self.problems: @@ -378,17 +320,7 @@ def _regions(elements: int, repeats: int) -> dict[str, float]: kernels' execution space (``reference_stream_copies``), and of single-element copies, whose cost is the region's own. - Parameters - ---------- - elements : int - The complex values of each copy. - repeats : int - The timed copies of each size. - - Returns - ------- - dict of str to float - ``copy`` and ``empty``: the median seconds of each. + - Returns: ``copy`` and ``empty``: the median seconds of each. """ from solprop_native import ( # noqa: PLC0415 # pyright: ignore[reportMissingImports] _core, diff --git a/src/solprop/calibration/command.py b/src/solprop/calibration/command.py index 636480f54..2633b91cb 100644 --- a/src/solprop/calibration/command.py +++ b/src/solprop/calibration/command.py @@ -89,15 +89,7 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: def _value(value: object) -> str: """Format one setting for the description. - Parameters - ---------- - value : object - A setting's value. - - Returns - ------- - str - ``yes`` or ``no`` for a flag, else the value as text. + - Returns: ``yes`` or ``no`` for a flag, else the value as text. """ if isinstance(value, bool): return "yes" if value else "no" @@ -105,18 +97,7 @@ def _value(value: object) -> str: def _summary(record: Mapping[str, object]) -> str: - """Describe an allocation class in a line. - - Parameters - ---------- - record : Mapping[str, object] - An allocation class's record. - - Returns - ------- - str - Its cores, kinds, spans, SMT and quota. - """ + """Describe an allocation class in a line.""" cores = record.get("cores") kinds = record.get("kinds") parts = [f"{cores} cores"] @@ -137,18 +118,7 @@ def _summary(record: Mapping[str, object]) -> str: def _evidence(evidence: Mapping[str, object]) -> list[str]: - """Summarise a row's evidence: why, the noise, each verdict, the validation. - - Parameters - ---------- - evidence : Mapping[str, object] - A row's evidence. - - Returns - ------- - list of str - One line each. - """ + """Summarise a row's evidence: why, the noise, each verdict, the validation.""" lines: list[str] = [] if "reason" in evidence: lines.append(f"decided: {evidence['reason']}") diff --git a/src/solprop/calibration/controller.py b/src/solprop/calibration/controller.py index da68b84dc..a8c663918 100644 --- a/src/solprop/calibration/controller.py +++ b/src/solprop/calibration/controller.py @@ -183,13 +183,10 @@ class Candidate: wait: str | None = None @property - def name(self) -> str: - """``"built-in"``, or the wait and width, such as ``"spin/6"``. + def name(self) -> str: # numpydoc ignore=RT01 + """Name in reports and scores. - Returns - ------- - str - The candidate's name in reports and scores. + ``"built-in"``, or the wait and width, such as ``"spin/6"``. """ if self.threads is None and self.wait is None: return "built-in" @@ -298,46 +295,22 @@ def describe(self) -> str: class _Deadline: - """One monotonic deadline for all of a level's work. - - Parameters - ---------- - seconds : float - The budget from now. - """ + """One monotonic deadline for all of a level's work.""" def __init__(self, seconds: float) -> None: self.start = time.monotonic() self.end = self.start + seconds def remaining(self) -> float: - """Return the seconds left, never negative. - - Returns - ------- - float - Seconds. - """ + """Return the seconds left, never negative.""" return max(0.0, self.end - time.monotonic()) def elapsed(self) -> float: - """Return the seconds since the start. - - Returns - ------- - float - Seconds. - """ + """Return the seconds since the start.""" return time.monotonic() - self.start def epoch(self) -> float: - """Return the deadline in seconds since the epoch, for a claim file. - - Returns - ------- - float - Seconds since the epoch. - """ + """Return the deadline in seconds since the epoch, for a claim file.""" return time.time() + self.remaining() @@ -348,14 +321,7 @@ class _ChildError(CalibrationError): class _Child: """A calibration child process and its reply queue. - Parameters - ---------- - candidate : Candidate - Its process settings. - cpus : sequence of int - The CPUs it pins itself to; empty for none. - environ : Mapping[str, str] - The controller's environment, from which the child's is made. + - ``cpus``: the CPUs it pins itself to; empty for none. """ def __init__( @@ -401,18 +367,7 @@ def _drain(self) -> None: del self.errors[:-20] def send(self, request: Mapping[str, object]) -> None: - """Write one request. - - Parameters - ---------- - request : Mapping[str, object] - The request. - - Raises - ------ - _ChildError - When the child's input is closed or broken. - """ + """Write one request.""" assert self.process.stdin is not None try: self.process.stdin.write(json.dumps(request) + "\n") @@ -423,20 +378,7 @@ def send(self, request: Mapping[str, object]) -> None: def receive(self, timeout: float) -> object: """Return one reply's value, waiting at most `timeout` seconds. - Parameters - ---------- - timeout : float - Seconds to wait. - - Returns - ------- - object - The reply's value. - - Raises - ------ - _ChildError - When the child overran, exited or reported an error. + - ``timeout``: seconds to wait. """ try: line = self.replies.get(timeout=max(timeout, 0.0)) @@ -453,17 +395,7 @@ def receive(self, timeout: float) -> object: def request(self, request: Mapping[str, object], timeout: float) -> object: """Send one request and return its reply's value. - Parameters - ---------- - request : Mapping[str, object] - The request. - timeout : float - Seconds to wait for its reply. - - Returns - ------- - object - The reply's value. + - ``timeout``: seconds to wait for its reply. """ self.send(request) return self.receive(timeout) @@ -554,27 +486,19 @@ def candidates( widths = sorted({width for width in narrower if 1 <= width < budget}, reverse=True) widths = widths[: level.widths] - def other(width: int) -> str: + def _other(width: int) -> str: """Return the wait the built-in rule does not choose at `width`. - Parameters - ---------- - width : int - A pool's width. - - Returns - ------- - str - ``"spin"`` or ``"passive"``. + - Returns: ``"spin"`` or ``"passive"``. """ return "spin" if builtin_wait(width, top_tier) == "passive" else "passive" - found = [Candidate(threads=budget, wait=other(budget))] + found = [Candidate(threads=budget, wait=_other(budget))] found += [ Candidate(threads=width, wait=builtin_wait(width, top_tier)) for width in widths ] if widths: - found.append(Candidate(threads=widths[0], wait=other(widths[0]))) + found.append(Candidate(threads=widths[0], wait=_other(widths[0]))) return found[: selection.MAX_CANDIDATES] @@ -610,18 +534,7 @@ def nested(topology: Topology, cpus: Sequence[int]) -> list[tuple[int, ...]]: def _geomean(values: Sequence[float]) -> float: - """Return the geometric mean of positive `values`. - - Parameters - ---------- - values : sequence of float - Positive values. - - Returns - ------- - float - Their geometric mean. - """ + """Return the geometric mean of positive `values`.""" return math.exp(sum(math.log(value) for value in values) / len(values)) @@ -631,17 +544,9 @@ def _telemetry(path: Path = Path("/proc/stat")) -> dict[str, float] | None: Linux only (``/proc/stat``); elsewhere load is unknown and only the A/A noise decides (SEP 46 section 6.5). - Parameters - ---------- - path : pathlib.Path, optional - The kernel's statistics file. - - Returns - ------- - dict of str to float or None - ``total`` and ``steal`` ticks and ``running`` tasks, and each CPU's ticks - (``cpuN.total``) and busy ticks (``cpuN.busy``, neither idle nor waiting); - ``None`` where unknown. + - Returns: ``total`` and ``steal`` ticks and ``running`` tasks, and each CPU's ticks + (``cpuN.total``) and busy ticks (``cpuN.busy``, neither idle nor waiting); + ``None`` where unknown. """ try: text = path.read_text(encoding="utf-8") @@ -675,19 +580,8 @@ def _busy( SMT sibling of its cores more than half busy (another task sharing a core it times), or more than a tenth of the time stolen by a hypervisor. - Parameters - ---------- - before, after : dict or None - :func:`_telemetry` before and after the measurements. - idle : int - The CPUs outside the allocation. - siblings : sequence of int, optional - The CPUs outside the allocation that share a core with it. - - Returns - ------- - str - The reason, empty when the host was quiet enough or its load is unknown. + - ``before``, ``after``: :func:`_telemetry` before and after the measurements. + - Returns: the reason, empty when the host was quiet enough or its load is unknown. """ if before is None or after is None: return "" @@ -732,20 +626,7 @@ class _Bench: repeats: Mapping[str, int] def time(self, plan: _Plan, child: _Child) -> list[float]: - """Return `child`'s seconds per step in each problem's window. - - Parameters - ---------- - plan : _Plan - The calibration's plan, for its problems and deadline. - child : _Child - The child to time. - - Returns - ------- - list of float - One value per problem. - """ + """Return `child`'s seconds per step in each problem's window.""" seconds: list[float] = [] for problem in plan.level.problems: reply = child.request( @@ -762,22 +643,6 @@ def _start( """Start and prepare the built-in child and the candidates; size the windows. Every child started is appended to `opened`, so that the caller reaps it. - - Parameters - ---------- - plan : _Plan - The calibration's plan. - cpus : sequence of int - The allocation's CPUs. - allocation : AllocationClass - Its class. - opened : list of _Child - Filled with the children started. - - Returns - ------- - _Bench - The prepared children and each window's repeats. """ builtin = _Child(Candidate(), cpus, plan.environ) opened.append(builtin) @@ -809,20 +674,9 @@ def _rounds( so that the ratio of two scores is the geometric mean of their windows' ratios. ``"again"`` is the built-in child timed a second time in the same round. - Parameters - ---------- - plan : _Plan - The calibration's plan. - bench : _Bench - The prepared children. - until : float - The seconds of the deadline kept back; no round starts within them. - - Returns - ------- - tuple - Each slot's score per round, ``None`` for a failed one; and each slot's - seconds per step on each problem per round. + - ``until``: the seconds of the deadline kept back; no round starts within them. + - Returns: each slot's score per round, ``None`` for a failed one; and each slot's + seconds per step on each problem per round. """ slots = [("built-in", bench.builtin), ("again", bench.builtin)] slots += [(child.candidate.name, child) for child in bench.rivals] @@ -857,15 +711,9 @@ def _window_ratios( ) -> dict[str, list[float | None]]: """Return each problem's per-round ratio of a candidate's seconds to a base's. - Parameters - ---------- - candidate, base : Mapping[str, sequence of float or None] - Seconds per round by problem, ``None`` for a failed round. - - Returns - ------- - dict of str to list - The ratios, ``None`` where either failed. + - ``candidate``, ``base``: seconds per round by problem, ``None`` for a failed + round. + - Returns: the ratios, ``None`` where either failed. """ return { problem: [ @@ -879,24 +727,7 @@ def _window_ratios( def _validate( plan: _Plan, bench: _Bench, winner: _Child, noise: float ) -> selection.Validation: - """Validate the winner against the built-in child on fresh rounds, by window. - - Parameters - ---------- - plan : _Plan - The calibration's plan. - bench : _Bench - The prepared children. - winner : _Child - The admitted candidate. - noise : float - The session's A/A noise. - - Returns - ------- - solprop.calibration.selection.Validation - The verdict. - """ + """Validate the winner against the built-in child on fresh rounds, by window.""" names = plan.level.problems combined: dict[str, list[float | None]] = {name: [] for name in names} table: dict[str, list[float | None]] = {name: [] for name in names} @@ -921,15 +752,7 @@ def _validate( def _settings(child: _Child) -> dict[str, object]: """Return a child's process settings as it reported them: width and wait. - Parameters - ---------- - child : _Child - A prepared child. - - Returns - ------- - dict - ``threads`` and ``wait``. + - Returns: ``threads`` and ``wait``. """ process = cast(dict[str, object], child.report["process"]) return {"threads": process["threads"], "wait": process["wait"]} @@ -944,25 +767,10 @@ def _decide( ) -> tuple[_Child, dict[str, object], str]: """Choose and validate a candidate; return its child, the evidence and why. - Parameters - ---------- - plan : _Plan - The calibration's plan. - bench : _Bench - The prepared children. - scores : dict - Each slot's score per round. - noise : float - The session's A/A noise. - seconds : Mapping or None, optional - Each slot's seconds per problem per round (:func:`_rounds`), so that no - problem may be slower under the winner. - - Returns - ------- - tuple - The child whose process settings were chosen (the built-in one when none - replaced them), the evidence, and the reason. + - ``seconds``: each slot's seconds per problem per round (:func:`_rounds`), so that + no problem may be slower under the winner. + - Returns: the child whose process settings were chosen (the built-in one when none + replaced them), the evidence, and the reason. """ windows = ( {} @@ -1034,25 +842,9 @@ def _compare_round( ) -> tuple[dict[str, float | None], dict[str, dict[str, float | None]]]: """Time one round of candidate setups against a base setup. - Parameters - ---------- - plan : _Plan - The calibration's plan, with each window's repeats. - child : _Child - The child whose process settings were chosen. - base : _Setup - The base setup. - candidates : sequence of tuple - Each candidate's name, setup and the problems it changes. - generator : random.Random - Shuffles the round's order. - - Returns - ------- - tuple - ``again``, the base's A/A ratio over the problems, and each candidate's ratio - to it over its problems, ``None`` for a failed window; and each candidate's - ratio on each of its problems. + - Returns: ``again``, the base's A/A ratio over the problems, and each candidate's + ratio to it over its problems, ``None`` for a failed window; and each candidate's + ratio on each of its problems. """ problems = tuple(sorted({name for _, _, names in candidates for name in names})) slots = [("base", base, problems), ("again", base, problems), *candidates] @@ -1102,26 +894,9 @@ def _session_of( # noqa: PLR0917 - called positionally ) -> tuple[float | None, dict[str, selection.Verdict]]: """Time interleaved rounds of candidate setups; return the noise and verdicts. - Parameters - ---------- - plan : _Plan - The calibration's plan. - child : _Child - The child whose process settings were chosen. - base : _Setup - The base setup. - candidates : sequence of tuple - Each candidate's name, setup and the problems it changes. - until : float - The seconds of the deadline kept back; no round starts within them. - seed : int - Seeds the rounds' order. - - Returns - ------- - tuple - The session's A/A noise (``None`` with too few rounds), and each - candidate's verdict, empty when the noise is too high to decide. + - ``until``: the seconds of the deadline kept back; no round starts within them. + - Returns: the session's A/A noise (``None`` with too few rounds), and each + candidate's verdict, empty when the noise is too high to decide. """ generator = random.Random(seed) ratios: dict[str, list[float | None]] = {"again": []} @@ -1158,20 +933,9 @@ def _grain_candidates( ) -> list[tuple[grain.Candidate, tuple[str, ...]]]: """Return the grain candidates and the problems each changes. - Parameters - ---------- - plan : _Plan - The calibration's plan, for its level's limits. - child : _Child - The child whose process settings were chosen. - sizing : Mapping[str, Mapping[str, object]] - Each problem's ``execution_sizing`` under the child's own record. - - Returns - ------- - list of tuple - Each candidate, with the problems whose operations (or compact team) it - changes; a candidate that changes none is left out. + - ``sizing``: each problem's ``execution_sizing`` under the child's own record. + - Returns: each candidate, with the problems whose operations (or compact team) it + changes; a candidate that changes none is left out. """ base = cast(dict[str, int | bool], child.report["execution"]) capacity = int(cast(int, _settings(child)["threads"])) @@ -1206,19 +970,8 @@ def _grain( §6.5's rule; the best of each setting are combined and validated together on fresh rounds of every problem. A failed validation drops the grain. - Parameters - ---------- - plan : _Plan - The calibration's plan, with each window's repeats. - child : _Child - The child whose process settings were chosen. - until : float - The seconds of the deadline kept back for publication. - - Returns - ------- - tuple - The workspace settings admitted (empty when none), and the evidence. + - ``until``: the seconds of the deadline kept back for publication. + - Returns: the workspace settings admitted (empty when none), and the evidence. """ try: sizing = cast( @@ -1267,26 +1020,7 @@ def _grain( def _validate_setups( plan: _Plan, child: _Child, combined: _Setup, table: _Setup, noise: float ) -> selection.Validation: - """Validate a combined setup against the base on fresh rounds of every window. - - Parameters - ---------- - plan : _Plan - The calibration's plan, with each window's repeats. - child : _Child - The child. - combined : _Setup - The admitted settings together. - table : _Setup - The base they replace. - noise : float - The session's A/A noise. - - Returns - ------- - solprop.calibration.selection.Validation - The verdict. - """ + """Validate a combined setup against the base on fresh rounds of every window.""" names = plan.level.problems times: dict[str, dict[str, list[float | None]]] = { "combined": {name: [] for name in names}, @@ -1337,16 +1071,9 @@ def _planning_candidates( share of the stage, so it was never timed and only took the time the timing in the step needed (solprop issue #595). - Parameters - ---------- - measured : Mapping[str, object] - The capture at measure: ``wisdom`` and ``groups`` by plan key. - - Returns - ------- - list of tuple - Each candidate's name, group and planning: the policy's efforts with one - group's changed, and the wisdom of every group it plans beyond estimate. + - ``measured``: the capture at measure: ``wisdom`` and ``groups`` by plan key. + - Returns: each candidate's name, group and planning: the policy's efforts with one + group's changed, and the wisdom of every group it plans beyond estimate. """ groups = cast(dict[str, str], measured["groups"]) wisdom = cast(dict[str, str], measured["wisdom"]) @@ -1356,17 +1083,7 @@ def planning( ) -> dict[str, object]: """Return a profile planning with `efforts` and the wisdom of `keys` they use. - Parameters - ---------- - efforts : Mapping[str, str] - Each group's effort. - keys : Mapping[str, str] - Wisdom by plan key; that of a group planned at estimate is left out. - - Returns - ------- - dict - The planning. + - ``keys``: wisdom by plan key; that of a group planned at estimate is left out. """ return { "mode": "profile", @@ -1410,40 +1127,20 @@ def _alone_ratios( ) -> dict[str, dict[str, float]]: """Return each candidate's plans' execution alone over the policy's (issue #595). - Parameters - ---------- - candidates : sequence of tuple - Each candidate's name, group and planning (:func:`_planning_candidates`). - groups : Mapping[str, str] - Each plan key's group. - measured : Mapping[str, object] - The capture at measure, whose ``alone`` holds each plan's seconds as - captured (``plan``) and at estimate (``estimate``). - - Returns - ------- - dict - By candidate, by plan key of its group: the candidate effort's seconds alone - over the policy effort's; a key timed at neither is left out. Set beside the - candidate's verdict in the step, it tells a plan slower alone from one that - loses only in the step. + - ``measured``: the capture at measure, whose ``alone`` holds each plan's seconds as + captured (``plan``) and at estimate (``estimate``). + - Returns: by candidate, by plan key of its group: the candidate effort's seconds + alone over the policy effort's; a key timed at neither is left out. Set beside the + candidate's verdict in the step, it tells a plan slower alone from one that loses + only in the step. """ timed = cast(dict[str, dict[str, float]], measured.get("alone", {})) def seconds(key: str, effort: str) -> float | None: """Return a plan's seconds alone at `effort`, or ``None``. - Parameters - ---------- - key : str - The plan key. - effort : str - ``"estimate"`` or ``"measure"``. - - Returns - ------- - float or None - The seconds, when timed and finite. + - ``effort``: ``"estimate"`` or ``"measure"``. + - Returns: the seconds, when timed and finite. """ value = timed.get(key, {}).get("estimate" if effort == "estimate" else "plan") return value if value is not None and math.isfinite(value) else None @@ -1485,21 +1182,9 @@ def _planning( step on the problems holding that group's transforms and admitted by §6.5's rule; the winners are validated together. Quick leaves the plan policy. - Parameters - ---------- - plan : _Plan - The calibration's plan, with each window's repeats. - child : _Child - The child whose process settings were chosen. - record : Mapping[str, int or bool] - The workspace record chosen, under which the transforms' threads are set. - until : float - The seconds of the deadline kept back for publication. - - Returns - ------- - tuple - The row's planning (``efforts``), the wisdom by plan key, and the evidence. + - ``until``: the seconds of the deadline kept back for publication. + - Returns: the row's planning (``efforts``), the wisdom by plan key, and the + evidence. """ if plan.level.name == "quick": return {}, {}, {"reason": "the plan policy (quick)"} @@ -1512,17 +1197,7 @@ def capture(effort: str, share: float) -> dict[str, object]: Each capture takes its share of the stage's time, so that the timing in the step still has the rest (SEP 46 section 6.7). - Parameters - ---------- - effort : str - ``"measure"`` or ``"patient"``. - share : float - The share of the stage's time left that the capture may take. - - Returns - ------- - dict - The child's capture (:meth:`solprop.calibration.child._Session.capture`). + - ``effort``: ``"measure"`` or ``"patient"``. """ budget = max(0.0, plan.deadline.remaining() - until) * share return cast( @@ -1649,24 +1324,6 @@ def _publish( """Publish `row` in a new generation, keeping the other classes' rows. A row of the same class from a higher level is kept instead. - - Parameters - ---------- - store : solprop_native._store.Store - The user's store. - fingerprint : str - The machine and build's key. - row : solprop_native._store.Row - The new row. - allocation : AllocationClass - Its class. - wisdom : Mapping[str, str] or None, optional - Its plans' wisdom by plan key, added to the generation's. - - Returns - ------- - pathlib.Path - The generation file published. """ previous: Generation | None = store.current(fingerprint) rows: list[Row] = [] @@ -1699,21 +1356,7 @@ def _calibrate_allocation( ) -> CalibrationReport: """Calibrate one allocation class; publish its row unless inconclusive. - Parameters - ---------- - plan : _Plan - The calibration's plan. - cpus : sequence of int - The allocation's CPUs. - allocation : AllocationClass - Its class. - until : float - The seconds of the deadline kept back for validation and publication. - - Returns - ------- - CalibrationReport - The class's report. + - ``until``: the seconds of the deadline kept back for validation and publication. """ base = CalibrationReport( level=plan.level.name, @@ -1925,18 +1568,7 @@ def calibrate( def _siblings(cpus: Sequence[int]) -> list[int]: - """Return the CPUs outside `cpus` that share a physical core with one of them. - - Parameters - ---------- - cpus : sequence of int - The allocation's CPUs. - - Returns - ------- - list of int - Their SMT siblings, ascending. - """ + """Return the CPUs outside `cpus` that share a physical core with one of them.""" machine = _native.topology.read_topology() cores = {machine.core[cpu] for cpu in cpus if cpu in machine.core} held = set(cpus) @@ -1951,15 +1583,7 @@ def _native_budget(cpus: Sequence[int]) -> int: The smallest of the CPUs, their physical cores, the fastest kind's cores and the control groups' quota (``thread_budget`` without ``SOLPROP_THREADS``). - Parameters - ---------- - cpus : sequence of int - The allocation's CPUs; empty for the whole machine. - - Returns - ------- - int - Threads. + - ``cpus``: the allocation's CPUs; empty for the whole machine. """ limits = [len(cpus) or (os.cpu_count() or 1)] for limit in ( diff --git a/src/solprop/calibration/first_use.py b/src/solprop/calibration/first_use.py index e01a9fb78..d480e7e7b 100644 --- a/src/solprop/calibration/first_use.py +++ b/src/solprop/calibration/first_use.py @@ -60,19 +60,7 @@ def _notice(level: str, seconds: float, store: object, cores: int) -> None: - """Issue the first-use notice as a warning and a log record. - - Parameters - ---------- - level : str - The level about to run. - seconds : float - Its budget. - store : object - The store's directory. - cores : int - The allocation's cores. - """ + """Issue the first-use notice as a warning and a log record.""" message = ( f"solprop is calibrating the native backend to this machine ({level}, about " f"{seconds:.0f} s on {cores} cores; once per machine, allocation and release). " @@ -85,13 +73,7 @@ def _notice(level: str, seconds: float, store: object, cores: int) -> None: def _device_build() -> bool: - """Return whether the installed native build runs on a device (phase 6's). - - Returns - ------- - bool - Whether its build record names a device backend. - """ + """Return whether the installed native build runs on a device (phase 6's).""" import json # noqa: PLC0415 for directory in _native.packages(): @@ -107,23 +89,7 @@ def _device_build() -> bool: def _wait_for(store: Store, digest: str, key: str) -> bool: - """Wait for another process on this host calibrating this class; say if one was. - - Parameters - ---------- - store : solprop_native._store.Store - The user's store. - digest : str - The machine and build's key. - key : str - The allocation class's key. - - Returns - ------- - bool - Whether a claim was held, here or elsewhere: then this process does not - calibrate. - """ + """Wait for another process on this host calibrating this class; say if one was.""" import socket # noqa: PLC0415 holder = store.holder(digest, key) @@ -137,18 +103,7 @@ def _wait_for(store: Store, digest: str, key: str) -> bool: def _allowed(environ: Mapping[str, str]) -> bool: - """Return whether this process may measure implicitly. - - Parameters - ---------- - environ : Mapping[str, str] - The environment. - - Returns - ------- - bool - ``SOLPROP_CALIBRATION=auto``, not a scan's worker, and a CPU build. - """ + """Return whether this process may measure implicitly.""" return ( _native.store.mode(environ) == "auto" and environ.get(ROLE) != "worker" @@ -157,23 +112,7 @@ def _allowed(environ: Mapping[str, str]) -> bool: def _covered(found: Choice, allocation: AllocationClass, need: str) -> bool: - """Return whether the profile already has the row an allocation needs. - - Parameters - ---------- - found : solprop_native._profile.Choice - What the stores hold for this machine and build. - allocation : solprop_native._allocation.AllocationClass - The allocation's class. - need : str - ``"any"``: any row it takes, its own or a nearer one's; ``"standard"``: its own - at the standard or thorough level. - - Returns - ------- - bool - Whether nothing needs measuring. - """ + """Return whether the profile already has the row an allocation needs.""" from solprop.calibration.controller import ORDER # noqa: PLC0415 if need == "any" and found.found is not None: @@ -193,21 +132,9 @@ def _calibrate( ) -> CalibrationReport | None: """Calibrate `cpus`' allocation at `level` unless a row it needs exists. - Parameters - ---------- - level : str - ``"quick"`` or ``"standard"``. - cpus : sequence of int - The allocation's CPUs. - environ : Mapping[str, str] - The environment. - need : str - ``"any"`` or ``"standard"`` (:func:`_covered`). - - Returns - ------- - CalibrationReport or None - The report, or ``None`` when nothing was measured. + - ``level``: ``"quick"`` or ``"standard"``. + - ``need``: ``"any"`` or ``"standard"`` (:func:`_covered`). + - Returns: the report, or ``None`` when nothing was measured. """ if len(cpus) < 2: # One CPU has no team, waiting or grain to choose: a single-threaded scan's diff --git a/src/solprop/calibration/grain.py b/src/solprop/calibration/grain.py index 4f44b9bf8..0ee759ade 100644 --- a/src/solprop/calibration/grain.py +++ b/src/solprop/calibration/grain.py @@ -243,24 +243,7 @@ def record(self, base: Record) -> dict[str, int | bool]: def _class_moves( kind: str, sizes: list[int], record: Record, *, fulls: bool ) -> list[Candidate]: - """Return a class's one-step moves past its nearest work sizes. - - Parameters - ---------- - kind : str - The operation class. - sizes : list of int - Its work sizes in the problems, ascending. - record : Mapping[str, int or bool] - The base record. - fulls : bool - Whether to move its full size as well as its threshold. - - Returns - ------- - list of Candidate - At most four. - """ + """Return a class's one-step moves past its nearest work sizes.""" threshold = int(record[f"{kind}_threshold"]) full = int(record[f"{kind}_full"]) serial = [size for size in sizes if size < threshold] @@ -378,15 +361,8 @@ def proposals( def _shape_moves(record: Record) -> list[Candidate]: """Return the transforms' shape settings halved and doubled. - Parameters - ---------- - record : Mapping[str, int or bool] - The base record. - - Returns - ------- - list of Candidate - Two at most, smallest first: a grain of one thread per sample is not halved. + - Returns: two at most, smallest first: a grain of one thread per sample is not + halved. """ (name,) = SHAPE_MOVES value = int(record[name]) diff --git a/src/solprop/calibration/problems.py b/src/solprop/calibration/problems.py index 13ed5bc3b..4d0d83742 100644 --- a/src/solprop/calibration/problems.py +++ b/src/solprop/calibration/problems.py @@ -42,16 +42,6 @@ def _gnlse(scale: float) -> Simulation: The dispersion coefficients follow J. M. Dudley, G. Genty and S. Coen, Rev. Mod. Phys. 78, 1135 (2006), doi:10.1103/RevModPhys.78.1135, section III, as the benchmark case does. - - Parameters - ---------- - scale : float - The window's size relative to the standard one. - - Returns - ------- - solprop.driver.Simulation - The simulation. """ from solprop.driver import Simulation # noqa: PLC0415 - imports the physics @@ -85,18 +75,7 @@ def _gnlse(scale: float) -> Simulation: def _capillary(scale: float) -> Simulation: - """Build four modes of a helium capillary with plasma, on `scale` x 200 fs. - - Parameters - ---------- - scale : float - The window's size relative to the standard one. - - Returns - ------- - solprop.driver.Simulation - The simulation. - """ + """Build four modes of a helium capillary with plasma, on `scale` x 200 fs.""" from solprop.driver import Simulation # noqa: PLC0415 - imports the physics return Simulation.from_capillary( @@ -116,18 +95,7 @@ def _capillary(scale: float) -> Simulation: def _averaged(scale: float) -> Simulation: - """Build a mode-averaged hydrogen capillary with Raman, on `scale` x 800 fs. - - Parameters - ---------- - scale : float - The window's size relative to the standard one. - - Returns - ------- - solprop.driver.Simulation - The simulation. - """ + """Build a mode-averaged hydrogen capillary with Raman, on `scale` x 800 fs.""" from solprop.driver import Simulation # noqa: PLC0415 - imports the physics return Simulation.from_capillary( @@ -152,17 +120,7 @@ def _averaged(scale: float) -> Simulation: def _spatial(kind: str, scale: float) -> Simulation: """Build an argon filament on a radial grid, or Kerr focusing on a line. - Parameters - ---------- - kind : str - ``"radial"`` or ``"line"``. - scale : float - The transverse count relative to the standard one. - - Returns - ------- - solprop.driver.Simulation - The simulation. + - ``kind``: ``"radial"`` or ``"line"``. """ import numpy as np # noqa: PLC0415 diff --git a/src/solprop/calibration/refine.py b/src/solprop/calibration/refine.py index fd21ab41f..8b87a6c91 100644 --- a/src/solprop/calibration/refine.py +++ b/src/solprop/calibration/refine.py @@ -105,12 +105,7 @@ def describe(self) -> str: class _Windows: """One record's backend on the simulation, and its trial steps along it. - Parameters - ---------- - simulation : solprop.driver.Simulation - The simulation. - record : Mapping[str, int or bool] or None - A native execution record, or ``None`` for the process's own. + - ``record``: a native execution record, or ``None`` for the process's own. """ def __init__( @@ -155,11 +150,6 @@ def fields(self) -> list[np.ndarray]: A low-storage march advances its state with each step, so it has no fields to compare and returns none. - - Returns - ------- - list of numpy.ndarray - One per window. """ found: list[np.ndarray] = [] for z in self.positions: @@ -170,18 +160,7 @@ def fields(self) -> list[np.ndarray]: return found def time(self, repeats: int) -> float: - """Return the seconds per trial step over the windows, after a warm-up. - - Parameters - ---------- - repeats : int - Trial steps timed in each window. - - Returns - ------- - float - Seconds per step. - """ + """Return the seconds per trial step over the windows, after a warm-up.""" steps: list[Callable[[], object]] = ( [self.probe.trial] if self.low_storage @@ -204,19 +183,9 @@ def close(self) -> None: def _accurate( found: list[np.ndarray], reference: list[np.ndarray], rtol: float ) -> bool: - """Return whether `found` is finite and within ``ACCURACY * rtol`` of `reference`. - - Parameters - ---------- - found, reference : list of numpy.ndarray - The windows' trial-step fields under a candidate and under the base. - rtol : float - The run's relative tolerance. + """Return whether `found` is finite and close to `reference`, window by window. - Returns - ------- - bool - Whether every window agrees, in the relative L2 norm. + Close is within ``ACCURACY * rtol`` in the relative L2 norm. """ for mine, theirs in zip(found, reference, strict=True): if not np.all(np.isfinite(mine)): @@ -268,21 +237,8 @@ def _candidates( ) -> list[grain.Candidate]: """Return the grain candidates that change this simulation's widths. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The translated problem, for its shape. - sizing : Mapping[str, object] - Its workspace's ``execution_sizing`` under the base record. - base : Mapping[str, int or bool] - The process's record. - threads : int - The pool's width. - - Returns - ------- - list of solprop.calibration.grain.Candidate - At most twelve. + - ``problem``: the translated problem, for its shape. + - ``sizing``: its workspace's ``execution_sizing`` under the base record. """ shape = cast(tuple[int, ...], getattr(problem, "shape")) # noqa: B009 - typed loosely operations = grain.operations(sizing) @@ -302,22 +258,8 @@ def _rounds( ) -> dict[str, list[float | None]]: """Time interleaved rounds of every candidate's windows against the base's. - Parameters - ---------- - base : _Windows - The process's record's windows. - windows : Mapping[str, _Windows] - Each candidate's. - repeats : int - Trial steps per window. - until : float - The monotonic time after which no round starts. - - Returns - ------- - dict of str to list - ``again``, the base's A/A ratio, and each candidate's ratio to the base, per - round. + - Returns: ``again``, the base's A/A ratio, and each candidate's ratio to the base, + per round. """ generator = random.Random(5000) ratios: dict[str, list[float | None]] = {"again": []} @@ -336,22 +278,7 @@ def _rounds( def _together( combined: _Windows, base: _Windows, repeats: int, noise: float ) -> selection.Validation: - """Validate the admitted settings together against the base on fresh rounds. - - Parameters - ---------- - combined, base : _Windows - The combination's windows and the base's. - repeats : int - Trial steps per window. - noise : float - The session's A/A noise. - - Returns - ------- - solprop.calibration.selection.Validation - The verdict. - """ + """Validate the admitted settings together against the base on fresh rounds.""" ratios: list[float | None] = [] for index in range(ROUNDS): pair = [("combined", combined), ("base", base)] @@ -371,24 +298,7 @@ def _refine( ) -> RefinementReport: """Carry out :func:`refine`; its report's seconds are set by the caller. - Parameters - ---------- - simulation : solprop.driver.Simulation - The simulation. - budget : float - The deadline, in seconds. - progress : callable or None - Called with a line of text as each stage starts. - - Returns - ------- - RefinementReport - The report. - - Raises - ------ - CalibrationError - For a budget that is not positive, or a simulation not on the native backend. + - ``budget``: the deadline, in seconds. """ if not budget > 0: raise CalibrationError(f"a refinement's budget must be positive, got {budget}") diff --git a/src/solprop/capabilities.py b/src/solprop/capabilities.py index 92fb9eaee..0b835f836 100644 --- a/src/solprop/capabilities.py +++ b/src/solprop/capabilities.py @@ -165,17 +165,10 @@ def admits(self, operation: str) -> bool: def _quantities(basis: BasisCapability) -> dict[str, Quantity]: """Return what each derived quantity is on a basis. - Parameters - ---------- - basis : {'modal', 'general_modal', 'spatial'} - The basis. - - Returns - ------- - dict - The quantities: physical on an orthogonal modal basis, coordinate on a general - one (whose physical energy is :func:`solprop.processing.modal.physical_energy`) - and on a spatial grid, whose amplitudes are per transverse column. + - Returns: the quantities: physical on an orthogonal modal basis, coordinate on a + general one (whose physical energy is + :func:`solprop.processing.modal.physical_energy`) and on a spatial grid, whose + amplitudes are per transverse column. """ kind: QuantityKind = "physical" if basis == "modal" else "coordinate" return { @@ -187,18 +180,7 @@ def _quantities(basis: BasisCapability) -> dict[str, Quantity]: def _compression_refusal(handler: Output) -> str | None: - """Return the compressors' refusal of a file, or ``None``. - - Parameters - ---------- - handler : solprop.output.schema.Output - The opened file. - - Returns - ------- - str or None - The refusal text, as the compressors raise it. - """ + """Return the compressors' refusal of a file, or ``None``.""" from solprop.processing.compression import ( # noqa: PLC0415 - layer above _require_modal, # pyright: ignore[reportPrivateUsage] ) @@ -247,20 +229,7 @@ def output(source: str | Path | Output) -> OutputCapabilities: def _statement(handler: Output, resumable: Resumability | None) -> OutputCapabilities: - """Return the statement for an opened handler. - - Parameters - ---------- - handler : solprop.output.schema.Output - The opened file. - resumable : Resumability or None - Its resumability, for a file. - - Returns - ------- - OutputCapabilities - The statement. - """ + """Return the statement for an opened handler.""" metadata = handler.metadata if metadata is None: from solprop.exceptions import OutputError # noqa: PLC0415 - only here @@ -843,16 +812,8 @@ def _ionisation_models(gas: Gas) -> tuple[IonisationModel, ...]: The models are the ones :func:`solprop.nonlinear.optmat_rates.selected_rate` resolves a selection's name from, each built once to read its record. - Parameters - ---------- - gas : solprop.physdata.Gas - The gas. - - Returns - ------- - tuple of IonisationModel - The recommended model first, then the literature forms in optmat's order; - empty for a gas neither of optmat's rate families covers. + - Returns: the recommended model first, then the literature forms in optmat's order; + empty for a gas neither of optmat's rate families covers. """ from optmat.gas.ionisation_rates import ( # noqa: PLC0415 - optmat is lazy ionisation_rate_coverage, @@ -893,18 +854,7 @@ def _ionisation_models(gas: Gas) -> tuple[IonisationModel, ...]: @functools.cache def _species(gas: Gas) -> SpeciesCapabilities: - """Return a built-in gas's capabilities, once per gas. - - Parameters - ---------- - gas : solprop.physdata.Gas - The gas, not air. - - Returns - ------- - SpeciesCapabilities - The statement. - """ + """Return a built-in gas's capabilities, once per gas.""" from solprop.nonlinear.optmat_raman import raman_status # noqa: PLC0415 - layer from solprop.physdata.species import OptmatGas # noqa: PLC0415 - as above from solprop.physdata.species import species as built_in # noqa: PLC0415 diff --git a/src/solprop/cli.py b/src/solprop/cli.py index 89516d83e..36217aa68 100644 --- a/src/solprop/cli.py +++ b/src/solprop/cli.py @@ -32,13 +32,7 @@ def _parser() -> argparse.ArgumentParser: - """Return the command line's parser. - - Returns - ------- - argparse.ArgumentParser - The parser, one subcommand per action. - """ + """Return the command line's parser.""" parser = argparse.ArgumentParser( prog="solprop", description="Run, check and export solprop recipes.", @@ -101,11 +95,8 @@ def _parser() -> argparse.ArgumentParser: def _stop_signals() -> tuple[signal.Signals, ...]: """Return the signals that stop a run from the command line. - Returns - ------- - tuple of signal.Signals - SIGINT and SIGTERM, and on Windows SIGBREAK, which ``CTRL_BREAK_EVENT`` - sends. + - Returns: SIGINT and SIGTERM, and on Windows SIGBREAK, which ``CTRL_BREAK_EVENT`` + sends. """ names = ("SIGINT", "SIGTERM", "SIGBREAK") return tuple(getattr(signal, name) for name in names if hasattr(signal, name)) @@ -118,29 +109,13 @@ def _stopping() -> Iterator[tuple[threading.Event, list[int]]]: The first signal sets the flag (SEP 41, S17), so the run writes its state and returns; a second, while it stops, interrupts at once. - Yields - ------ - tuple - The flag, and the list the signals received are appended to. + - Yields: the flag, and the list the signals received are appended to. """ flag = threading.Event() received: list[int] = [] def stop(number: int, frame: FrameType | None) -> None: - """Set the run's flag on the first signal; interrupt on the second. - - Parameters - ---------- - number : int - The signal. - frame : types.FrameType or None - The interrupted frame, unused. - - Raises - ------ - KeyboardInterrupt - On a second signal, while the run is still stopping. - """ + """Set the run's flag on the first signal; interrupt on the second.""" del frame if flag.is_set(): raise KeyboardInterrupt @@ -158,18 +133,9 @@ def stop(number: int, frame: FrameType | None) -> None: def _stopped(received: list[int], output: Path) -> int: """Report how a run ended and return the exit status. - Parameters - ---------- - received : list of int - The signals that stopped it; empty when it completed. - output : pathlib.Path - The file it wrote. - - Returns - ------- - int - Zero, or ``128`` plus the first signal's number. A signal that came too late to - stop anything, since the file says the run completed, is zero too. + - ``received``: the signals that stopped it; empty when it completed. + - Returns: zero, or ``128`` plus the first signal's number. A signal that came too + late to stop anything, since the file says the run completed, gives zero too. """ if not received: return 0 @@ -199,22 +165,8 @@ def _stopped(received: list[int], output: Path) -> int: def _run(recipe_path: Path, output: Path, progress: str, *, resume: bool) -> int: """Build and run a recipe, stopping it cleanly on a signal. - Parameters - ---------- - recipe_path : pathlib.Path - The recipe file. - output : pathlib.Path - The HDF5 file. - progress : str - The progress choice. - resume : bool - Whether to continue the file. - - Returns - ------- - int - Zero when the run completes, and ``128`` plus the signal's number when a - signal stopped it. + - Returns: zero when the run completes, and ``128`` plus the signal's number when a + signal stopped it. """ from solprop.recipe import Recipe # noqa: PLC0415 - imports the physics @@ -227,19 +179,7 @@ def _run(recipe_path: Path, output: Path, progress: str, *, resume: bool) -> int def _resume(output: Path, mode: bool | str, progress: str) -> int: """Continue a run from its output file alone (SEP 41, S2). - Parameters - ---------- - output : pathlib.Path - The HDF5 file. - mode : bool or str - ``True`` or ``"interpolated"``. - progress : str - The progress choice. - - Returns - ------- - int - As :func:`_run`'s. + - ``mode``: ``True`` or ``"interpolated"``. """ from solprop.recipe import resume_file # noqa: PLC0415 - imports the physics @@ -256,15 +196,7 @@ def _resume(output: Path, mode: bool | str, progress: str) -> int: def _check(recipe_path: Path) -> int: """Print what stops a recipe building, or makes it warn. - Parameters - ---------- - recipe_path : pathlib.Path - The recipe file. - - Returns - ------- - int - Zero when the recipe builds without an error, one otherwise. + - Returns: zero when the recipe builds without an error, one otherwise. """ from solprop.recipe import Recipe # noqa: PLC0415 - imports the physics @@ -317,18 +249,7 @@ def main(argv: Sequence[str] | None = None) -> int: def _command(args: argparse.Namespace) -> int: - """Carry out one parsed command other than ``scan``. - - Parameters - ---------- - args : argparse.Namespace - The parsed command line. - - Returns - ------- - int - The command's exit status. - """ + """Carry out one parsed command other than ``scan``.""" if args.command == "run": return _run(args.recipe, args.output, args.progress, resume=args.resume) if args.command == "check": diff --git a/src/solprop/coupling/coupler.py b/src/solprop/coupling/coupler.py index 3b4567e0f..938c8adfe 100644 --- a/src/solprop/coupling/coupler.py +++ b/src/solprop/coupling/coupler.py @@ -173,13 +173,10 @@ class AdmissionItem: note: str = "" @property - def passed(self) -> bool: - """Return whether the item is within its own cap. + def passed(self) -> bool: # numpydoc ignore=RT01 + """Whether the item is within its own cap. - Returns - ------- - bool - True when it has no value or no cap, or its value is at most the cap. + True when it has no value or no cap, or its value is at most the cap. """ return ( self.value is None or self.allowance is None or self.value <= self.allowance @@ -258,83 +255,55 @@ def __post_init__(self) -> None: object.__setattr__(self, "source", MappingProxyType(dict(self.source))) @property - def uncaptured(self) -> Float[np.ndarray, " n_omega"] | None: - """Return the incident energy each bin leaves outside the target, not clipped. - - Returns - ------- - numpy.ndarray or None - ``incident - captured``, J, in the common positive metric where the - orthogonal-residual identity makes it a nonnegative physical share; - None where `captured` is None. + def uncaptured( + self, + ) -> Float[np.ndarray, " n_omega"] | None: # numpydoc ignore=RT01 + """Incident energy each bin leaves outside the target, not clipped. + + ``incident - captured``, J, in the common positive metric where the + orthogonal-residual identity makes it a nonnegative physical share; + None where `captured` is None. """ return None if self.captured is None else self.incident - self.captured @property - def incident_energy(self) -> float: - """Return the whole incident energy. - - Returns - ------- - float - J. - """ + def incident_energy(self) -> float: # numpydoc ignore=RT01 + """Whole incident energy, J.""" return float(np.sum(self.incident)) @property - def launched_energy(self) -> float: - """Return the energy the launch carries into the target. - - Returns - ------- - float - J. - """ + def launched_energy(self) -> float: # numpydoc ignore=RT01 + """Energy the launch carries into the target, J.""" return float(np.sum(self.launched)) @property - def captured_energy(self) -> float | None: - """Return the captured energy in the common metric. - - Returns - ------- - float or None - J; None where it is not defined. - """ + def captured_energy(self) -> float | None: # numpydoc ignore=RT01 + """Captured energy in the common metric, J, or ``None`` where undefined.""" return None if self.captured is None else float(np.sum(self.captured)) @property - def uncaptured_energy(self) -> float | None: - """Return the uncaptured energy in the common metric, not clipped. + def uncaptured_energy(self) -> float | None: # numpydoc ignore=RT01 + """Uncaptured energy in the common metric, J, not clipped. - Returns - ------- - float or None - J; None where it is not defined. + ``None`` where it is not defined. """ uncaptured = self.uncaptured return None if uncaptured is None else float(np.sum(uncaptured)) @property - def efficiency(self) -> float | None: - """Return the launched energy as a share of the incident energy. + def efficiency(self) -> float | None: # numpydoc ignore=RT01 + """Launched energy as a share of the incident energy. - Returns - ------- - float or None - Dimensionless; None when nothing is incident, where it is undefined. + ``None`` when nothing is incident, where it is undefined. """ incident = self.incident_energy return None if incident == 0.0 else self.launched_energy / incident @property - def budget(self) -> float: - """Return the sum of the counted admission items. + def budget(self) -> float: # numpydoc ignore=RT01 + """Sum of the counted admission items. - Returns - ------- - float - Dimensionless; at most `model_error` for an admitted coupling. + At most `model_error` for an admitted coupling. """ return float( sum(item.value for item in self.items if item.counted and item.value) @@ -361,10 +330,7 @@ def __repr__(self) -> str: def _describe(self) -> tuple[str, list[tuple[str, object]]]: """Return the title and rows of the printed block. - Returns - ------- - tuple - The title and ``(key, value)`` rows. + - Returns: the title and ``(key, value)`` rows. """ efficiency = self.efficiency rows: list[tuple[str, object]] = [ @@ -491,20 +457,7 @@ def couple( def _check_common(plane: FieldPlane, port: InputPort) -> None: - """Refuse a plane on another lattice, orientation or outside the port's band. - - Parameters - ---------- - plane : solprop.coupling.plane.FieldPlane - The source plane. - port : solprop.coupling.port.InputPort - The receiving port. - - Raises - ------ - CouplingError - Naming the disagreement. - """ + """Refuse a plane on another lattice, orientation or outside the port's band.""" if lattice_identity(plane.grid) != port.lattice or not np.array_equal( plane.grid.omega, port.grid.omega ): @@ -538,34 +491,9 @@ def _report( ) -> CouplingReport: """Assemble a report, refusing a coupling whose budget is exceeded. - Parameters - ---------- - plane : solprop.coupling.plane.FieldPlane - The source plane. - port : solprop.coupling.port.InputPort - The receiving port. - model : solprop.coupling.models.CouplingModel - The model. - index : numpy.ndarray - The common measure's reference index per bin. - incident, launched : numpy.ndarray - Per-bin energies, J. - captured : numpy.ndarray or None - Per-bin captured energy in the common metric, J. - captured_reason : str, optional - Why `captured` is None. - items : tuple of AdmissionItem, optional - The admission terms. - - Returns - ------- - CouplingReport - The report. - - Raises - ------ - CouplingError - If an item exceeds its own cap or the counted items exceed `model_error`. + - ``incident``, ``launched``: Per-bin energies, J. + - ``captured``: Per-bin captured energy in the common metric, J. + - ``captured_reason``: why `captured` is None. """ report = CouplingReport( model.kind, @@ -607,15 +535,7 @@ def _report( def _bin_weights(port: InputPort) -> Float[np.ndarray, " n_omega"]: """Return the energy weight of each stored bin of the port's lattice. - Parameters - ---------- - port : solprop.coupling.port.InputPort - The port. - - Returns - ------- - numpy.ndarray - :func:`solprop.fourier.energy_weights`, s per bin. + - Returns: :func:`solprop.fourier.energy_weights`, s per bin. """ grid = port.grid return fourier.energy_weights(grid.n_omega, grid.dt, kind=grid.kind) @@ -624,15 +544,7 @@ def _bin_weights(port: InputPort) -> Float[np.ndarray, " n_omega"]: def _occupied(energy: Float[np.ndarray, " n_omega"]) -> np.ndarray: """Return the bins that carry energy above rounding of the peak. - Parameters - ---------- - energy : numpy.ndarray - Per-bin energy, shape ``(n_omega,)``. - - Returns - ------- - numpy.ndarray - Boolean mask, shape ``(n_omega,)``. + - Returns: boolean mask, shape ``(n_omega,)``. """ peak = float(np.max(energy)) if energy.size else 0.0 return energy > _OCCUPIED * peak if peak > 0.0 else np.zeros(energy.shape, bool) @@ -641,20 +553,7 @@ def _occupied(energy: Float[np.ndarray, " n_omega"]) -> np.ndarray: def _weighted( defect: Float[np.ndarray, " n_omega"], energy: Float[np.ndarray, " n_omega"] ) -> float: - """Return a per-bin relative defect weighted by energy: a bound on the total. - - Parameters - ---------- - defect : numpy.ndarray - Relative defect per bin. - energy : numpy.ndarray - Energy per bin. - - Returns - ------- - float - ``sum(defect energy) / sum(energy)``; zero for a zero field. - """ + """Return a per-bin relative defect weighted by energy: a bound on the total.""" total = float(np.sum(energy)) return 0.0 if total == 0.0 else float(np.sum(np.abs(defect) * energy) / total) @@ -664,24 +563,8 @@ def _metric_matrices( ) -> Complex[np.ndarray, "n_omega n_modes n_modes"]: """Return a modal metric as one Hermitian matrix per bin. - Parameters - ---------- - metric : solprop.coupling.plane.PlaneMetric - An ``identity`` or ``dense`` metric. - n_modes : int - The number of columns. - n_omega : int - The number of bins. - - Returns - ------- - numpy.ndarray - Shape ``(n_omega, n_modes, n_modes)``, optics convention. - - Raises - ------ - CouplingError - For a spatial or unknown metric. + - ``metric``: an ``identity`` or ``dense`` metric. + - Returns: shape ``(n_omega, n_modes, n_modes)``, optics convention. """ if metric.kind == "identity": return np.broadcast_to( @@ -698,22 +581,9 @@ def _whitened_extremes( ) -> tuple[float, float]: """Return the extreme generalised eigenvalues of ``form`` against ``metric``. - Parameters - ---------- - form : numpy.ndarray - Hermitian quadratic form. - metric : numpy.ndarray - Hermitian positive-definite metric. - - Returns - ------- - tuple of float - The smallest and largest eigenvalue of ``metric^{-1/2} form metric^{-1/2}``. - - Raises - ------ - CouplingError - If the metric is not positive definite. + - ``metric``: Hermitian positive-definite metric. + - Returns: the smallest and largest eigenvalue of ``metric^{-1/2} form + metric^{-1/2}``. """ try: factor = np.linalg.cholesky(0.5 * (metric + metric.conj().T)) @@ -734,26 +604,7 @@ def _same_basis( ) -> tuple[Complex[np.ndarray, "n_modes n_pol n_omega"], CouplingReport]: """Recover a plane's coordinates exactly in the port's basis. - Parameters - ---------- - plane : solprop.coupling.plane.FieldPlane - The source plane. - port : solprop.coupling.port.InputPort - The receiving port. - model : solprop.coupling.models.CouplingModel - The ``same_basis`` model, with its optional basis change. - - Returns - ------- - tuple - The target samples and the report. - - Raises - ------ - CouplingError - If the bases differ with no declared change and no common E/H nodes, or a - declared change does not preserve energy, or the recovered field is not in - the port's span. + - Returns: the target samples and the report. """ incident = plane.spectral_energy() recovery: tuple[AdmissionItem, ...] = () @@ -799,28 +650,7 @@ def _same_basis( def _declared_change( plane: FieldPlane, port: InputPort, model: CouplingModel ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: - """Apply a declared basis change after checking it preserves energy. - - Parameters - ---------- - plane : solprop.coupling.plane.FieldPlane - The source plane, modal. - port : solprop.coupling.port.InputPort - The receiving port, modal. - model : solprop.coupling.models.CouplingModel - The model carrying the change. - - Returns - ------- - numpy.ndarray - The target samples, shape ``port.shape``. - - Raises - ------ - CouplingError - If the change names other bases, has the wrong shape, or does not map the - plane's metric onto the port's. - """ + """Apply a declared basis change after checking it preserves energy.""" change = model.basis_change assert change is not None if change.source != plane.basis.identity or change.target != port.basis.identity: @@ -870,23 +700,8 @@ def _flux_recovery( field is refused as outside the span. Both defects are reported (not counted: ``same_basis`` has no budget). - Parameters - ---------- - plane : solprop.coupling.plane.FieldPlane - A general modal plane read with its source simulation (``nodal`` E and H). - port : solprop.coupling.port.InputPort - A general modal port carrying E/H at fixed nodes. - - Returns - ------- - tuple - The target samples, shape ``port.shape``, and the ``target metric`` and - ``source metric`` items. - - Raises - ------ - CouplingError - If either side lacks E/H, the nodes differ, or the field is not in the span. + - Returns: the target samples, shape ``port.shape``, and the ``target metric`` and + ``source metric`` items. """ nodal = plane.nodal if nodal is None or nodal.magnetic is None: @@ -972,17 +787,9 @@ def _flux_matrix( (\mathbf e_m^*\times\mathbf h_n)]_z`; conjugating every field (optics against internal convention) conjugates F, so the formula holds in either. - Parameters - ---------- - electric, magnetic : numpy.ndarray - Basis E and H per √W, shape ``(n_nodes, 3, n_modes, n_omega)``. - weights : numpy.ndarray - Node area weights, m^2. - - Returns - ------- - numpy.ndarray - Shape ``(n_omega, n_modes, n_modes)``, dimensionless. + - ``electric``, ``magnetic``: basis E and H per √W, shape ``(n_nodes, 3, n_modes, + n_omega)``. + - ``weights``: node area weights, m^2. """ ex, ey = electric[:, 0], electric[:, 1] hx, hy = magnetic[:, 0], magnetic[:, 1] @@ -1007,20 +814,12 @@ def _pairing( :math:`B_m = \frac14\sum_q w_q[\mathbf E\times\mathbf h_m^* + \mathbf e_m^*\times\mathbf H]_z`. - Parameters - ---------- - field_e, field_h : numpy.ndarray - The field's E (V/m) and H (A/m) spectra at the nodes, shape - ``(n_nodes, 3, n_omega)``. - electric, magnetic : numpy.ndarray - Basis E and H per √W, shape ``(n_nodes, 3, n_modes, n_omega)``. - weights : numpy.ndarray - Node area weights, m^2. - - Returns - ------- - numpy.ndarray - In √W, shape ``(n_modes, n_omega)``. + - ``field_e``, ``field_h``: the field's E (V/m) and H (A/m) spectra at the nodes, + shape ``(n_nodes, 3, n_omega)``. + - ``electric``, ``magnetic``: basis E and H per √W, shape ``(n_nodes, 3, n_modes, + n_omega)``. + - ``weights``: node area weights, m^2. + - Returns: in √W, shape ``(n_modes, n_omega)``. """ first = np.einsum("q,qw,qmw->mw", weights, field_e[:, 0], magnetic[:, 1].conj()) first -= np.einsum("q,qw,qmw->mw", weights, field_e[:, 1], magnetic[:, 0].conj()) @@ -1076,17 +875,8 @@ def _channels( ) -> tuple[Float[np.ndarray, "n_channels 2"], Int[np.ndarray, " n_channels"]]: """Return each channel's Jones vector and mode index, channel order m-major. - Parameters - ---------- - profiles : solprop.coupling.models.ScalarModeBasis - The profiles. - n_pol : int - 1: each mode with its own polarisation; 2: each mode in x and in y. - - Returns - ------- - tuple of numpy.ndarray - Jones vectors, shape ``(n_channels, 2)``, and the mode of each channel. + - ``n_pol``: 1, each mode with its own polarisation; 2, each mode in x and in y. + - Returns: Jones vectors, shape ``(n_channels, 2)``, and the mode of each channel. """ if n_pol == 1: return np.asarray(profiles.jones), np.arange(profiles.n_modes) @@ -1125,24 +915,7 @@ def _scalar_paraxial( ) -> tuple[Complex[np.ndarray, "n_modes n_pol n_omega"], CouplingReport]: """Fit a scalar field onto a port's scalar profiles in the common measure. - Parameters - ---------- - plane : solprop.coupling.plane.FieldPlane - The source plane. - port : solprop.coupling.port.InputPort - The receiving port, carrying scalar profiles and a reference index. - model : solprop.coupling.models.CouplingModel - The model and its budget. - - Returns - ------- - tuple - The target samples and the report. - - Raises - ------ - CouplingError - For an unqualified pair or an exceeded budget. + - Returns: the target samples and the report. """ profiles = port.profiles if profiles is None or port.reference_index is None: @@ -1252,20 +1025,9 @@ def _kappa_star( :math:`\kappa_* = n_{\rm ref}(\omega/c)\sin\theta_*`, with :math:`1 - \cos\theta_* = \epsilon_m/8`. - Parameters - ---------- - index : numpy.ndarray - Reference index per bin. - omega : numpy.ndarray - Angular frequency per bin, rad/s. - epsilon : float - The model budget. - - Returns - ------- - numpy.ndarray - Transverse wavenumber in rad/m, zero where the index or the frequency is - not positive. + - ``omega``: angular frequency per bin, rad/s. + - Returns: transverse wavenumber in rad/m, zero where the index or the frequency is + not positive. """ sine = math.sin(math.acos(1.0 - epsilon / 8.0)) return np.where((index > 0) & (omega > 0), index * omega / C * sine, 0.0) @@ -1281,32 +1043,8 @@ def _spatial_fit( # noqa: PLR0917 - called positionally ) -> tuple[_Fit, Float[np.ndarray, " n_omega"], dict[str, float]]: """Return the scalar fit of a spatial plane onto a port's scalar profiles. - Parameters - ---------- - plane : solprop.coupling.plane.FieldPlane - A radial or Cartesian plane. - profiles : solprop.coupling.models.ScalarModeBasis - The target profiles. - jones : numpy.ndarray - Each target channel's Jones vector. - mode_of : numpy.ndarray - Each target channel's mode. - index : numpy.ndarray - Reference index per bin. - epsilon : float - The model budget. - - Returns - ------- - tuple - The fit, the plane's native energy per bin (J) and the envelope's range - parameters. - - Raises - ------ - CouplingError - If the plane occupies nonpropagating columns, or its metric is not an - admittance. + - Returns: the fit, the plane's native energy per bin (J) and the envelope's range + parameters. """ space = cast("RadialGrid | FreeGrid", plane.basis.space) metric = plane.metric @@ -1381,18 +1119,8 @@ def _spectral_edge_share( ) -> float: """Return a spatial field's energy share in the outer fifth of its spectral window. - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - The grid. - column_energy : numpy.ndarray - Each column's weighted squared samples per bin. - - Returns - ------- - float - Dimensionless; columns with a transverse wavenumber above 0.8 of the - window's largest along an axis. + - Returns: dimensionless; columns with a transverse wavenumber above 0.8 of the + window's largest along an axis. """ if isinstance(space, RadialGrid): k_r = np.asarray(space.k_r) @@ -1406,18 +1134,7 @@ def _spectral_edge_share( def _bin_like(plane: FieldPlane) -> Float[np.ndarray, " n_omega"]: - """Return the plane's bin energy weights. - - Parameters - ---------- - plane : solprop.coupling.plane.FieldPlane - The plane. - - Returns - ------- - numpy.ndarray - :func:`solprop.fourier.energy_weights` of its grid. - """ + """Return the plane's bin energy weights.""" grid = plane.grid return fourier.energy_weights(grid.n_omega, grid.dt, kind=grid.kind) @@ -1434,28 +1151,8 @@ def _modal_fit( ) -> tuple[_Fit, Float[np.ndarray, " n_omega"], dict[str, float]]: """Return the scalar fit of a modal plane's synthesised field onto target profiles. - Parameters - ---------- - plane : solprop.coupling.plane.FieldPlane - An ordinary modal plane with its profiles. - source : solprop.coupling.models.ScalarModeBasis - Its profiles. - profiles : solprop.coupling.models.ScalarModeBasis - The target profiles. - jones : numpy.ndarray - Each target channel's Jones vector. - mode_of : numpy.ndarray - Each target channel's mode. - index : numpy.ndarray - Reference index per bin. - epsilon : float - The model budget. - - Returns - ------- - tuple - The fit, the plane's native energy per bin (J) and the envelope's range - parameters. + - Returns: the fit, the plane's native energy per bin (J) and the envelope's range + parameters. """ source_jones, source_mode = _channels(source, plane.samples.shape[1]) overlap_matrix, gram_source, _, error = _profile_overlaps(source, profiles) @@ -1541,15 +1238,7 @@ def _profile_overlaps( quadrature error, together with each Gram's departure from the identity the profiles' closed-form normalisation fixes. - Parameters - ---------- - source, target : solprop.coupling.models.ScalarModeBasis - The two profile sets. - - Returns - ------- - tuple - The target-by-source overlaps, both Grams and the error estimate. + - Returns: the target-by-source overlaps, both Grams and the error estimate. """ radii = (source.radius, target.radius) outer = max(source.outer(), target.outer()) @@ -1561,15 +1250,8 @@ def _profile_overlaps( def overlaps(step: float) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """Return the three overlap matrices on a rule of the given panel length. - Parameters - ---------- - step : float - Largest panel length, m. - - Returns - ------- - tuple of numpy.ndarray - Target-by-source overlaps and the two Grams. + - ``step``: largest panel length, m. + - Returns: Target-by-source overlaps and the two Grams. """ r, w = radial_rule(radii, outer, panel=step) vs = source.values(r) @@ -1595,16 +1277,8 @@ def _step_index_parameters( ) -> dict[str, float]: """Return a step-index pair's envelope parameters at each profile wavelength. - Parameters - ---------- - source, target : solprop.coupling.models.ScalarModeBasis - The two LP sets. - - Returns - ------- - dict - ``v_source`` and ``v_target``: each fibre's V at its profile wavelength, - from its LP01 U and W. + - Returns: ``v_source`` and ``v_target``: each fibre's V at its profile wavelength, + from its LP01 U and W. """ return { "v_source": float(math.hypot(source.u[0], source.w[0])), @@ -1615,16 +1289,8 @@ def _step_index_parameters( def _beam_step_index_parameters(profiles: ScalarModeBasis) -> dict[str, float]: """Return a beam-into-fibre pair's envelope parameters at the profile wavelength. - Parameters - ---------- - profiles : solprop.coupling.models.ScalarModeBasis - The target fibre's LP set. - - Returns - ------- - dict - ``v_target``: the fibre's V from its LP01 U and W; ``modes``: the number of - distinct LP0m profiles (two polarisations of one profile count once). + - Returns: ``v_target``: the fibre's V from its LP01 U and W; ``modes``: the number + of distinct LP0m profiles (two polarisations of one profile count once). """ return { "v_target": float(math.hypot(profiles.u[0], profiles.w[0])), @@ -1644,24 +1310,6 @@ def _target_metric( The profiles are orthonormal, so in true-power coordinates :math:`G = \operatorname{diag}(n_{\rm ref}/\operatorname{Re}n_{\rm eff})` and ``Q`` is the identity: the whitened eigenvalues are the ratios. - - Parameters - ---------- - profiles : solprop.coupling.models.ScalarModeBasis - The target profiles. - mode_of : numpy.ndarray - Each channel's mode. - index : numpy.ndarray - Reference index per bin. - occupied : numpy.ndarray - Occupied bins. - epsilon : float - The model budget. - - Returns - ------- - AdmissionItem - ``target metric``, capped at ``epsilon / 8``. """ effective = profiles.effective_index[mode_of][:, occupied] if effective.size == 0: @@ -1688,27 +1336,10 @@ def _target_angular_scalar( # noqa: PLR0917 - called positionally ) -> AdmissionItem: """Return the retained target subspace's angular defect, its worst unit field. - Parameters - ---------- - profiles : solprop.coupling.models.ScalarModeBasis - The target profiles. - channels : tuple of numpy.ndarray - Each channel's Jones vector and mode (:func:`_channels`). - index : numpy.ndarray - Reference index per bin. - port : solprop.coupling.port.InputPort - The port. - occupied : numpy.ndarray - Occupied bins. - epsilon : float - The model budget. - - Returns - ------- - AdmissionItem - ``angular`` of the target subspace: the largest eigenvalue of - ``(1 - cos theta*) K + (1 - (1 - cos theta*)) T`` against the channel Gram - ``K``, with T the tail Gram. + - ``channels``: each channel's Jones vector and mode (:func:`_channels`). + - Returns: ``angular`` of the target subspace: the largest eigenvalue of ``(1 - cos + theta*) K + (1 - (1 - cos theta*)) T`` against the channel Gram ``K``, with T the + tail Gram. """ jones, mode_of = channels polarisation = jones @ jones.T @@ -1738,19 +1369,8 @@ def _source_metric_item( ) -> AdmissionItem: """Return the relative defect between the common measure and the source's own. - Parameters - ---------- - incident : numpy.ndarray - The incident energy per bin in the common measure, J. - native : numpy.ndarray - The plane's energy per bin in its native metric, J. - epsilon : float - The model budget. - - Returns - ------- - AdmissionItem - ``source metric``: ``sum |M_0 - Q_A| / sum Q_A``, capped at ``epsilon/8``. + - ``incident``: the incident energy per bin in the common measure, J. + - ``native``: the plane's energy per bin in its native metric, J. """ total = float(np.sum(native)) value = 0.0 if total == 0.0 else float(np.sum(np.abs(incident - native)) / total) @@ -1779,19 +1399,8 @@ def _leaky_parameters( bins beyond the range, which the envelope does not bound and the item counts in full. - Parameters - ---------- - profiles : solprop.coupling.models.ScalarModeBasis - The target's capillary profiles. - plane : solprop.coupling.plane.FieldPlane - The source plane. - incident : numpy.ndarray - Incident energy per bin, J. - - Returns - ------- - dict - ``leaky`` and ``uncovered``, dimensionless. + - ``incident``: incident energy per bin, J. + - Returns: ``leaky`` and ``uncovered``, dimensionless. """ omega = plane.grid.omega delta = cast("np.ndarray", profiles.leaky(omega)) @@ -1819,22 +1428,10 @@ def _uncovered_v_share( each bin. A bin where any fibre's V is outside the range is not bounded by the envelope, so its share of the incident energy is counted in full. - Parameters - ---------- - fibres : collections.abc.Mapping - Each fibre's LP set, with its core and cladding indices, by the name of - its V range in `envelope` (``v_source``, ``v_target``). - envelope : solprop.coupling.envelopes.PairEnvelope - The pair's envelope. - omega : numpy.ndarray - Absolute angular frequency of each bin, rad/s. - incident : numpy.ndarray - Incident energy per bin, J. - - Returns - ------- - float - Dimensionless. + - ``fibres``: each fibre's LP set, with its core and cladding indices, by the name + of its V range in `envelope` (``v_source``, ``v_target``). + - ``omega``: absolute angular frequency of each bin, rad/s. + - ``incident``: incident energy per bin, J. """ beyond = np.zeros(omega.shape, bool) for name, basis in fibres.items(): @@ -1863,32 +1460,6 @@ def _contrast_items( # noqa: PLR0917 - called positionally for an incident beam), unless its recorded envelope covers it. Every pair needs its envelope to cover it: the capillary one, the fibre-pair one, or for a beam into a fibre :data:`~solprop.coupling.envelopes.STEP_INDEX_BEAM_ENVELOPE`. - - Parameters - ---------- - profiles : solprop.coupling.models.ScalarModeBasis - The target profiles. - plane : solprop.coupling.plane.FieldPlane - The source plane; for a step-index target, a spatial plane or a modal plane - with its profiles. - index : numpy.ndarray - Reference index per bin. - occupied : numpy.ndarray - Occupied bins. - epsilon : float - The model budget. - parameters : dict - The pair's envelope parameters. - - Returns - ------- - list of AdmissionItem - ``contrast`` and ``envelope``. - - Raises - ------ - CouplingError - If no recorded envelope covers the pair. """ envelope: PairEnvelope = CAPILLARY_ENVELOPE if profiles.family == "step_index": @@ -1964,17 +1535,7 @@ def _scalar_gain(fit: _Fit, occupied: np.ndarray) -> float: source's native coordinates to the target's, ``C Q_A^-1 C^H`` against ``Q_B`` (the identity), minus one. - Parameters - ---------- - fit : _Fit - The fit, with its ``gain_form``. - occupied : numpy.ndarray - Occupied bins. - - Returns - ------- - float - Dimensionless; negative for a strictly contracting map. + - Returns: dimensionless; negative for a strictly contracting map. """ form = cast("np.ndarray", fit.gain_form) gain = -1.0 @@ -2025,24 +1586,7 @@ def _vector_paraxial( in the internal convention, where the port's tables live, and the launch is converted to the optics convention at the end. - Parameters - ---------- - plane : solprop.coupling.plane.FieldPlane - The source plane. - port : solprop.coupling.port.InputPort - A general modal port carrying E/H at fixed nodes and its contrast. - model : solprop.coupling.models.CouplingModel - The model and its budget. - - Returns - ------- - tuple - The target samples and the report. - - Raises - ------ - CouplingError - For an unqualified pair or an exceeded budget. + - Returns: the target samples and the report. """ if ( port.route != "general_modal" @@ -2130,22 +1674,7 @@ def _vector_envelope_item( a value: its omitted facet reflection is bounded only by the contrast screen and the closure. - Parameters - ---------- - port : solprop.coupling.port.InputPort - The general modal port. - incident : numpy.ndarray - Incident energy per bin, J. - - Returns - ------- - AdmissionItem - ``envelope``. - - Raises - ------ - CouplingError - If the weighted parameters lie outside the envelope's range. + - ``incident``: incident energy per bin, J. """ if port.normalised_frequency is None: return AdmissionItem( @@ -2210,22 +1739,8 @@ def _vector_coordinates( of ``C = F^-1 L`` (``L`` the pairing per unit source coordinate) against the native metrics. - Parameters - ---------- - source : _Source - The source's field map. - port : solprop.coupling.port.InputPort - The general modal port. - admittance : numpy.ndarray - :math:`Y_0` per bin, A/V. - occupied : numpy.ndarray - Occupied bins. - - Returns - ------- - tuple - Internal-convention amplitudes, shape ``(n_modes, n_omega)``; the largest - apparent gain; the largest whitened defect of F against the native Q. + - Returns: Internal-convention amplitudes, shape ``(n_modes, n_omega)``; the largest + apparent gain; the largest whitened defect of F against the native Q. """ weights = np.asarray(port.node_weights) electric = np.conj(cast("np.ndarray", port.electric)) @@ -2276,26 +1791,6 @@ def _gain_item( Its excess over one is rounding below :data:`PASSIVITY_ROUNDING`, a model defect within the model's own metric, angular and closure items, and refused beyond them. It is never rescaled or clipped. - - Parameters - ---------- - gain : float - The largest whitened squared singular value minus one. - items : list of AdmissionItem - The items already measured. - scope : {'map', 'field'}, optional - Whether the certificate covers the whole map or the coordinates this field - occupies. - - Returns - ------- - AdmissionItem - ``passivity``. - - Raises - ------ - CouplingError - If the gain exceeds the model's own defects. """ allowance = sum( item.value @@ -2327,29 +1822,7 @@ def _vector_source( index: Float[np.ndarray, " n_omega"], epsilon: float, ) -> _Source: - """Return how a plane's coordinates make a field at a port's nodes. - - Parameters - ---------- - plane : solprop.coupling.plane.FieldPlane - A radial or Cartesian plane, or an ordinary modal plane with profiles. - port : solprop.coupling.port.InputPort - The general modal port. - index : numpy.ndarray - Reference index per bin. - epsilon : float - The model budget. - - Returns - ------- - _Source - The field map, coordinates, native weights, incident norm and items. - - Raises - ------ - CouplingError - For an unsupported plane. - """ + """Return how a plane's coordinates make a field at a port's nodes.""" nodes = cast("np.ndarray", port.nodes) if plane.kind in ("radial", "cartesian"): return _spatial_source(plane, nodes, index, epsilon) @@ -2369,24 +1842,7 @@ def _spatial_angular( index: Float[np.ndarray, " n_omega"], epsilon: float, ) -> AdmissionItem: - """Return a spatial plane's angular defect, energy-weighted (section 4.6). - - Parameters - ---------- - plane : solprop.coupling.plane.FieldPlane - A radial or Cartesian plane. - column_energy : numpy.ndarray - Each column's weighted squared samples per bin. - index : numpy.ndarray - Reference index per bin. - epsilon : float - The model budget. - - Returns - ------- - AdmissionItem - ``angular`` of the incident field. - """ + """Return a spatial plane's angular defect, energy-weighted (section 4.6).""" space = cast("RadialGrid | FreeGrid", plane.basis.space) kappa = np.sqrt( np.asarray(space.k_r) ** 2 @@ -2415,22 +1871,7 @@ def _refuse_nonpropagating( admittance: Float[np.ndarray, "n_columns n_omega"], occupied: np.ndarray, ) -> None: - """Refuse a plane with more than rounding in nonpropagating columns. - - Parameters - ---------- - column_energy : numpy.ndarray - Each column's weighted squared samples per bin. - admittance : numpy.ndarray - Each column's real admittance per bin. - occupied : numpy.ndarray - Occupied bins. - - Raises - ------ - CouplingError - If the nonpropagating share exceeds 1e-12. - """ + """Refuse a plane with more than rounding in nonpropagating columns.""" dead = (admittance <= 0.0) & occupied[None, :] total = float(np.sum(column_energy[:, occupied])) share = 0.0 if total == 0.0 else float(np.sum(column_energy[dead])) / total @@ -2450,27 +1891,7 @@ def _spatial_source( ) -> _Source: """Return a radial or Cartesian plane's field map at a guide's nodes. - Parameters - ---------- - plane : solprop.coupling.plane.FieldPlane - A radial or Cartesian plane. - nodes : numpy.ndarray - The guide's nodes, m. - index : numpy.ndarray - Reference index per bin. - epsilon : float - The model budget. - - Returns - ------- - _Source - The field map and its items. - - Raises - ------ - CouplingError - If the nodes reach beyond a window whose exterior is unknown, or the plane - occupies nonpropagating columns. + - ``nodes``: the guide's nodes, m. """ grid = plane.grid space = cast("RadialGrid | FreeGrid", plane.basis.space) @@ -2525,21 +1946,7 @@ def _modal_source( ) -> _Source: """Return an ordinary modal plane's synthesised field map at a guide's nodes. - Parameters - ---------- - plane : solprop.coupling.plane.FieldPlane - An ordinary modal plane with its profiles. - nodes : numpy.ndarray - The guide's nodes, m. - index : numpy.ndarray - Reference index per bin. - epsilon : float - The model budget. - - Returns - ------- - _Source - The field map and its items. + - ``nodes``: the guide's nodes, m. """ grid = plane.grid source = cast("ScalarModeBasis", plane.profiles) @@ -2554,15 +1961,7 @@ def _modal_source( def transfer(k: int) -> np.ndarray: """Return the field at the nodes per unit true-power coefficient. - Parameters - ---------- - k : int - The bin. - - Returns - ------- - numpy.ndarray - Shape ``(n_nodes, 2, n_channels)``, V/m per √W. + - Returns: shape ``(n_nodes, 2, n_channels)``, V/m per √W. """ scale = np.sqrt(2.0 / (EPS0 * C)) / root[:, k] return values.T[:, None, :] * (jones.T * scale[None, :])[None, :, :] @@ -2599,29 +1998,7 @@ def _modal_angular( ) -> AdmissionItem: """Return a modal source field's angular defect, energy-weighted. - Parameters - ---------- - source : solprop.coupling.models.ScalarModeBasis - The source profiles. - jones : numpy.ndarray - Each source channel's Jones vector. - mode_of : numpy.ndarray - Each source channel's mode. - scaled : numpy.ndarray - The vacuum-normalised source amplitudes ``b / sqrt(Re n_eff)``. - norm : numpy.ndarray - The incident norm in the common measure per bin. - index : numpy.ndarray - Reference index per bin. - plane : solprop.coupling.plane.FieldPlane - The source plane. - epsilon : float - The model budget. - - Returns - ------- - AdmissionItem - ``angular`` of the incident field. + - ``scaled``: the vacuum-normalised source amplitudes ``b / sqrt(Re n_eff)``. """ star = _kappa_star(index, plane.grid.omega, epsilon) defect = np.zeros(plane.grid.n_omega) @@ -2645,18 +2022,7 @@ def _modal_angular( def _window(space: RadialGrid | FreeGrid) -> float: - """Return the radius inside which a spatial plane's interpolant is its field, m. - - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - The grid. - - Returns - ------- - float - The radial window, or the smaller Cartesian half-width. - """ + """Return the radius inside which a spatial plane's interpolant is its field, m.""" if isinstance(space, RadialGrid): return float(space.hankel.radius) return float(min(np.max(np.abs(space.x)), np.max(np.abs(space.y)))) @@ -2674,19 +2040,7 @@ def _column_functions( DFT's trigonometric interpolant ``f(x) = sum_j S_j exp(i k_j (x - x0)) / N``. Zero at nodes outside the window. - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - The grid. - nodes : numpy.ndarray - Node positions ``(x, y)``, m, shape ``(n_nodes, 2)``. - inside : numpy.ndarray - Nodes inside the window. - - Returns - ------- - numpy.ndarray - Shape ``(n_nodes, n_columns)``. + - ``nodes``: node positions ``(x, y)``, m, shape ``(n_nodes, 2)``. """ if isinstance(space, RadialGrid): radius = np.hypot(nodes[:, 0], nodes[:, 1]) @@ -2719,19 +2073,7 @@ def _window_edge_share( share in the outer tenth of its window, which bounds the tail the zero exterior discards to leading order. - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - The grid. - samples : numpy.ndarray - Internal-convention columns. - inside : numpy.ndarray - Port nodes inside the window. - - Returns - ------- - float - Dimensionless. + - ``samples``: Internal-convention columns. """ if np.all(inside): return 0.0 @@ -2755,17 +2097,7 @@ def _probe_bins( ) -> np.ndarray: """Return up to :data:`_TARGET_BINS` occupied bins spanning the band. - Parameters - ---------- - occupied : numpy.ndarray - Occupied bins. - norm : numpy.ndarray - Incident norm per bin. - - Returns - ------- - numpy.ndarray - Bin indices: the edges, the peak and points between. + - Returns: bin indices: the edges, the peak and points between. """ live = np.flatnonzero(occupied) if live.size == 0: @@ -2793,23 +2125,7 @@ def _closure_item( # noqa: PLR0917 - called positionally :math:`Y_0\hat z\times\mathbf e_t`; the energy discrepancy is :math:`2\delta + \delta^2` (section 4.7). - Parameters - ---------- - electric, magnetic : numpy.ndarray - Basis E and H at the nodes, internal convention. - weights : numpy.ndarray - Node area weights. - admittance : numpy.ndarray - :math:`Y_0` per bin, A/V. - probes : numpy.ndarray - The bins evaluated. - epsilon : float - The model budget. - - Returns - ------- - AdmissionItem - ``closure``, capped at ``epsilon / 8``. + - ``electric``, ``magnetic``: basis E and H at the nodes, internal convention. """ worst = 0.0 for k in probes: @@ -2848,25 +2164,7 @@ def _target_angular_vector( # noqa: PLR0917 - called positionally The transverse E of each channel is Fourier transformed by its node quadrature on a polar rule inside the cone; the tail is the node Gram minus the cone. - Parameters - ---------- - electric : numpy.ndarray - Basis E at the nodes, internal convention. - weights : numpy.ndarray - Node area weights. - port : solprop.coupling.port.InputPort - The port. - index : numpy.ndarray - Reference index per bin. - probes : numpy.ndarray - The bins evaluated. - epsilon : float - The model budget. - - Returns - ------- - AdmissionItem - ``angular`` of the target subspace. + - ``electric``: basis E at the nodes, internal convention. """ nodes = cast("np.ndarray", port.nodes) extent = float(np.max(np.hypot(nodes[:, 0], nodes[:, 1]))) diff --git a/src/solprop/coupling/elements.py b/src/solprop/coupling/elements.py index f81a2942f..b133ead6d 100644 --- a/src/solprop/coupling/elements.py +++ b/src/solprop/coupling/elements.py @@ -198,13 +198,10 @@ def __post_init__(self) -> None: object.__setattr__(self, "frame", frame) @property - def n_pol(self) -> int: - """Return the number of stored polarisation components. + def n_pol(self) -> int: # numpydoc ignore=RT01 + """Number of stored polarisation components. - Returns - ------- - int - One for a scalar state, two for ``(x, y)``. + One for a scalar state, two for ``(x, y)``. """ return int(self.spectrum.shape[1]) @@ -246,20 +243,9 @@ def with_spectrum( def _unit_frame(frame: ArrayLike) -> Float[np.ndarray, "3 3"]: """Return a validated, read-only right-handed orthonormal frame. - Parameters - ---------- - frame : array_like - Rows x, y and forward direction in laboratory coordinates, shape ``(3, 3)``. - - Returns - ------- - numpy.ndarray - A read-only float64 copy. - - Raises - ------ - CouplingError - If the frame is not finite, orthonormal and right-handed. + - ``frame``: rows x, y and forward direction in laboratory coordinates, shape ``(3, + 3)``. + - Returns: a read-only float64 copy. """ values = np.array(frame, dtype=np.float64, copy=True) if ( @@ -472,36 +458,12 @@ def apply(self, state: SpatialState) -> ElementResult: def _band(grid: TimeGrid) -> Bool[np.ndarray, " n_omega"]: - """Return the bins that can carry field: active and at positive frequency. - - Parameters - ---------- - grid : solprop.grid.TimeGrid - Temporal grid. - - Returns - ------- - numpy.ndarray - Boolean mask, shape ``(n_omega,)``. - """ + """Return the bins that can carry field: active and at positive frequency.""" return grid.active_mask & (grid.omega > 0) def _index(medium: SpatialMedium, grid: TimeGrid) -> Complex[np.ndarray, " n_omega"]: - """Return the medium's complex index on the band, zero elsewhere. - - Parameters - ---------- - medium : solprop.spatial.SpatialMedium - Homogeneous medium. - grid : solprop.grid.TimeGrid - Temporal grid. - - Returns - ------- - numpy.ndarray - Refractive index, dimensionless, shape ``(n_omega,)``. - """ + """Return the medium's complex index on the band, zero elsewhere.""" band = _band(grid) index = np.zeros(grid.n_omega, dtype=np.complex128) index[band] = medium.index(grid.omega[band]) @@ -511,17 +473,7 @@ def _index(medium: SpatialMedium, grid: TimeGrid) -> Complex[np.ndarray, " n_ome def _same_medium(first: SpatialMedium, second: SpatialMedium, grid: TimeGrid) -> bool: """Return whether two media have the same index on the band. - Parameters - ---------- - first, second : solprop.spatial.SpatialMedium - The media. - grid : solprop.grid.TimeGrid - Temporal grid. - - Returns - ------- - bool - True for the same object or exactly equal indices on every band bin. + True for the same object or exactly equal indices on every band bin: no tolerance. """ return first is second or bool( np.array_equal(_index(first, grid), _index(second, grid)) @@ -531,15 +483,7 @@ def _same_medium(first: SpatialMedium, second: SpatialMedium, grid: TimeGrid) -> def _transverse_squares(space: RadialGrid | FreeGrid) -> Float[np.ndarray, " n_modes"]: """Return the squared transverse wave number of each stored column. - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - Transverse representation. - - Returns - ------- - numpy.ndarray - ``k_r**2`` or ``kperp_sq`` in m^-2, shape ``(n_modes,)``. + - Returns: ``k_r**2`` or ``kperp_sq`` in m^-2, shape ``(n_modes,)``. """ return space.k_r**2 if isinstance(space, RadialGrid) else space.kperp_sq @@ -549,18 +493,8 @@ def _to_field( ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: """Synthesise the field samples from a stored transverse spectrum. - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - Transverse representation. - spectrum : numpy.ndarray - Stored spectrum, shape ``(n_modes, n_pol, n_omega)``. - - Returns - ------- - numpy.ndarray - Field samples in the internal temporal convention, V/m times the temporal - DFT scaling, at the grid's nodes (x fastest on a Cartesian grid). + - Returns: field samples in the internal temporal convention, V/m times the temporal + DFT scaling, at the grid's nodes (x fastest on a Cartesian grid). """ if isinstance(space, RadialGrid): return np.moveaxis(space.hankel.inverse(np.moveaxis(spectrum, 0, -1)), -1, 0) @@ -573,17 +507,7 @@ def _to_spectrum( ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: """Transform field samples to the stored transverse spectrum. - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - Transverse representation. - samples : numpy.ndarray - Field samples at the grid's nodes, shape ``(n_modes, n_pol, n_omega)``. - - Returns - ------- - numpy.ndarray - The raw Hankel or DFT spectrum of the samples, the same shape. + - Returns: the raw Hankel or DFT spectrum of the samples, the same shape. """ if isinstance(space, RadialGrid): return np.moveaxis(space.hankel.forward(np.moveaxis(samples, 0, -1)), -1, 0) @@ -594,15 +518,8 @@ def _to_spectrum( def _nodes(space: RadialGrid | FreeGrid) -> tuple[np.ndarray, np.ndarray]: """Return the x and y coordinates of the grid's nodes. - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - Transverse representation; radial nodes lie on the x axis. - - Returns - ------- - tuple of numpy.ndarray - ``x`` and ``y`` in m, each shape ``(n_nodes,)``, x fastest. + - ``space``: transverse representation; radial nodes lie on the x axis. + - Returns: ``x`` and ``y`` in m, each shape ``(n_nodes,)``, x fastest. """ if isinstance(space, RadialGrid): return np.asarray(space.r), np.zeros(space.n_r) @@ -615,15 +532,7 @@ def _nodes(space: RadialGrid | FreeGrid) -> tuple[np.ndarray, np.ndarray]: def _flux(state: SpatialState) -> SpatialFlux: """Resolve the axial-flux measure of the state's plane. - Parameters - ---------- - state : SpatialState - The plane; its medium is homogeneous, so the position is immaterial. - - Returns - ------- - solprop.spatial.SpatialFlux - The measure of SEP 34. + The plane's medium is homogeneous, so the position is immaterial. """ return SpatialFlux.resolve(state.grid, state.space, state.medium, z=0.0) @@ -647,17 +556,7 @@ class _Measured: def _measure(state: SpatialState, flux: SpatialFlux) -> _Measured: """Measure the physical energy of a state. - Parameters - ---------- - state : SpatialState - The plane. - flux : solprop.spatial.SpatialFlux - Its measure. - - Returns - ------- - _Measured - Total and per-bin energy, J. + - Returns: total and per-bin energy, J. """ return _Measured( spatial_energy(state.spectrum, flux), @@ -687,10 +586,7 @@ class _Angular: def total(self) -> float: """Return the whole reference-measure content. - Returns - ------- - float - ``cone + tail`` in J. + - Returns: ``cone + tail`` in J. """ return self.cone + self.tail @@ -704,19 +600,9 @@ def _angular_content( in place of the axial admittance; the obliquity of a propagating column is the admittance over the index, :math:`\cos\theta`. - Parameters - ---------- - state : SpatialState - The plane. - flux : solprop.spatial.SpatialFlux - Its measure. - cone_cosine : float - :math:`\cos\theta_*`; columns with a smaller obliquity are the tail. - - Returns - ------- - _Angular - The split content, J. + - ``cone_cosine``: :math:`\cos\theta_*`; columns with a smaller obliquity are the + tail. + - Returns: the split content, J. """ grid = state.grid weights = fourier.energy_weights(grid.n_omega, grid.dt, kind=grid.kind) @@ -753,21 +639,8 @@ def _reference_norm( ) -> float: """Return the squared reference-measure norm of a spectrum, up to a constant. - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - Transverse representation. - grid : solprop.grid.TimeGrid - Temporal grid. - index : numpy.ndarray - Real index per bin, zero off band, shape ``(n_omega,)``. - spectrum : numpy.ndarray - Stored spectrum, shape ``(n_modes, n_pol, n_omega)``. - - Returns - ------- - float - ``sum v_j gamma_k n_k |X_jpk|^2``, in the units of ``|X|^2`` m^-2 s. + - ``index``: real index per bin, zero off band. + - Returns: ``sum v_j gamma_k n_k |X_jpk|^2``, in the units of ``|X|^2`` m^-2 s. """ weights = fourier.energy_weights(grid.n_omega, grid.dt, kind=grid.kind) density = np.sum(np.abs(spectrum) ** 2, axis=1) @@ -779,16 +652,6 @@ def _edge_nodes(space: RadialGrid | FreeGrid) -> Bool[np.ndarray, " n_nodes"]: The room outside the requested size where the grid has some, otherwise the outer tenth of each half-width. - - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - Transverse representation. - - Returns - ------- - numpy.ndarray - Boolean mask over the nodes, shape ``(n_nodes,)``. """ if space.collar_fraction > 0: return np.asarray(space.window < 1) @@ -809,21 +672,7 @@ def _node_content( ) -> Float[np.ndarray, " n_modes"]: """Return each node's reference-measure content, up to a constant. - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - Transverse representation. - grid : solprop.grid.TimeGrid - Temporal grid. - index : numpy.ndarray - Real index per bin, zero off band. - samples : numpy.ndarray - Field samples at the nodes, shape ``(n_modes, n_pol, n_omega)``. - - Returns - ------- - numpy.ndarray - Area-weighted content per node, shape ``(n_modes,)``. + - ``index``: real index per bin, zero off band. """ weights = fourier.energy_weights(grid.n_omega, grid.dt, kind=grid.kind) density = np.sum(np.abs(samples) ** 2, axis=1) @@ -835,16 +684,8 @@ def _node_content( def _edge_share(state: SpatialState) -> float: """Return the share of the field in the window's outer band. - Parameters - ---------- - state : SpatialState - The plane. - - Returns - ------- - float - Reference-measure share of the field at the edge nodes, in [0, 1]; zero for - a zero field. + - Returns: Reference-measure share of the field at the edge nodes, in [0, 1]; zero + for a zero field. """ index = np.real(_index(state.medium, state.grid)) content = _node_content( @@ -863,16 +704,6 @@ def _round_trip_error(state: SpatialState) -> float: and transformed back differs by :math:`\delta` in the reference norm, which bounds the relative energy discrepancy by :math:`2\delta+\delta^2` (SEP 31 §4.7). On a Cartesian grid it is rounding. - - Parameters - ---------- - state : SpatialState - The plane. - - Returns - ------- - float - :math:`2\delta+\delta^2`; zero for a zero field. """ index = np.real(_index(state.medium, state.grid)) norm = _reference_norm(state.space, state.grid, index, state.spectrum) @@ -922,25 +753,9 @@ def _report( ) -> ElementReport: """Close an element's ledger and build its report. - Parameters - ---------- - element : str - Description of the element. - measured_in, measured_out : _Measured - Energies of the input and output planes. - specified_loss : float, optional - Declared loss, J. - removed : float, optional - Intentional removal, J. - numerical_error : float, optional - Estimated numerical error, J. - extras : _Extras or None, optional - The optional report fields. - - Returns - ------- - ElementReport - The report, with the approximation error that closes the ledger. + - ``specified_loss``: declared loss, J. + - ``removed``: intentional removal, J. + - ``numerical_error``: estimated numerical error, J. """ extras = _Extras() if extras is None else extras approximation = measured_out.energy - measured_in.energy + specified_loss + removed @@ -970,24 +785,8 @@ def _spectral_values( ) -> Complex[np.ndarray, " n_band"]: """Evaluate a constant or a function of frequency on the band. - Parameters - ---------- - values : float, complex or callable - A constant, or a function of positive angular frequency. - omega : numpy.ndarray - Band frequencies in rad/s, shape ``(n_band,)``. - name : str - The quantity, for messages. - - Returns - ------- - numpy.ndarray - Complex values, shape ``(n_band,)``. - - Raises - ------ - CouplingError - If the values are not finite or do not match the band. + - ``values``: a constant, or a function of positive angular frequency. + - ``omega``: band frequencies in rad/s, shape ``(n_band,)``. """ result = values(omega) if callable(values) else values array = np.broadcast_to(np.asarray(result, dtype=np.complex128), omega.shape) @@ -998,18 +797,7 @@ def _spectral_values( def _check_model_error(model_error: float) -> None: - """Refuse a model-error budget outside (0, 1). - - Parameters - ---------- - model_error : float - The budget. - - Raises - ------ - CouplingError - If it is not finite and strictly between zero and one. - """ + """Refuse a model-error budget outside (0, 1).""" if not (math.isfinite(model_error) and 0 < model_error < 1): msg = f"model_error is a required budget in (0, 1), got {model_error!r}" raise CouplingError(msg) @@ -1019,18 +807,7 @@ def _check_model_error(model_error: float) -> None: def _occupied(content: Float[np.ndarray, " n_modes"]) -> Bool[np.ndarray, " n_modes"]: - """Return the nodes whose content is above the diagnostic floor. - - Parameters - ---------- - content : numpy.ndarray - Content per node. - - Returns - ------- - numpy.ndarray - Boolean mask; all False for a zero field. - """ + """Return the nodes whose content is above the diagnostic floor.""" peak = float(np.max(content)) if content.size else 0.0 return content > _OCCUPIED * peak if peak > 0 else np.zeros(content.shape, bool) @@ -1045,20 +822,10 @@ def _phase_step( A step of pi or more samples the screen's phase below its Nyquist rate: the spatial frequencies it generates alias onto the grid. - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - Transverse representation. - transmission : numpy.ndarray - Optics-convention transmission at the nodes, shape ``(n_modes, n_band)``. - occupied : numpy.ndarray - Nodes that hold field, shape ``(n_modes,)``. - - Returns - ------- - float - The largest step in rad over neighbours that are both occupied and both - transmitting; zero if there are none. + - ``transmission``: Optics-convention transmission at the nodes, shape ``(n_modes, + n_band)``. + - Returns: the largest step in rad over neighbours that are both occupied and both + transmitting; zero if there are none. """ pairs: list[tuple[np.ndarray, np.ndarray, np.ndarray]] = [] if isinstance(space, RadialGrid): @@ -1091,36 +858,13 @@ def _apply_screen( ) -> ElementResult: """Apply a thin local screen and certify it against its budget. - Parameters - ---------- - state : SpatialState - The input plane. - transmission : numpy.ndarray - Optics-convention field transmission at the nodes on the band bins, - shape ``(n_modes, n_band)``. - element : str - Description, for the report. - model_error : float - The required budget in (0, 1). - phase_only : bool - Whether the screen is declared unit-modulus; its local loss is then zero. - gradient : numpy.ndarray or None, optional - The screen's analytic phase gradient magnitude in rad/m at the nodes, - shape ``(n_modes, n_band)``, when it is known. Sampled phases wrap, so - only an analytic gradient detects a step beyond pi. - notes : tuple of str, optional - Statements for the report. - - Returns - ------- - ElementResult - The output plane and its report with the certificate. - - Raises - ------ - CouplingError - If the screen amplifies anywhere, its phase is undersampled where the - field is, or the certificate exceeds the budget. + - ``transmission``: Optics-convention field transmission at the nodes on the band + bins, shape ``(n_modes, n_band)``. + - ``model_error``: the required budget in (0, 1). + - ``phase_only``: the screen is declared unit-modulus, so its local loss is zero. + - ``gradient``: the screen's analytic phase gradient magnitude in rad/m at the + nodes, shape ``(n_modes, n_band)``, when it is known. Sampled phases wrap, so only + an analytic gradient detects a step beyond pi. """ _check_model_error(model_error) if not np.all(np.isfinite(transmission)): @@ -1189,27 +933,7 @@ def _screen_certificate( ) -> ScreenCertificate: """Evaluate the thin-screen defect on input and output and admit or refuse. - Parameters - ---------- - before, after : SpatialState - The input and output planes, on one representation and medium. - flux : solprop.spatial.SpatialFlux - Their common measure. - model_error : float - The budget in (0, 1). - numerical : float - Relative numerical error added to the ratio. - - Returns - ------- - ScreenCertificate - The admitted certificate. - - Raises - ------ - CouplingError - If a nonzero input carries no forward flux or the defect ratio exceeds - the budget. + - ``model_error``: the budget in (0, 1). """ cosine = 1 - model_error / 8 if not np.any(before.spectrum): @@ -1245,26 +969,9 @@ def _evaluate_transmission( ) -> Complex[np.ndarray, "n_nodes n_band"]: """Evaluate a transmission callable and check its shape. - Parameters - ---------- - transmission : callable - ``T(x, y, omega)`` in the optics convention. - x, y : numpy.ndarray - Node positions in m, shape ``(n_nodes,)``. - omega : numpy.ndarray - Band frequencies in rad/s, shape ``(n_band,)``. - element : str - Description, for messages. - - Returns - ------- - numpy.ndarray - The transmission, shape ``(n_nodes, n_band)``. - - Raises - ------ - CouplingError - If the result does not broadcast to ``(n_nodes, n_band)``. + - ``transmission``: ``T(x, y, omega)`` in the optics convention. + - ``x``, ``y``: node positions in m, shape ``(n_nodes,)``. + - ``omega``: band frequencies in rad/s, shape ``(n_band,)``. """ values = np.asarray(transmission(x, y, omega), dtype=np.complex128) try: @@ -1288,26 +995,7 @@ def _symmetric_transmission( The order-zero Hankel representation holds a cylindrically symmetric field; a screen that depends on the azimuth would break it silently. - Parameters - ---------- - transmission : callable - ``T(x, y, omega)``. - space : solprop.grid.RadialGrid - Radial representation. - omega : numpy.ndarray - Band frequencies in rad/s. - element : str - Description, for messages. - - Returns - ------- - numpy.ndarray - The transmission at the radial nodes, shape ``(n_r, n_band)``. - - Raises - ------ - CouplingError - If the transmission depends on the azimuth. + - ``omega``: band frequencies in rad/s. """ radius = np.asarray(space.r) reference = _evaluate_transmission( @@ -1534,22 +1222,8 @@ def _shaped_weight( ) -> Float[np.ndarray, " n_nodes"]: """Return the weight of a shape other than a circle at the nodes. - Parameters - ---------- - radial : bool - Whether the plane is on a radial grid. - x, y : numpy.ndarray - The nodes in m, shape ``(n_nodes,)``. - - Returns - ------- - numpy.ndarray - The weight in ``[0, 1]``, shape ``(n_nodes,)``. - - Raises - ------ - CouplingError - As :meth:`apply` documents. + - ``x``, ``y``: the nodes in m, shape ``(n_nodes,)``. + - Returns: the weight in ``[0, 1]``, shape ``(n_nodes,)``. """ shape = self.shape assert shape is not None # a circle does not come here @@ -1807,25 +1481,10 @@ def _uniform_result( the ledger closes to rounding. Across a medium change the loss is the declared reflection and the rest is obliquity. - Parameters - ---------- - before, after : SpatialState - The input and output planes. - element : str - Description. - removed_bins : numpy.ndarray or None, optional - Bins set to exactly zero by the element; their input energy is reported as - removed. - reflected : numpy.ndarray or None, optional - Declared reflectance per bin across a medium change; the specified loss is - then ``sum(R * spectrum_in)``. - extras : _Extras or None, optional - The optional report fields. - - Returns - ------- - ElementResult - The output plane and its report. + - ``removed_bins``: bins the element sets to exactly zero; their input energy is + reported as removed. + - ``reflected``: declared reflectance per bin across a medium change; the specified + loss is then ``sum(R * spectrum_in)``. """ measured_in = _measure(before, _flux(before)) measured_out = _measure(after, _flux(after)) @@ -1858,17 +1517,7 @@ def _apply_jones( A scalar state is polarised along x. It stays scalar when the map creates no y component and becomes a two-component state otherwise. - Parameters - ---------- - state : SpatialState - The input plane. - jones : numpy.ndarray - Jones matrices acting on ``(E_x, E_y)``, shape ``(n_band, 2, 2)``. - - Returns - ------- - numpy.ndarray - The new internal spectrum, shape ``(n_modes, 1 or 2, n_omega)``. + - Returns: the new internal spectrum, shape ``(n_modes, 1 or 2, n_omega)``. """ band = _band(state.grid) stored = np.zeros((state.grid.n_omega, 2, 2), dtype=np.complex128) @@ -1885,20 +1534,7 @@ def _apply_jones( def _check_passive_jones( jones: Complex[np.ndarray, "n_band 2 2"], element: str ) -> None: - """Refuse a Jones map that amplifies some polarisation. - - Parameters - ---------- - jones : numpy.ndarray - Jones matrices, shape ``(n_band, 2, 2)``. - element : str - Description, for messages. - - Raises - ------ - CouplingError - If a matrix is not finite or its largest singular value exceeds one. - """ + """Refuse a Jones map that amplifies some polarisation.""" if not np.all(np.isfinite(jones)): msg = f"{element}: the Jones matrices must be finite" raise CouplingError(msg) @@ -1966,22 +1602,7 @@ def _unit_vector( ) -> Float[np.ndarray, " 3"]: """Return a normalised three-vector. - Parameters - ---------- - vector : sequence of float or numpy.ndarray - Three finite components, not all zero. - name : str - The quantity, for messages. - - Returns - ------- - numpy.ndarray - The unit vector, shape ``(3,)``. - - Raises - ------ - CouplingError - If the vector is not a finite nonzero three-vector. + - ``vector``: three finite components, not all zero. """ array = np.asarray(vector, dtype=np.float64) if array.shape != (3,) or not np.all(np.isfinite(array)) or not np.any(array): @@ -2035,15 +1656,8 @@ def __post_init__(self) -> None: def _geometry(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """Validate the directions and return them normalised. - Returns - ------- - tuple of numpy.ndarray - Incoming direction, outgoing direction and s axis, each shape ``(3,)``. - - Raises - ------ - CouplingError - If the directions do not define a reflection with this s axis. + - Returns: incoming direction, outgoing direction and s axis, each shape + ``(3,)``. """ incident = _unit_vector(self.incident, "incident") outgoing = _unit_vector(self.outgoing, "outgoing") @@ -2124,25 +1738,7 @@ def _mirror_image( ) -> Complex[np.ndarray, "n_modes n_components n_omega"]: """Reflect a Cartesian field's transverse pattern through a local axis. - Parameters - ---------- - space : solprop.grid.FreeGrid - Cartesian representation. - spectrum : numpy.ndarray - Stored spectrum, shape ``(n_modes, n_components, n_omega)``. - image : numpy.ndarray - The 2x2 position map, ``diag(-1, 1)`` or ``diag(1, -1)`` to rounding. - - Returns - ------- - numpy.ndarray - The reflected spectrum. - - Raises - ------ - CouplingError - If the map is not a reflection through x or y, or the reflected axis has - an odd sample count and so is not symmetric about zero. + - ``image``: the 2x2 position map, ``diag(-1, 1)`` or ``diag(1, -1)`` to rounding. """ flips = np.round(np.diag(image)) if np.max(np.abs(image - np.diag(flips))) > _FRAME_TOLERANCE: @@ -2251,16 +1847,8 @@ def _known_calibration( # noqa: PLR0911 - one return per known element type ) -> tuple[SpectralCalibration, ...] | None: """Return the affine-phase record of a known optics element. - Parameters - ---------- - transfer : solprop.optics.TransferFunction - The element. - - Returns - ------- - tuple of SpectralCalibration or None - One record per factor (a chain gives one per member), or None for an - unknown type. + - Returns: one record per factor (a chain gives one per member), or None for an + unknown type. """ if isinstance(transfer, Chain): records: list[SpectralCalibration] = [] @@ -2314,15 +1902,7 @@ def _known_calibration( # noqa: PLR0911 - one return per known element type def _phase_only(transfer: TransferFunction) -> bool | None: """Return whether a known optics model omits all loss. - Parameters - ---------- - transfer : solprop.optics.TransferFunction - The element. - - Returns - ------- - bool or None - True for a phase-only model, False for one with loss, None if unknown. + - Returns: True for a phase-only model, False for one with loss, None if unknown. """ if isinstance(transfer, Chain): members = [_phase_only(member) for member in transfer.elements] @@ -2341,16 +1921,8 @@ def _phase_only(transfer: TransferFunction) -> bool | None: def _reconstruction(transfer: TransferFunction) -> bool: """Return whether a known optics element is a reconstruction operation. - Parameters - ---------- - transfer : solprop.optics.TransferFunction - The element. - - Returns - ------- - bool - True for a negative material length or an inverse mirror phase, in the - element or any member of a chain. + True for a negative material length or an inverse mirror phase, in the element or + any member of a chain. """ if isinstance(transfer, Chain): return any(_reconstruction(member) for member in transfer.elements) @@ -2397,18 +1969,7 @@ class SpectralTransfer: reconstruction: bool = False def _calibrations(self) -> tuple[SpectralCalibration, ...]: - """Return the affine-phase records, derived or given. - - Returns - ------- - tuple of SpectralCalibration - One record per factor. - - Raises - ------ - CouplingError - If an unknown callable carries no calibration. - """ + """Return the affine-phase records, derived or given.""" if isinstance(self.calibration, SpectralCalibration): return (self.calibration,) if self.calibration is not None: @@ -2600,19 +2161,7 @@ class FreeSpace: beyond_window: Literal["refuse", "remove"] = "refuse" def _validate(self, state: SpatialState) -> None: - """Check the element against the plane. - - Parameters - ---------- - state : SpatialState - The input plane. - - Raises - ------ - CouplingError - If the distance, method, band limit or output plane is invalid, or - the gap's medium is not the plane's. - """ + """Check the element against the plane.""" if not (math.isfinite(self.distance) and math.isfinite(self.frame_phase)): msg = ( f"distance and frame_phase must be finite, got {self.distance!r} and " @@ -2648,15 +2197,7 @@ def _validate(self, state: SpatialState) -> None: def _delay(self, state: SpatialState) -> float: """Return the delay removed from the whole field. - Parameters - ---------- - state : SpatialState - The input plane. - - Returns - ------- - float - ``frame_delay``, or ``beta1(omega0) L`` in s. + - Returns: ``frame_delay``, or ``beta1(omega0) L`` in s. """ if self.frame_delay is not None: return self.frame_delay @@ -2738,18 +2279,7 @@ def apply(self, state: SpatialState) -> ElementResult: return ElementResult(result, report) def _check_edge(self, edge: float) -> None: - """Refuse a same-grid result that fills the window's outer band. - - Parameters - ---------- - edge : float - Reference-measure share of the propagated field in the outer band. - - Raises - ------ - CouplingError - If `edge` exceeds ``edge_tolerance``. - """ + """Refuse a same-grid result that fills the window's outer band.""" if edge <= self.edge_tolerance: return # The wrap test follows rays per column; a field that has spread into the @@ -2774,25 +2304,7 @@ def _limited( ) -> tuple[SpatialState, float]: """Refuse evanescent content, or remove the declared angular band. - Parameters - ---------- - state : SpatialState - The input plane. - flux : solprop.spatial.SpatialFlux - Its measure. - measured : _Measured - Its energy. - - Returns - ------- - tuple - The retained plane and the physical energy removed, J. - - Raises - ------ - CouplingError - If evanescent content exceeds its tolerance, or any remains for a - backward propagation. + - Returns: the retained plane and the physical energy removed, J. """ content = _angular_content(state, flux, 1.0) if self.band_limit is None: @@ -2821,19 +2333,9 @@ def _wavenumber( ) -> Complex[np.ndarray, "n_chunk n_omega"]: """Return the longitudinal wave number of a chunk of columns. - Parameters - ---------- - state : SpatialState - The plane. - columns : slice - Transverse columns. - - Returns - ------- - numpy.ndarray - :math:`k_z` in m^-1 with nonnegative imaginary part for a lossless - medium (decay of a nonpropagating column), shape ``(n_chunk, n_omega)``; - zero off band. + - Returns: :math:`k_z` in m^-1 with nonnegative imaginary part for a lossless + medium (decay of a nonpropagating column), shape ``(n_chunk, n_omega)``; zero + off band. """ grid = state.grid band = _band(grid) @@ -2849,26 +2351,9 @@ def _propagate( ) -> tuple[SpatialState, float, float, dict[str, float]]: """Propagate the stored spectrum exactly on its own grid. - Parameters - ---------- - state : SpatialState - The (band-limited) input plane. - flux : solprop.spatial.SpatialFlux - Its measure. - delay : float - Delay removed from the whole field, s. - - Returns - ------- - tuple - The propagated plane, the energy absorbed by the medium in J, the - energy removed beyond the window in J, and the wrap diagnostics. - - Raises - ------ - CouplingError - If content whose rays leave the window exceeds ``sampling_tolerance`` - and its removal is not declared. + - ``delay``: delay removed from the whole field, s. + - Returns: the propagated plane, the energy absorbed by the medium in J, the + energy removed beyond the window in J, and the wrap diagnostics. """ grid = state.grid band = _band(grid) @@ -2936,29 +2421,8 @@ def _fresnel( ) -> ElementResult: """Evaluate the Fresnel integral of the source samples at the output nodes. - Parameters - ---------- - state : SpatialState - The input plane, for the medium and grid. - kept : SpatialState - The band-limited input plane. - measured_in : _Measured - Energy of the input plane. - removed : float - Energy removed by the band limit, J. - delay : float - Delay removed from the whole field, s. - - Returns - ------- - ElementResult - The output plane and the report. - - Raises - ------ - CouplingError - If the distance is not positive or the kernel is undersampled on the - source grid. + - ``removed``: energy removed by the band limit, J. + - ``delay``: delay removed from the whole field, s. """ output = self.output assert output is not None @@ -3052,22 +2516,9 @@ def add( beyond the window's half-width the periodic (Cartesian) or reflecting (radial) representation folds them back. - Parameters - ---------- - state : SpatialState - The plane. - columns : slice - Transverse columns. - k_z : numpy.ndarray - Their longitudinal wave numbers, shape ``(n_chunk, n_omega)``. - distance : float - Propagation distance in m. - - Returns - ------- - numpy.ndarray - True where a propagating column's rays move beyond the window's - half-width, shape ``(n_chunk, n_omega)``. + - ``distance``: propagation distance in m. + - Returns: True where a propagating column's rays move beyond the window's + half-width, shape ``(n_chunk, n_omega)``. """ grid = state.grid space = state.space @@ -3104,10 +2555,7 @@ def add( def share(self) -> float: """Return the wrapped share. - Returns - ------- - float - Share in [0, 1]; zero for a zero field. + - Returns: share in [0, 1]; zero for a zero field. """ return 0.0 if self.total == 0 else self.wrapped / self.total @@ -3117,19 +2565,9 @@ def _share_beyond( ) -> float: """Return the reference-measure share in columns beyond a transverse wave number. - Parameters - ---------- - state : SpatialState - The plane. - index : numpy.ndarray - Real index per bin, zero off band. - limit : float - Transverse wave number in rad/m; a nonpositive limit counts every column. - - Returns - ------- - float - Share in [0, 1]; zero for a zero field. + - ``limit``: transverse wave number in rad/m; a nonpositive limit counts every + column. + - Returns: share in [0, 1]; zero for a zero field. """ grid = state.grid weights = fourier.energy_weights(grid.n_omega, grid.dt, kind=grid.kind) @@ -3147,15 +2585,7 @@ def _share_beyond( def _spacing(space: RadialGrid | FreeGrid) -> float: """Return the largest spacing between neighbouring nodes. - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - Transverse representation. - - Returns - ------- - float - Spacing in m. + - Returns: spacing in m. """ if isinstance(space, RadialGrid): return float(np.max(np.diff(np.concatenate([[0.0], space.r])))) @@ -3172,18 +2602,6 @@ def _angular_output( first-sample origin, the Nyquist column split symmetrically. Nodes outside the source window are zero. The kernel is built in blocks of output nodes; no transverse-by-transverse-by-frequency array is formed. - - Parameters - ---------- - state : SpatialState - The source plane. - output : solprop.grid.RadialGrid or solprop.grid.FreeGrid - The output representation, of the same kind. - - Returns - ------- - numpy.ndarray - The output's stored spectrum, shape ``(n_out, n_pol, n_omega)``. """ space = state.space spectrum = state.spectrum @@ -3227,20 +2645,11 @@ def _interpolant( ) -> Complex[np.ndarray, "n_source n_target"]: """Return the trigonometric interpolation kernel of one periodic axis. - Parameters - ---------- - axis : numpy.ndarray - Source sample positions in m, uniform, shape ``(n_source,)``. - frequencies : numpy.ndarray - Their DFT angular frequencies in rad/m, FFT order. - target : numpy.ndarray - Output positions in m, shape ``(n_target,)``. - - Returns - ------- - numpy.ndarray - ``exp(i k (x' - x_0))``, zero for outputs outside the source period; the - Nyquist row of an even axis is the cosine, so a real field stays real. + - ``axis``: source sample positions in m, uniform, shape ``(n_source,)``. + - ``frequencies``: their DFT angular frequencies in rad/m, FFT order. + - ``target``: output positions in m, shape ``(n_target,)``. + - Returns: ``exp(i k (x' - x_0))``, zero for outputs outside the source period; the + Nyquist row of an even axis is the cosine, so a real field stays real. """ origin = float(axis[0]) spacing = float(axis[1] - axis[0]) @@ -3269,21 +2678,8 @@ def _fresnel_bin( \Delta A`, separable in x and y. The real part of ``k`` sets the phases and the imaginary part attenuates through ``exp(ikL)`` only. - Parameters - ---------- - source, output : solprop.grid.RadialGrid or solprop.grid.FreeGrid - Source and output representations, of one kind. - samples : numpy.ndarray - Source field samples at one bin, shape ``(n_source, n_pol)``. - wavenumber : complex - :math:`k = \omega n / c` in m^-1. - distance : float - Positive distance in m. - - Returns - ------- - numpy.ndarray - Output samples at that bin, shape ``(n_target, n_pol)``. + - ``wavenumber``: :math:`k = \omega n / c` in m^-1. + - ``distance``: positive distance in m. """ k = wavenumber.real prefactor = (wavenumber / (1j * distance)) * np.exp(1j * wavenumber * distance) diff --git a/src/solprop/coupling/models.py b/src/solprop/coupling/models.py index 8551ad2ee..e1ea90b79 100644 --- a/src/solprop/coupling/models.py +++ b/src/solprop/coupling/models.py @@ -238,13 +238,10 @@ def __post_init__(self) -> None: raise CouplingError(msg) @property - def cone(self) -> float: - """Return the admitted cone half-angle of the budget. + def cone(self) -> float: # numpydoc ignore=RT01 + """Admitted cone half-angle of the budget, rad. - Returns - ------- - float - :func:`cone_angle` of `model_error`, rad. + :func:`cone_angle` of `model_error`. Raises ------ @@ -312,19 +309,8 @@ def _hankel_core( within 1e-4 of :math:`q = u`, where the quotient cancels, the integral is evaluated by quadrature instead. - Parameters - ---------- - u : float - The profile's dimensionless transverse wavenumber. - q : numpy.ndarray - :math:`\kappa a`, dimensionless, shape ``(n_k,)``. - radius : float - The core radius a, m. - - Returns - ------- - numpy.ndarray - The integral in m^2, shape ``(n_k,)``. + - ``radius``: the core radius a, m. + - Returns: the integral in m^2, shape ``(n_k,)``. """ q = np.asarray(q, dtype=np.float64) near = np.abs(q - u) < 1e-4 * max(u, 1.0) @@ -354,19 +340,8 @@ def _hankel_cladding( /(\alpha^2 + \beta^2)` of :math:`xK_0(\alpha x)J_0(\beta x)`, which vanishes at infinity: :math:`a^2[wK_1(w)J_0(q) - qK_0(w)J_1(q)]/(w^2 + q^2)`. - Parameters - ---------- - w : float - The profile's dimensionless cladding decay. - q : numpy.ndarray - :math:`\kappa a`, dimensionless, shape ``(n_k,)``. - radius : float - The core radius a, m. - - Returns - ------- - numpy.ndarray - The integral in m^2, shape ``(n_k,)``. + - ``radius``: the core radius a, m. + - Returns: the integral in m^2, shape ``(n_k,)``. """ q = np.asarray(q, dtype=np.float64) return radius * radius * (w * k1(w) * j0(q) - q * k0(w) * j1(q)) / (w * w + q * q) @@ -380,22 +355,8 @@ def _lp_parameter(v: float, m: int) -> tuple[float, float]: :math:`UJ_1(U)K_0(W) - WK_1(W)J_0(U) = 0` and bracketed between consecutive zeros of :math:`J_1`, where the :math:`m`-th root lies. - Parameters - ---------- - v : float - The normalised frequency V. - m : int - Radial order, at least 1. - - Returns - ------- - tuple of float - U and W. - - Raises - ------ - CouplingError - If the mode is below its cutoff at this V. + - ``v``: the normalised frequency V. + - ``m``: radial order, at least 1. """ lower = 0.0 if m == 1 else float(jn_zeros(1, m - 1)[-1]) if v <= lower * (1.0 + 1e-9): @@ -404,18 +365,7 @@ def _lp_parameter(v: float, m: int) -> tuple[float, float]: upper = min(float(jn_zeros(0, m)[-1]), v) def equation(u: float) -> float: - """Return the pole-free characteristic function at U. - - Parameters - ---------- - u : float - The core parameter U. - - Returns - ------- - float - The function's value, zero at an eigenvalue. - """ + """Return the pole-free characteristic function at U.""" w = math.sqrt(max(v * v - u * u, 0.0)) # Exponentially scaled K keeps W large finite; the common factor exp(-W) # is positive and does not move the root. @@ -429,16 +379,8 @@ def equation(u: float) -> float: def _waveguide_of(mode: Mode) -> Mode: """Return the waveguide mode a mode-averaged fibre was built from, or the mode. - Parameters - ---------- - mode : solprop.modes.protocol.Mode - A run's mode. - - Returns - ------- - solprop.modes.protocol.Mode - The capillary or step-index mode its dispersion forwards to, when it has - one; otherwise `mode` itself. + - Returns: the capillary or step-index mode its dispersion forwards to, when it has + one; otherwise `mode` itself. """ inner = getattr(getattr(mode, "dispersion", None), "mode", None) return inner if isinstance(inner, MarcatiliMode | StepIndexMode) else mode @@ -589,25 +531,16 @@ def leaky( return values @property - def n_modes(self) -> int: - """Return the number of channels. - - Returns - ------- - int - ``len(labels)``. - """ + def n_modes(self) -> int: # numpydoc ignore=RT01 + """Number of channels.""" return len(self.labels) def _square_integrals(self) -> Float[np.ndarray, " n_modes"]: r"""Return :math:`\int|\hat v_m|^2dA` of each unnormalised profile, m^2. - Returns - ------- - numpy.ndarray - Shape ``(n_modes,)``: :math:`\pi a^2J_1(u)^2` for a capillary profile, - :math:`\pi a^2[J_0^2 + J_1^2](U) + \pi a^2(J_0(U)/K_0(W))^2 - [K_1^2 - K_0^2](W)` for an LP profile. + - Returns: shape ``(n_modes,)``: :math:`\pi a^2J_1(u)^2` for a capillary + profile, :math:`\pi a^2[J_0^2 + J_1^2](U) + \pi a^2(J_0(U)/K_0(W))^2 [K_1^2 - + K_0^2](W)` for an LP profile. """ area = math.pi * self.radius**2 core = area * (j0(self.u) ** 2 + j1(self.u) ** 2) @@ -804,28 +737,7 @@ def _capillary( ) -> "ScalarModeBasis": """Return the J0 profiles of capillary HE1m modes. - Parameters - ---------- - modes : sequence of MarcatiliMode - The modes. - labels : tuple of str - Their labels. - index : numpy.ndarray - Their real effective indices, shape ``(n_modes, n_omega)``. - grid : solprop.grid.TimeGrid - The temporal grid, for the core gas's index. - z : float - The plane's position, m. - - Returns - ------- - ScalarModeBasis - The profiles. - - Raises - ------ - CouplingError - For a family other than HE1m, or modes of different radii. + - ``z``: the plane's position, m. """ radii = {float(mode.dimlimits(z)[2][0]) for mode in modes} if len(radii) != 1: @@ -871,28 +783,7 @@ def _step_index( ) -> "ScalarModeBasis": """Return the weak-guidance LP0m profiles of step-index HE1m modes. - Parameters - ---------- - modes : sequence of StepIndexMode - The modes, of one fibre. - labels : tuple of str - Their labels. - index : numpy.ndarray - Their real effective indices, shape ``(n_modes, n_omega)``. - grid : solprop.grid.TimeGrid - The temporal grid. - z : float - The plane's position, m. - - Returns - ------- - ScalarModeBasis - The profiles, frozen at the modes' profile wavelength. - - Raises - ------ - CouplingError - For a family other than HE1m, or modes of different fibres. + - ``z``: the plane's position, m. """ first = modes[0] for mode, label in zip(modes, labels, strict=True): diff --git a/src/solprop/coupling/plane.py b/src/solprop/coupling/plane.py index e38f6c8eb..ee51db233 100644 --- a/src/solprop/coupling/plane.py +++ b/src/solprop/coupling/plane.py @@ -114,26 +114,7 @@ def _frozen( ) -> np.ndarray: """Return an owned, read-only, finite copy of an array. - Parameters - ---------- - values : object - Anything :func:`numpy.array` accepts. - dtype : type - The dtype of the copy. - name : str - The attribute name, for the refusal. - shape : tuple of int or None, optional - The required shape, when one is fixed. - - Returns - ------- - numpy.ndarray - The copy, with ``flags.writeable`` False. - - Raises - ------ - CouplingError - If the array is not finite or has the wrong shape. + - ``shape``: the required shape, when one is fixed. """ array = np.array(values, dtype=dtype, copy=True) if shape is not None and array.shape != shape: @@ -149,16 +130,8 @@ def _frozen( def _canonical(record: object) -> str: """Return the canonical JSON text an identity is a digest of. - Parameters - ---------- - record : object - JSON-ready data. - - Returns - ------- - str - Sorted, compact JSON. A material's open interval bound is infinite, and - JSON's ``Infinity`` spells it deterministically. + A material's open interval bound is infinite, and JSON's ``Infinity`` spells it + deterministically. """ return json.dumps(record, sort_keys=True, separators=(",", ":")) @@ -204,18 +177,7 @@ def basis_identity(route: PlaneKind, record: Mapping[str, object]) -> str: def _quantised(values: np.ndarray) -> list[float]: - """Return values rounded to :data:`_QUANTUM_DIGITS` significant digits. - - Parameters - ---------- - values : numpy.ndarray - Finite real values. - - Returns - ------- - list of float - The rounded values. - """ + """Return values rounded to :data:`_QUANTUM_DIGITS` significant digits.""" return [float(f"{float(one):.{_QUANTUM_DIGITS}g}") for one in np.ravel(values)] @@ -407,14 +369,8 @@ def __post_init__(self) -> None: object.__setattr__(self, "x_axis", tuple(float(one) for one in self.x_axis)) @property - def y_axis(self) -> tuple[float, float, float]: - """Return the local y axis, ``normal`` cross ``x_axis``. - - Returns - ------- - tuple of float - The unit vector completing the right-handed frame. - """ + def y_axis(self) -> tuple[float, float, float]: # numpydoc ignore=RT01 + """Local y axis, ``normal`` cross ``x_axis``.""" value = np.cross(np.asarray(self.normal), np.asarray(self.x_axis)) return (float(value[0]), float(value[1]), float(value[2])) @@ -581,23 +537,8 @@ def from_record(cls, record: Mapping[str, object]) -> "PlaneReference": def _chebyshev_at(table: Mapping[str, object], z: float) -> float: """Evaluate a persisted piecewise Chebyshev series at one position. - Parameters - ---------- - table : collections.abc.Mapping - ``breakpoints`` (increasing interval edges, m) and ``coefficients`` (one - list per interval, lowest degree first). - z : float - The position, inside the span to rounding. - - Returns - ------- - float - The value. - - Raises - ------ - CouplingError - If ``z`` lies outside the span. + - ``table``: ``breakpoints`` (increasing interval edges, m) and ``coefficients`` + (one list per interval, lowest degree first). """ edges = np.asarray(table["breakpoints"], dtype=float) slack = 1e-9 * max(1.0, abs(float(edges[-1]))) @@ -1057,13 +998,10 @@ class AnalyticRecord: resolution_change: float @property - def error(self) -> float: - """Return the larger of the two checks, the energy's numerical error bound. + def error(self) -> float: # numpydoc ignore=RT01 + """Larger of the two checks, the energy's numerical error bound. - Returns - ------- - float - ``max(domain_change, resolution_change)``, dimensionless. + ``max(domain_change, resolution_change)``, dimensionless. """ return max(self.domain_change, self.resolution_change) @@ -1178,13 +1116,10 @@ def __repr__(self) -> str: ) @property - def omega(self) -> Float[np.ndarray, " n_omega"]: - """Return the absolute angular frequency of each stored bin. + def omega(self) -> Float[np.ndarray, " n_omega"]: # numpydoc ignore=RT01 + """Absolute angular frequency of each stored bin, rad/s. - Returns - ------- - numpy.ndarray - Angular frequencies in rad/s, shape ``(n_omega,)``, in storage order. + Shape ``(n_omega,)``, in storage order. """ return self.grid.omega @@ -1651,19 +1586,9 @@ def _transverse( ) -> Complex[np.ndarray, "n_columns n_pol n_omega"]: """Apply a grid's transverse transform along the column axis. - Parameters - ---------- - values : numpy.ndarray - Internal-convention spectra, shape ``(n_columns, n_pol, n_omega)``. - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - The grid whose QDHT or two-dimensional FFT is applied. - inverse : bool - True for columns to nodes, False for nodes to columns. - - Returns - ------- - numpy.ndarray - The transformed spectra, same shape. + - ``values``: Internal-convention spectra, shape ``(n_columns, n_pol, n_omega)``. + - ``space``: the grid whose QDHT or two-dimensional FFT is applied. + - ``inverse``: True for columns to nodes, False for nodes to columns. """ if isinstance(space, RadialGrid): moved = np.moveaxis(values, 0, -1) @@ -1679,18 +1604,7 @@ def _transverse( def _kappa_squared(space: RadialGrid | FreeGrid) -> Float[np.ndarray, " n_columns"]: - """Return each transverse column's squared wavenumber, m^-2. - - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - The grid. - - Returns - ------- - numpy.ndarray - Shape ``(n_columns,)``. - """ + """Return each transverse column's squared wavenumber, m^-2.""" if isinstance(space, RadialGrid): return np.asarray(space.k_r, dtype=np.float64) ** 2 return np.asarray(space.kperp_sq, dtype=np.float64) @@ -1699,15 +1613,7 @@ def _kappa_squared(space: RadialGrid | FreeGrid) -> Float[np.ndarray, " n_column def _node_positions(space: RadialGrid | FreeGrid) -> Float[np.ndarray, "n_nodes 2"]: """Return a grid's nodes as ``(x, y)`` pairs; radial nodes lie on the x axis. - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - The grid. - - Returns - ------- - numpy.ndarray - Positions in m, shape ``(n_nodes, 2)``, in the grid's node order. + - Returns: positions in m, shape ``(n_nodes, 2)``, in the grid's node order. """ if isinstance(space, RadialGrid): return np.column_stack((space.r, np.zeros_like(space.r))) @@ -1717,36 +1623,14 @@ def _node_positions(space: RadialGrid | FreeGrid) -> Float[np.ndarray, "n_nodes def _extent(space: RadialGrid | FreeGrid) -> float: - """Return the radius of the largest centred disc inside a grid's window, m. - - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - The grid. - - Returns - ------- - float - The radial window, or the smaller Cartesian half-width. - """ + """Return the radius of the largest centred disc inside a grid's window, m.""" if isinstance(space, RadialGrid): return float(space.hankel.radius) return float(min(np.max(np.abs(space.x)), np.max(np.abs(space.y)))) def _enlarged(space: RadialGrid | FreeGrid) -> RadialGrid | FreeGrid: - """Return the grid with its window doubled at the same node spacing. - - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - The grid. - - Returns - ------- - solprop.grid.RadialGrid or solprop.grid.FreeGrid - The doubled grid. - """ + """Return the grid with its window doubled at the same node spacing.""" if isinstance(space, RadialGrid): return RadialGrid(2.0 * space.radius, 2 * space.n_r, space.collar_fraction) return FreeGrid( @@ -1759,18 +1643,7 @@ def _enlarged(space: RadialGrid | FreeGrid) -> RadialGrid | FreeGrid: def _refined(space: RadialGrid | FreeGrid) -> RadialGrid | FreeGrid: - """Return the grid with its node spacing halved in the same window. - - Parameters - ---------- - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - The grid. - - Returns - ------- - solprop.grid.RadialGrid or solprop.grid.FreeGrid - The refined grid. - """ + """Return the grid with its node spacing halved in the same window.""" if isinstance(space, RadialGrid): return RadialGrid(space.radius, 2 * space.n_r, space.collar_fraction) return FreeGrid( @@ -1790,27 +1663,9 @@ def _analytic_columns( ) -> Complex[np.ndarray, "n_columns n_pol n_omega"]: """Sample an analytic field at a grid's nodes and return its plane columns. - Parameters - ---------- - field : callable - The analytic field (:meth:`FieldPlane.from_analytic`). - grid : solprop.grid.TimeGrid - Temporal grid. - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - Transverse grid. - support : float or None - Declared support radius, m. - - Returns - ------- - numpy.ndarray - Optics-convention raw column spectra referenced to the window centre, - shape ``(n_columns, n_pol, n_omega)``. - - Raises - ------ - CouplingError - If the field returns the wrong shape or nonfinite values. + - ``support``: declared support radius, m. + - Returns: Optics-convention raw column spectra referenced to the window centre, + shape ``(n_columns, n_pol, n_omega)``. """ xy = _node_positions(space) radius = np.hypot(xy[:, 0], xy[:, 1]) diff --git a/src/solprop/coupling/port.py b/src/solprop/coupling/port.py index 2b70bf5b2..22d3b42bc 100644 --- a/src/solprop/coupling/port.py +++ b/src/solprop/coupling/port.py @@ -204,25 +204,19 @@ def __post_init__(self) -> None: object.__setattr__(self, name, array) @property - def lattice(self) -> str: - """Return the identity of the receiving temporal lattice. + def lattice(self) -> str: # numpydoc ignore=RT01 + """Identity of the receiving temporal lattice. - Returns - ------- - str - :func:`~solprop.coupling.plane.lattice_identity` of `grid`. + :func:`~solprop.coupling.plane.lattice_identity` of `grid`. """ return lattice_identity(self.grid) @property - def identity(self) -> str: - """Return the port's identity: route, lattice, basis and entrance. + def identity(self) -> str: # numpydoc ignore=RT01 + """The port's identity: route, lattice, basis and entrance. - Returns - ------- - str - A SHA-256 hex digest; two ports with the same identity accept the same - launch. + A SHA-256 hex digest; two ports with the same identity accept the same + launch. """ record = { "route": self.route, diff --git a/src/solprop/coupling/remap.py b/src/solprop/coupling/remap.py index eed58e052..804af6d33 100644 --- a/src/solprop/coupling/remap.py +++ b/src/solprop/coupling/remap.py @@ -127,19 +127,7 @@ class RegridReport: def _q(grid: TimeGrid) -> Float[np.ndarray, " n_omega"]: - """Return the analytic-signal factor of each stored bin. - - Parameters - ---------- - grid : solprop.grid.TimeGrid - The grid. - - Returns - ------- - numpy.ndarray - Two on interior bins of a field grid, one at its end bins, one on an - envelope grid; shape ``(n_omega,)``. - """ + """Return the analytic-signal factor of each stored bin.""" factor = np.ones(grid.n_omega) if grid.kind == "field": factor[1:-1] = 2.0 @@ -154,18 +142,6 @@ def _analytic( ``a_j = sum_k q_k s_k exp(-i omega_k t_j) / N`` in the optics convention. With ``omega_k = omega_c + kappa_k d_omega`` (``omega_c`` the carrier of an envelope grid, zero on a field grid) and ``t_j = t_0 + j dt`` it is one FFT. - - Parameters - ---------- - samples : numpy.ndarray - Stored spectra, shape ``(n_columns, n_omega)``. - grid : solprop.grid.TimeGrid - Their grid. - - Returns - ------- - numpy.ndarray - The analytic signal, shape ``(n_columns, n_t)``. """ centre = grid.omega0 if grid.kind == "envelope" else 0.0 kappa = np.rint((grid.omega - centre) / grid.d_omega).astype(np.int64) @@ -179,16 +155,8 @@ def _analytic( def _column_weights(plane: FieldPlane) -> Float[np.ndarray, " n_modes"]: """Return the transverse weight each column's record norm carries. - Parameters - ---------- - plane : FieldPlane - The source. - - Returns - ------- - numpy.ndarray - One per column: the metric's Parseval weights times its scale for a spatial - plane, ones otherwise. + - Returns: one per column: the metric's Parseval weights times its scale for a + spatial plane, ones otherwise. """ metric = plane.metric if metric.weights is not None: @@ -197,23 +165,7 @@ def _column_weights(plane: FieldPlane) -> Float[np.ndarray, " n_modes"]: def _target_metric(plane: FieldPlane, grid: TimeGrid, same: bool) -> PlaneMetric: - """Return the result's metric on the target lattice. - - Parameters - ---------- - plane : FieldPlane - The source. - grid : solprop.grid.TimeGrid - The target grid. - same : bool - Whether the lattices are identical. - - Returns - ------- - PlaneMetric - The identity kept, an admittance resolved again from the plane's medium, the - source's metric on an identical lattice, or ``unknown``. - """ + """Return the result's metric on the target lattice.""" metric = plane.metric if metric.kind in ("identity", "unknown") or same: return metric @@ -234,17 +186,8 @@ def _target_metric(plane: FieldPlane, grid: TimeGrid, same: bool) -> PlaneMetric def _within_band(grid: TimeGrid, omega: np.ndarray) -> np.ndarray: """Return which absolute frequencies lie inside a grid's active band. - Parameters - ---------- - grid : solprop.grid.TimeGrid - The receiving grid. - omega : numpy.ndarray - Absolute angular frequencies, rad/s. - - Returns - ------- - numpy.ndarray - Boolean mask of the same shape. + - ``omega``: absolute angular frequencies, rad/s. + - Returns: boolean mask of the same shape. """ active = grid.omega[grid.active_mask] return (omega >= float(np.min(active))) & (omega <= float(np.max(active))) @@ -439,34 +382,10 @@ def _record_window( ) -> tuple[np.ndarray, float, float]: """Return which source samples the target window keeps, and check the record. - Parameters - ---------- - plane : FieldPlane - The source. - analytic : numpy.ndarray - Its analytic signal, shape ``(n_columns, n_t)``. - weights : numpy.ndarray - Each column's transverse weight. - grid : solprop.grid.TimeGrid - The target grid. - shift : float - Record-time shift, s. - truncate : bool - Whether the record may be cut and declared zero outside its window. - tolerance : float - Share below which a window counts as unoccupied. - - Returns - ------- - tuple - The kept-sample mask, the share outside the target window and the share in - the source's temporal collar. - - Raises - ------ - CouplingError - If light falls outside the target window, or a nonlocalised or - unknown-exterior record would be extended, without `truncate`. + - ``shift``: Record-time shift, s. + - ``truncate``: whether the record may be cut and declared zero outside the window. + - Returns: the kept-sample mask, the share outside the target window and the share + in the source's temporal collar. """ source = plane.grid power = np.sum(weights[:, None] * np.abs(analytic) ** 2, axis=0) @@ -521,24 +440,8 @@ def _direct_sum( record, not light, so a target bin outside that span is zero: the source represents nothing there. - Parameters - ---------- - analytic : numpy.ndarray - Source analytic signal at the kept samples, shape ``(n_columns, n_inside)``. - times : numpy.ndarray - Their shifted times, s. - grid : solprop.grid.TimeGrid - The target grid. - source : solprop.grid.TimeGrid - The source grid. - phase_shift : float - Phase applied, rad. - - Returns - ------- - numpy.ndarray - Target stored spectra, zero outside the target's active band and outside - the source lattice's span, shape ``(n_columns, n_omega)``. + - ``times``: their shifted times, s. + - ``phase_shift``: phase applied, rad. """ result = np.zeros((analytic.shape[0], grid.n_omega), dtype=np.complex128) half = 0.5 * source.d_omega diff --git a/src/solprop/coupling/transport.py b/src/solprop/coupling/transport.py index ef5f61432..a6251cd1e 100644 --- a/src/solprop/coupling/transport.py +++ b/src/solprop/coupling/transport.py @@ -109,39 +109,18 @@ class TransportReport: source_certificate: AdiabaticAdmission | None = None @property - def ledger_residual(self) -> float: - """Return the part of the energy change the ledger does not account for. - - Returns - ------- - float - ``energy_out - (energy_in - specified_loss - removed + - approximation_error)`` in J; rounding only. + def ledger_residual(self) -> float: # numpydoc ignore=RT01 + """Part of the energy change the ledger does not account for. + + ``energy_out - (energy_in - specified_loss - removed + + approximation_error)`` in J; rounding only. """ explained = self.energy_in - self.specified_loss - self.removed return self.energy_out - (explained + self.approximation_error) def _as_element(item: object, position: int) -> Element: - """Return an element, adapting a frequency-only transfer function. - - Parameters - ---------- - item : object - An element, or an :class:`solprop.optics.TransferFunction` such as a chain. - position : int - Its position, for messages. - - Returns - ------- - Element - The element itself, or its :class:`SpectralTransfer` adapter. - - Raises - ------ - CouplingError - If the item is neither. - """ + """Return an element, adapting a frequency-only transfer function.""" if isinstance(item, Element): return item if isinstance(item, TransferFunction): @@ -169,21 +148,9 @@ def _frame_term( :func:`solprop.coupling.plane.reference_at`. Without a frame only the delay :math:`\beta_1(\omega_0)L` at the grid's reference frequency is removed. - Parameters - ---------- - frame : solprop.linear.Frame or None - The plane's frame, or None. - medium : solprop.spatial.SpatialMedium - The gap's homogeneous medium. - grid : solprop.grid.TimeGrid - The temporal grid. - distance : float - The gap's length, m. - - Returns - ------- - tuple - The phase in rad, the delay in s, and a description of the source. + - ``frame``: the plane's frame, or None. + - ``distance``: the gap's length, m. + - Returns: the phase in rad, the delay in s, and a description of the source. """ reference = grid.omega0 if frame is None else frame.omega0 at = np.array([reference]) @@ -204,20 +171,7 @@ def _frame_term( def _state(plane: FieldPlane) -> SpatialState: """Return the internal spatial state of a radial or Cartesian plane. - Parameters - ---------- - plane : FieldPlane - The plane. - - Returns - ------- - SpatialState - Its internal-convention spectrum on its grids, medium and local frame. - - Raises - ------ - CouplingError - If the plane is modal or planar, or carries no medium. + - Returns: its internal-convention spectrum on its grids, medium and local frame. """ if plane.kind in ("modal", "general_modal"): msg = ( @@ -251,23 +205,9 @@ def _sequence( ) -> tuple[SpatialState, TransportReport]: """Apply an ordered sequence to an internal state and keep the ledger. - Parameters - ---------- - state : SpatialState - The field at the first plane. - elements : sequence - Elements or transfer functions, applied in order. - frame : solprop.linear.Frame or None, optional - The frame of each free-space gap without an explicit ``frame_delay``. - track : bool, optional - Whether the caller tracks a reference, so that a gap without an explicit - ``frame_delay`` takes its frame term from `frame`. Default False: the - element's own default applies. - - Returns - ------- - tuple - The last state and the sequential report. + - ``track``: the caller tracks a reference, so a gap without an explicit + ``frame_delay`` takes its frame term from `frame`; otherwise the element's own + default applies. """ current = state reports: list[ElementReport] = [] @@ -310,18 +250,6 @@ def _reference( Each free-space gap adds the phase and delay it removed. A spectral element that drops its constant phase or its group delay, or does not say, leaves that term uncalibrated: the physical element adds what its model omits. - - Parameters - ---------- - reference : PlaneReference - The input reference. - reports : sequence of ElementReport - The element reports, in order. - - Returns - ------- - PlaneReference - The output reference. """ phase, delay = reference.phase, reference.delay known = set(reference.calibrated) @@ -351,26 +279,8 @@ def _geometry( current normal and keeps its origin otherwise; a gap leaves it away from any declared jump (no side); its axes are the state's frame, turned by any mirror. - Parameters - ---------- - plane : FieldPlane - The input plane. - state : SpatialState - The output state, whose frame holds the output axes. - elements : sequence - The applied elements, in order. - declared : PlaneGeometry or None - The caller's output geometry, checked against the output axes. - - Returns - ------- - PlaneGeometry - The output geometry. - - Raises - ------ - CouplingError - If a declared geometry's normal or x axis is not the transported one. + - ``state``: the output state, whose frame holds the output axes. + - ``declared``: the caller's output geometry, checked against the output axes. """ row_normal, row_x = state.frame[2], state.frame[0] normal = (float(row_normal[0]), float(row_normal[1]), float(row_normal[2])) diff --git a/src/solprop/driver/_launch.py b/src/solprop/driver/_launch.py index b7c6fc65d..991486635 100644 --- a/src/solprop/driver/_launch.py +++ b/src/solprop/driver/_launch.py @@ -72,15 +72,7 @@ class LaunchedMode: def _number(value: object) -> float | None: """Return a real number as a float, or ``None`` for anything else. - Parameters - ---------- - value : object - The value. - - Returns - ------- - float or None - The value, for an int or float that is not a bool; ``None`` otherwise. + - Returns: the value, for an int or float that is not a bool; ``None`` otherwise. """ if isinstance(value, bool) or not isinstance(value, numbers.Real): return None @@ -90,23 +82,7 @@ def _number(value: object) -> float | None: def _entry(value: object, where: str) -> tuple[float, float]: """Return one mode's share and phase from its entry. - Parameters - ---------- - value : object - A share, or a mapping with ``"share"`` and, optionally, ``"phase"``. - where : str - The entry's path, for a refusal. - - Returns - ------- - tuple of float - The share as given, and the phase, rad. - - Raises - ------ - DriverError - If the entry is neither, names another key, or holds a share that is - negative or not finite, or a phase that is not finite. + - Returns: the share as given, and the phase, rad. """ if isinstance(value, Mapping): unknown = sorted(str(key) for key in value if key not in _ENTRY_KEYS) @@ -136,26 +112,6 @@ def _resolve(key: object, labels: Sequence[str], where: str) -> int: A display label names its row; an index names that row. A label without its parity or handedness suffix (``"HE1,1"`` for ``"HE1,1:even"``) names the one row that completes it, and is refused where both partners are present. - - Parameters - ---------- - key : object - The key: a display label, a label without its suffix, or an index. - labels : sequence of str - The mode set's display labels, in row order. - where : str - The key's path, for a refusal. - - Returns - ------- - int - The row. - - Raises - ------ - DriverError - If the key is not a string or an integer, names no row, or leaves a parity - open between two rows. """ if isinstance(key, str): if key in labels: @@ -184,28 +140,7 @@ def _resolve(key: object, labels: Sequence[str], where: str) -> int: def _one_pulse( launch: object, labels: Sequence[str], where: str ) -> tuple[LaunchedMode, ...]: - """Resolve and normalise one pulse's launch. - - Parameters - ---------- - launch : object - The mapping from mode to entry. - labels : sequence of str - The mode set's display labels, in row order. - where : str - The mapping's path, for a refusal. - - Returns - ------- - tuple of LaunchedMode - The modes in row order, their shares summing to one. - - Raises - ------ - DriverError - If the mapping is empty or not a mapping, names a mode twice, or has no - positive share. - """ + """Resolve and normalise one pulse's launch.""" if not isinstance(launch, Mapping) or not launch: msg = ( f"{where} maps mode labels to shares, as {{'HE1,1': 0.9, 'HE1,2': " @@ -233,27 +168,7 @@ def _one_pulse( def _resolve_launch( launch: object, labels: Sequence[str], n_pulses: int ) -> tuple[tuple[LaunchedMode, ...], ...]: - """Return each pulse's launch: one mapping for every pulse, or one each. - - Parameters - ---------- - launch : object - The ``launch`` keyword. - labels : sequence of str - The mode set's display labels, in row order. - n_pulses : int - How many pulses the input holds. - - Returns - ------- - tuple of tuple of LaunchedMode - One launch per pulse. - - Raises - ------ - DriverError - If a list does not hold one mapping per pulse, or a mapping is refused. - """ + """Return each pulse's launch: one mapping for every pulse, or one each.""" if isinstance(launch, Mapping): one = _one_pulse(launch, labels, "launch") return (one,) * n_pulses @@ -281,22 +196,7 @@ def refuse_beside( incident: object = None, coupling: object = None, ) -> None: - """Refuse a launch beside another way of saying what is launched. - - Parameters - ---------- - launch : object - The ``launch`` keyword. - channel : int or str or None, optional - The named channel, which a launch generalises. - incident, coupling : object, optional - An incident beam and its coupling model, which compute a launch of their own. - - Raises - ------ - DriverError - If `launch` is given beside any of them. - """ + """Refuse a launch beside another way of saying what is launched.""" if launch is None: return if channel is not None: @@ -326,20 +226,8 @@ def _placed( ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: """Place one pulse in its modes, each at its share and phase. - Parameters - ---------- - built : numpy.ndarray - The pulse, shape ``(1, n_pol, n_omega)``, internal convention, in the square - root of watts. - modes : tuple of LaunchedMode - Its modes. - n_modes : int - How many modes the set carries. - - Returns - ------- - numpy.ndarray - The pulse's part of the state, shape ``(n_modes, n_pol, n_omega)``. + - ``built``: the pulse, shape ``(1, n_pol, n_omega)``, internal convention, in the + square root of watts. """ part = np.zeros((n_modes, *built.shape[1:]), dtype=np.complex128) for mode in modes: @@ -360,39 +248,14 @@ def launch_pulse( ) -> tuple[Complex[np.ndarray, "n_modes n_pol n_omega"], dict[str, object]]: """Launch an input's pulses into chosen modes, and return the state and its record. - Parameters - ---------- - pulse : solprop.pulses.Pulse - The input: a pulse, or a - :class:`~solprop.pulses.PulseSum` whose members are launched one by one. - launch : object - The ``launch`` keyword: a mapping from mode label or index to a share, or to - ``{"share": ..., "phase": ...}`` with the phase in radians; or a list of such - mappings, one per pulse. - grid : solprop.grid.TimeGrid - The run's grid. - labels : sequence of solprop.modes.protocol.ChannelLabel - The mode set's labels, in row order. - metric : numpy.ndarray or None, optional - The basis flux metric ``W(omega)`` at the launch plane, shape - ``(n_modes, n_modes, n_omega)``; ``None``, the default, is the identity, on - which the shares are energy fractions. - - Returns - ------- - numpy.ndarray - The state, shape ``(n_modes, n_pol, n_omega)``, internal convention, in the - square root of watts. - dict - The record, ``configuration["modal_launch"]``: the ``split`` (``"energy"`` or - ``"coordinate"``) and, per pulse, its modes' labels, normalised shares and - phases in rad. - - Raises - ------ - DriverError - If a label, share or phase is refused, a list does not hold one mapping per - pulse, or a pulse builds into more than one mode. + - ``metric``: the basis flux metric ``W(omega)`` at the launch plane, shape + ``(n_modes, n_modes, n_omega)``; ``None``, the default, is the identity, on which + the shares are energy fractions. + - Returns: the state, shape ``(n_modes, n_pol, n_omega)``, internal convention, in + the square root of watts. + - Returns: the record, ``configuration["modal_launch"]``: the ``split`` + (``"energy"`` or ``"coordinate"``) and, per pulse, its modes' labels, normalised + shares and phases in rad. """ members: tuple[Pulse, ...] = ( pulse.pulses if isinstance(pulse, PulseSum) else (pulse,) diff --git a/src/solprop/driver/_scan_batch.py b/src/solprop/driver/_scan_batch.py index 46fa34ae9..7a816f951 100644 --- a/src/solprop/driver/_scan_batch.py +++ b/src/solprop/driver/_scan_batch.py @@ -61,20 +61,7 @@ class _Prepared: def _construct(simulation: Simulation, cleanup: ExitStack) -> Backend: - """Construct a member's selected backend with its cleanup registered. - - Parameters - ---------- - simulation : Simulation - Independently translated member. - cleanup : contextlib.ExitStack - Owner of the backend's lifetime until a group takes it over. - - Returns - ------- - solprop.backends.protocol.Backend - The backend, holding the member's parameter tables and no state yet. - """ + """Construct a member's selected backend with its cleanup registered.""" owner = select( simulation.settings.backend, simulation.translate(), @@ -89,20 +76,7 @@ def _construct(simulation: Simulation, cleanup: ExitStack) -> Backend: def _prepare(job: _Job, work: _Work) -> _Prepared | PointResult: - """Validate a point's seed and hash once, before batch admission. - - Parameters - ---------- - job : _Job - Global point and existing completion digest. - work : _Work - Builder and retention policy. - - Returns - ------- - _Prepared or PointResult - Pending simulation, skipped point, hash conflict or builder failure. - """ + """Validate a point's seed and hash once, before batch admission.""" try: simulation = work.simulation(job) metadata = simulation.metadata() @@ -130,25 +104,6 @@ def _admit(group: Sequence[_Prepared], work: _Work, executor: Batch) -> int: unique byte count is recorded, not added again. The first workspace uses the larger of measured RSS and baseline plus its ledger; only subsequent workspaces are additive. Two output copies cover capture consolidation and writer transfer. - - Parameters - ---------- - group : sequence of _Prepared - Bounded compatible simulations, already translated. - work : _Work - Retention policy used to estimate point outputs. - executor : Batch - Resource bounds and destination for the admission calculation. - - Returns - ------- - int - Positive number of members admitted simultaneously. - - Raises - ------ - DriverError - If the allocation cannot accommodate even one member. """ limit = _memory_limit() if executor.memory_limit is not None: @@ -233,21 +188,8 @@ def _plane( ) -> SavedPlane: """Build the plane a member's capture stores from its observation. - Parameters - ---------- - session : solprop.backends.session.GroupSession - The group, its batch pending. - member : int - The member's position. - event : solprop.backends.events.ObservationReady - The member's observation. - keep : bool - Whether the scan reads this plane's field. Otherwise no field crosses. - - Returns - ------- - solprop.output.schema.SavedPlane - The plane, its field in true-power amplitudes and the internal convention. + - Returns: the plane, its field in true-power amplitudes and the internal + convention. """ rows = event.step_stats return SavedPlane( @@ -272,24 +214,6 @@ def _service( A boundary stop ends its member alone, as a lone run of it would fail (SEP 21); a group-wide failure is raised, and fails every member. - - Parameters - ---------- - session : solprop.backends.session.GroupSession - The group, its batch pending. - batch : solprop.backends.events.GroupBatch - One common window's events. - captures : sequence of _Capture - Independent member outputs in dispatch order. - errors : list of str - Per-member failures; a failed member receives no further planes. - keep : bool - Whether the scan reads this plane's field. - - Raises - ------ - BaseException - The group-wide failure, when the window ended the group. """ if batch.error is not None: raise batch.error @@ -321,18 +245,7 @@ def _service( def _observations(batch: GroupBatch) -> Iterator[ObservationReady]: - """Yield the save-plane observations of a window, in member order. - - Parameters - ---------- - batch : solprop.backends.events.GroupBatch - The window's events. - - Yields - ------ - solprop.backends.events.ObservationReady - Each member's plane, the launch excluded. - """ + """Yield the save-plane observations of a window, in member order.""" for events in batch.members: for event in events: if isinstance(event, ObservationReady) and event.plane_index >= 0: @@ -340,20 +253,7 @@ def _observations(batch: GroupBatch) -> Iterator[ObservationReady]: def _run(group: Sequence[_Prepared], work: _Work) -> Iterator[PointResult]: - """Run one admitted group and preserve independently reduced point results. - - Parameters - ---------- - group : sequence of _Prepared - Compatible, admitted simulations with global indices. - work : _Work - Capture and reduction policy. - - Yields - ------ - PointResult - Complete per-member output or an explicit runtime failure. - """ + """Run one admitted group and preserve independently reduced point results.""" started = time.perf_counter() captures: list[_Capture] = [] results: list[PointResult] = [] @@ -361,20 +261,7 @@ def _run(group: Sequence[_Prepared], work: _Work) -> Iterator[PointResult]: records: Sequence[Mapping[str, object] | None] = [None] * len(group) def failed(index: int, error: str) -> PointResult: - """Retain available provenance when one member fails. - - Parameters - ---------- - index : int - Member position within this admitted group. - error : str - Traceback from propagation, capture or reduction. - - Returns - ------- - PointResult - Failure with the available backend record and no numerical arrays. - """ + """Retain available provenance when one member fails.""" member = group[index] capture = captures[index] if index < len(captures) else None metadata = ( @@ -498,40 +385,14 @@ def failed(index: int, error: str) -> PointResult: def _stopping(work: _Work) -> bool: - """Return whether the scan's stop flag is set. - - Parameters - ---------- - work : _Work - The scan's work, which holds the caller's flag. - - Returns - ------- - bool - True once the flag is set. - """ + """Return whether the scan's stop flag is set.""" return work.stop is not None and work.stop.is_set() def execute( jobs: Iterable[_Job], work: _Work, executor: Batch ) -> Iterator[PointResult]: - """Build bounded groups and execute their independently selected backend. - - Parameters - ---------- - jobs : iterable of _Job - Pending and previously completed points in dispatch order. - work : _Work - Simulation builder and requested output contract. - executor : Batch - Size and memory limits, plus recorded admission calculations. - - Yields - ------ - PointResult - Globally indexed result suitable for the controller's ordinary writer. - """ + """Build bounded groups and execute their independently selected backend.""" backend: str | None = None for chunk in batched(jobs, executor.size, strict=False): pending: dict[int, _Prepared] = {} diff --git a/src/solprop/driver/_scan_model.py b/src/solprop/driver/_scan_model.py index dff5d4f95..d62dbd315 100644 --- a/src/solprop/driver/_scan_model.py +++ b/src/solprop/driver/_scan_model.py @@ -56,22 +56,7 @@ def name_seed(name: str, index: int) -> int: ``SeedSequence([root, global_index]).generate_state(1, dtype=uint64)[0]``, the derivation ``base_seed`` uses. - Parameters - ---------- - name : str - The scan's name. - index : int - The point's global index. - - Returns - ------- - int - A non-negative 64-bit seed. - - Examples - -------- - >>> name_seed("rdw", 3) == name_seed("rdw", 3) != name_seed("rdw", 4) - True + - Returns: a non-negative 64-bit seed. """ root = int.from_bytes(hashlib.sha256(name.encode("utf-8")).digest()[:8], "little") sequence = np.random.SeedSequence([root, index]) @@ -141,10 +126,7 @@ def __repr__(self) -> str: def _describe(self) -> tuple[str, list[tuple[str, object]]]: """Return the title and rows of the printed block. - Returns - ------- - tuple - The title and ``(key, value)`` rows. + - Returns: the title and ``(key, value)`` rows. """ rows: list[tuple[str, object]] = [ (name, f"{values.size} values, {values[0]:.6g} to {values[-1]:.6g}") @@ -156,36 +138,24 @@ def _describe(self) -> tuple[str, list[tuple[str, object]]]: return f"Scan {self.name!r}: {self.size} points", rows @property - def variables(self) -> Mapping[str, np.ndarray]: - """Return copies of the named axes in declaration order. + def variables(self) -> Mapping[str, np.ndarray]: # numpydoc ignore=RT01 + """Copies of the named axes in declaration order. - Returns - ------- - collections.abc.Mapping - One-dimensional arrays; changing them cannot alter this scan. + One-dimensional arrays; changing them cannot alter this scan. """ return {name: values.copy() for name, values in self._variables.items()} @property - def shape(self) -> tuple[int, ...]: - """Return the complete Cartesian shape, including for a subset. + def shape(self) -> tuple[int, ...]: # numpydoc ignore=RT01 + """Complete Cartesian shape, including for a subset. - Returns - ------- - tuple of int - Axis lengths in declaration order; empty before any variable is added. + Axis lengths in declaration order; empty before any variable is added. """ return tuple(values.size for values in self._variables.values()) @property - def size(self) -> int: - """Return the number of points in the complete scan. - - Returns - ------- - int - Non-negative Cartesian product size; zero for no variables. - """ + def size(self) -> int: # numpydoc ignore=RT01 + """Number of points in the complete scan; zero for no variables.""" return prod(self.shape) if self.shape else 0 def __len__(self) -> int: diff --git a/src/solprop/driver/_scan_run.py b/src/solprop/driver/_scan_run.py index 650a3db31..973f3e796 100644 --- a/src/solprop/driver/_scan_run.py +++ b/src/solprop/driver/_scan_run.py @@ -106,22 +106,7 @@ def launch_statistics( assembled with: the builders' own is in true sqrt(W), while ``initial.noise0`` is already divided by the entrance weight. - Parameters - ---------- - backend : solprop.backends.protocol.Backend - The constructed backend. - problem : solprop.translate.problem.PropagationProblem - Its problem. - - Returns - ------- - collections.abc.Mapping - The launch plane's statistics, without a batch axis. - - Raises - ------ - BaseException - The launch's own failure, when its statistics could not be evaluated. + - Returns: the launch plane's statistics, without a batch axis. """ session = start_session( backend, problem, backend.request, Start.entrance(problem), owns=False @@ -141,17 +126,7 @@ def launch_statistics( def _plane_index(index: int, count: int) -> int: """Resolve a saved-plane index and reject out-of-range selections. - Parameters - ---------- - index : int - Global point index, or saved-plane index where specified. - count : int - Number of saved planes, including the input. - - Returns - ------- - int - Zero-based resolved saved-plane index. + - ``index``: global point index, or saved-plane index where specified. """ resolved = index + count if index < 0 else index if not 0 <= resolved < count: @@ -166,19 +141,7 @@ def _checked( ) -> np.ndarray: """Validate a reduction against its declared array contract. - Parameters - ---------- - reduction : Reduction - Reduction declaration including shape, dtype and units. - value : np.ndarray - Value to validate or serialise. - metadata : RunMetadata - Complete single-run metadata. - - Returns - ------- - numpy.ndarray - Validated independent copy of the reduction result. + - ``reduction``: reduction declaration including shape, dtype and units. """ array = np.asarray(value) expected = reduction.shape(metadata) @@ -195,14 +158,7 @@ def _checked( class _Capture(MemoryOutput): """Keep statistics, selected fields and plane-local reductions only. - Parameters - ---------- - options : _Options - Validated field, statistic and reduction retention policy. - simulation : Simulation - Simulation whose translated shape determines storage. - scratch : Path - Worker-local temporary directory. + - ``simulation``: simulation whose translated shape determines storage. """ def __init__( @@ -238,50 +194,26 @@ def __init__( def retain(self, retention: FieldRetention, field_shape: tuple[int, ...]) -> None: """Adopt the run's field retention, and hand it to the spool of a reduction. - Parameters - ---------- - retention : solprop.output.schema.FieldRetention - Which planes keep their field, resolved. - field_shape : tuple of int - ``(n_modes, n_pol, n_omega)`` of the run's state. + - ``field_shape``: ``(n_modes, n_pol, n_omega)`` of the run's state. """ super().retain(retention, field_shape) if self.spool is not None: self.spool.retain(retention, field_shape) def open(self, metadata: RunMetadata, *, force: bool = False) -> None: - """Initialize retained statistics and any temporary field spool. - - Parameters - ---------- - metadata : RunMetadata - Complete single-run metadata. - force : bool - Whether to replace existing metadata. - """ + """Initialize retained statistics and any temporary field spool.""" super().open(metadata, force=force) if self.spool is not None: self.spool.open(metadata) def _keep(self, name: str, value: np.ndarray | float) -> None: - """Copy retained data before the backend reuses its buffers. - - Parameters - ---------- - name : str - Dataset path or column name. - value : np.ndarray | float - Value to validate or serialise. - """ + """Copy retained data before the backend reuses its buffers.""" self.arrays.setdefault(name, []).append(np.asarray(value).copy()) def save(self, plane: SavedPlane) -> None: """Capture requested fields and statistics before the next step. - Parameters - ---------- - plane : SavedPlane - Saved plane in the internal Fourier convention, or its index. + - ``plane``: saved plane in the internal Fourier convention, or its index. """ metadata = self.metadata if metadata is None: @@ -344,10 +276,7 @@ def _last_saved_z(self) -> float: The capture keeps its columns itself rather than in the store; a stopped run reads this back before it records its terminal checkpoint. - Returns - ------- - float - The position, m. + - Returns: the position, m. """ return float(self.arrays["z"][-1]) @@ -360,10 +289,7 @@ def close(self) -> None: def results(self) -> dict[str, np.ndarray]: """Combine retained columns and evaluate complete-output reducers. - Returns - ------- - dict - Retained arrays keyed by their storage paths. + - Returns: retained arrays keyed by their storage paths. """ if self.spool is not None: with open_file(self.custom_path) as output: @@ -391,10 +317,8 @@ def results(self) -> dict[str, np.ndarray]: class _Display(NullScanReporter): """The outer display a named progress choice means: one bar, log lines, or nothing. - Parameters - ---------- - choice : {'auto', 'bar', 'log', 'none'} - The named choice; ``"auto"`` draws a bar when the process is interactive. + - ``choice``: the named choice; ``"auto"`` draws a bar when the process is + interactive. """ def __init__(self, choice: ProgressChoice) -> None: @@ -404,13 +328,7 @@ def __init__(self, choice: ProgressChoice) -> None: self.started = time.perf_counter() def scan_started(self, n_points: int) -> None: - """Draw the bar, when one is chosen. - - Parameters - ---------- - n_points : int - The selected points, the bar's extent. - """ + """Draw the bar, when one is chosen.""" self.started = time.perf_counter() if self.choice == "bar" or (self.choice == "auto" and is_interactive()): self.bar = BarReporter(position=0, leave=True, echo=False) @@ -419,14 +337,9 @@ def scan_started(self, n_points: int) -> None: def point_finished(self, index: int, status: str, wall: float) -> None: """Log a point that did not fail, and advance the bar. - Parameters - ---------- - index : int - The point's global index. - status : str - Its status; a failure is logged with its traceback when it is collected. - wall : float - Its build and run time, s. + - ``status``: its status; a failure is logged with its traceback when it is + collected. + - ``wall``: its build and run time, s. """ if status != "failed" and self.choice != "none": logger.info("scan point %d: %s (%.3f s)", index, status, wall) @@ -446,13 +359,6 @@ class _ScanEvents: Lives in the controlling process only. A point's start is reported when it is dispatched (``Local``, ``Pool``) or, for an executor that reports none, just before its result is collected. - - Parameters - ---------- - reporter : solprop.log.ScanReporter - Where the events go. - scan : Scan - The scan, which looks a collected point up by its global index. """ def __init__(self, reporter: ScanReporter, scan: Scan) -> None: @@ -465,16 +371,8 @@ def __init__(self, reporter: ScanReporter, scan: Scan) -> None: def begin(self, point: ScanPoint) -> ProgressReporter | None: """Report a point's start and return the reporter for its run, if any. - Parameters - ---------- - point : ScanPoint - The point dispatched. - - Returns - ------- - solprop.log.ProgressReporter or None - The reporter :meth:`solprop.log.WatchingScanReporter.point_reporter` - returned, or ``None``. + - Returns: the reporter :meth:`solprop.log.WatchingScanReporter.point_reporter` + returned, or ``None``. """ self.started.add(point.index) self.reporter.point_started(point.index, point) @@ -494,18 +392,8 @@ def relay( ) -> None: """Make a reporter call a pool worker sent for its point's run. - Parameters - ---------- - index : int - The point's global index. - call : str - ``"summary"``, ``"started"``, ``"plane"``, ``"finished"`` or - ``"interrupted"``. - args : tuple - The call's arguments as :class:`~solprop.driver.executors.pool.Pool`'s - worker sends them. - stop : StopFlag or None - The caller's flag, which a worker's stop stands for. + - ``call``: ``"summary"``, ``"started"``, ``"plane"``, ``"finished"`` or + ``"interrupted"``. """ watcher = self.watchers.get(index) if watcher is None: @@ -531,13 +419,7 @@ def relay( watcher.interrupted(z, error) def finish(self, result: PointResult) -> None: - """Report a collected point, starting it first if nothing has. - - Parameters - ---------- - result : PointResult - The point's result. - """ + """Report a collected point, starting it first if nothing has.""" if result.index not in self.started: self.started.add(result.index) self.reporter.point_started(result.index, self.scan.point(result.index)) @@ -565,13 +447,7 @@ class _Work: _kept: SimulationRunner | None = field(default=None, init=False, repr=False) def __getstate__(self) -> dict[str, object]: - """Drop the controller's flag and reporter, which stay in its process. - - Returns - ------- - dict - The fields a worker needs. - """ + """Drop the controller's flag and reporter, which stay in its process.""" state: dict[str, object] = dict(self.__dict__) return state | {"stop": None, "events": None, "_kept": None} @@ -595,17 +471,6 @@ def _run_point( a scan in this process keeps it while each point is the last but for its input, which a builder arranges with :meth:`Simulation.with_input`. A point that isn't closes it first, so that two are never held, and one that raises takes it down. - - Parameters - ---------- - simulation : Simulation - The point, keeping the fields the scan reads. - capture : _Capture - Where its planes go. - progress : ProgressReporter, {'auto', 'bar', 'log', 'none'} or None - Where progress goes. - stop : object or None - The point's stop flag. """ if ( not self.keeps_backend @@ -626,65 +491,26 @@ def _run_point( raise def dispatched(self, job: _Job) -> None: - """Report that a pool sent a point to a worker. - - Parameters - ---------- - job : _Job - The point. - """ + """Report that a pool sent a point to a worker.""" if self.events is not None: self.events.begin(job.point) def relay(self, job: _Job, call: str, args: tuple[object, ...]) -> None: - """Make a reporter call a pool worker sent for its point's run. - - Parameters - ---------- - job : _Job - The point the worker runs. - call : str - The reporter method. - args : tuple - Its arguments. - """ + """Make a reporter call a pool worker sent for its point's run.""" if self.events is not None: self.events.relay(job.point.index, call, args, self.stop) def stopped(self, job: _Job, wall_time: float = 0.0) -> PointResult: """Return the result of a point the stop flag stopped, which is not committed. - Parameters - ---------- - job : _Job - The point. - wall_time : float, optional - What it ran for before it stopped, s. - - Returns - ------- - PointResult - Its ``"stopped"`` result. + - ``wall_time``: what it ran for before it stopped, s. """ return PointResult( job.point.index, "stopped", error=STOPPED, wall_time=wall_time ) def run_batch(self, jobs: Iterable[_Job], executor: Batch) -> Iterator[PointResult]: - """Delegate compatible jobs to the common lockstep scan driver. - - Parameters - ---------- - jobs : iterable of _Job - Global jobs, including previously completed configuration hashes. - executor : Batch - Batch-size and memory policy, with recorded admission calculations. - - Yields - ------ - PointResult - One durable result per dispatched point. - """ + """Delegate compatible jobs to the common lockstep scan driver.""" from solprop.driver._scan_batch import ( # noqa: PLC0415 - shared worker types execute, ) @@ -699,16 +525,6 @@ def simulation(self, job: _Job) -> Simulation: that the point is reproducible and a rerun matches the record of its first run; noise the builder seeded must have used ``point.seed``. The draw goes on a copy: a builder may return one object for every point. - - Parameters - ---------- - job : _Job - Global point and previously committed configuration digest. - - Returns - ------- - Simulation - Constructed simulation with reproducible noise. """ simulation = self.build(job.point) if not isinstance(simulation, Simulation): @@ -759,15 +575,7 @@ def simulation(self, job: _Job) -> Simulation: def probe(self, job: _Job) -> Mapping[str, int]: """Measure backend allocations without propagating a point. - Parameters - ---------- - job : _Job - Global point and previously committed configuration digest. - - Returns - ------- - collections.abc.Mapping - Peak RSS, baseline RSS and named backend allocation bytes. + - Returns: peak RSS, baseline RSS and named backend allocation bytes. """ baseline = peak_rss() simulation = self.simulation(job) @@ -792,20 +600,7 @@ def probe(self, job: _Job) -> Mapping[str, int]: backend.close() def failure(self, job: _Job, error: str) -> PointResult: - """Record an abnormal worker exit without losing other completed points. - - Parameters - ---------- - job : _Job - Point assigned to the terminated worker. - error : str - Child traceback or abnormal-exit explanation. - - Returns - ------- - PointResult - Failure of this attempt, which never demotes a committed point. - """ + """Record an abnormal worker exit without losing other completed points.""" # A crash is a crash whatever the point's stored state: only `__call__` # can see a genuine configuration disagreement, and only it reports one. # Calling a dead worker a conflict aborted the whole scan through @@ -817,17 +612,8 @@ def _output_bytes( ) -> int: """Estimate retained plane data and reductions for resource admission. - Parameters - ---------- - simulation : Simulation - Probed point with its final translation. - statistics : mapping - One plane of statistics with resolved species and mode axes. - - Returns - ------- - int - Retained array bytes; adaptive periodic rows use the memory headroom. + - ``statistics``: one plane of statistics with resolved species and mode axes. + - Returns: retained array bytes; adaptive periodic rows use the memory headroom. """ count = len(simulation.settings.save_planes) + 1 selected = ( @@ -880,18 +666,7 @@ def _output_bytes( return field_bytes + stat_bytes + periodic_bytes + reduced_bytes + count * 8 def __call__(self, job: _Job) -> PointResult: - """Run a point in this process, under the caller's flag and reporter. - - Parameters - ---------- - job : _Job - Global point and previously committed configuration digest. - - Returns - ------- - object - Completed calculation, captured point failure, or a stopped point. - """ + """Run a point in this process, under the caller's flag and reporter.""" reporter = None if self.events is None else self.events.begin(job.point) return self.run_point(job, self.stop, reporter) @@ -900,20 +675,9 @@ def run_point( ) -> PointResult: """Execute the declared worker calculation. - Parameters - ---------- - job : _Job - Global point and previously committed configuration digest. - stop : StopFlag or None - The flag the point's run polls; when it stops the run, the point is - ``"stopped"``, not done. - reporter : solprop.log.ProgressReporter or None - Where the point's run reports; ``None`` runs it silently. - - Returns - ------- - object - Completed calculation, captured point failure, or a stopped point. + - ``stop``: the flag the point's run polls; when it stops the run, the point is + ``"stopped"``, not done. + - ``reporter``: where the point's run reports; ``None`` runs it silently. """ started = time.perf_counter() metadata_json = digest = "" @@ -996,15 +760,7 @@ def run_point( def _closing_monitor(capture: _Capture | None) -> Mapping[str, object] | None: """Return a point's closing boundary-monitor record from its capture. - Parameters - ---------- - capture : _Capture or None - The point's capture, once its run has recorded an outcome. - - Returns - ------- - collections.abc.Mapping or None - The record, or ``None`` when the point was not watched or never ran. + - Returns: the record, or ``None`` when the point was not watched or never ran. """ outcome = None if capture is None else capture.outcome record = None if outcome is None else outcome.execution.get("boundary_monitor") @@ -1017,16 +773,6 @@ def _field_retention(capture: _Capture) -> FieldRetention: The fields the scan retains, those its built-in reductions read at their target planes, and, while a custom reduction that reads fields spools the run, every field (SEP 22, Q3). - - Parameters - ---------- - capture : _Capture - The point's capture, which knows the selected and target planes. - - Returns - ------- - solprop.output.schema.FieldRetention - The rule, its plane indices resolved. """ options = capture.options spooled = any( @@ -1052,18 +798,6 @@ def _retained(simulation: Simulation, capture: _Capture) -> Simulation: The builder's own simulation is left as it is: a builder may return one object for every point. - - Parameters - ---------- - simulation : Simulation - The point's simulation, translated. - capture : _Capture - The point's capture, which knows the selected and target planes. - - Returns - ------- - Simulation - A shallow copy sharing the translation, with the scan's field retention. """ retention = _field_retention(capture) if retention.fields == "all": @@ -1087,26 +821,9 @@ def _jobs( # noqa: PLR0917 - called positionally ) -> Iterator[_Job]: """Claim eligible points lazily as dispatch capacity becomes available. - Parameters - ---------- - scan : Scan - Cartesian scan or selection retaining global indices. - writer : ScanWriter - Controller-owned scan file. - retry : bool - Whether failed points are eligible. - age : float - Positive abandonment interval, s. - stop : StopFlag or None - Once set, no further point is claimed. - claims : dict - Filled with each claimed point's state and update time before the claim, so - that a stopped point is given back as it was (`_release`). - - Yields - ------ - object - Result of the declared operation. + - ``age``: positive abandonment interval, s. + - ``claims``: filled with each claimed point's state and update time before the + claim, so that a stopped point is given back as it was (`_release`). """ for point in scan.points(): if stop is not None and stop.is_set(): @@ -1128,14 +845,7 @@ def _release(writer: ScanWriter, index: int, claim: tuple[str, float]) -> None: A stopped point is not committed, so a rerun computes it again: left ``running``, a rerun would skip it for ``stale_age``. - Parameters - ---------- - writer : ScanWriter - Controller-owned scan file. - index : int - The point's global index. - claim : tuple - Its state and update time before the claim. + - ``claim``: its state and update time before the claim. """ state, updated = claim _dataset(writer.file, "points/status")[index] = state @@ -1161,39 +871,8 @@ def execute_scan( ) -> ScanOutput: """Validate, dispatch and commit a scan through one writer. - Parameters - ---------- - scan : Scan - Cartesian scan or selection retaining global indices. - build : Callable[[ScanPoint], Simulation] - Callable constructing a Simulation from a ScanPoint. - output : Path | str - Output handler for reductions, or collected destination path for execution. - executor : Executor | None - Execution policy; None selects the CLI policy or Local. - reductions : Sequence[Reduction] - Worker-side reduction declarations. - fields : str - Field retention mode: none, planes or all. - field_planes : Sequence[int] - Explicit saved-plane indices; negative indices count from the end. - stats : str | Sequence[str] - All, none, or explicit statistic names. - retry_failed : bool - Whether to dispatch previously failed points. - stale_age : float - Positive abandonment interval, s. - progress : {'auto', 'bar', 'log', 'none'} or solprop.log.ScanReporter - Outer progress display, or a reporter that replaces it. - stop : StopFlag or None, optional - The scan's stop flag; refused by Queue and Slurm. - recipe : solprop.output.schema.RecipeRecord or None, optional - The recipe the scan was built from, recorded once in its file. - - Returns - ------- - ScanOutput - Reader for the collected scan. + - ``executor``: execution policy; None selects the CLI policy or Local. + - ``stale_age``: positive abandonment interval, s. """ _validate_options(fields, field_planes, stats, stale_age, reductions) if progress not in _PROGRESS_CHOICES and not isinstance( @@ -1277,14 +956,8 @@ def _collect( ) -> None: """Commit a collected point, or give a stopped one back, and log a failure. - Parameters - ---------- - result : PointResult - The point's result. - writer : ScanWriter or None - Local controller writer, or None for a durable queue. - claim : tuple or None - The point's state and update time before this scan claimed it. + - ``writer``: local controller writer, or None for a durable queue. + - ``claim``: the point's state and update time before this scan claimed it. """ if result.status == "stopped": if writer is not None and claim is not None: @@ -1320,16 +993,7 @@ def _finish_execution( ) -> None: """Record controller provenance and consolidate the durable output. - Parameters - ---------- - executor : Executor - Selected execution policy and measured resource settings. - build : Callable[[ScanPoint], Simulation] - Builder whose source identifies this invocation. - writer : ScanWriter or None - Local controller writer, or None for a durable queue. - output : pathlib.Path or str - Collected scan destination. + - ``writer``: local controller writer, or None for a durable queue. """ policy = ( dataclasses.asdict(executor) @@ -1363,18 +1027,7 @@ def _validate_options( ) -> None: """Reject ambiguous retention controls before opening the output. - Parameters - ---------- - fields : str - Field retention mode: none, planes or all. - field_planes : Sequence[int] - Explicit saved-plane indices; negative indices count from the end. - stats : str | Sequence[str] - All, none, or explicit statistic names. - stale_age : float - Positive abandonment interval, s. - reductions : Sequence[Reduction] - Worker-side reduction declarations. + - ``stale_age``: positive abandonment interval, s. """ refusal = field_retention_refusal(fields, field_planes) if refusal is not None: diff --git a/src/solprop/driver/batch.py b/src/solprop/driver/batch.py index 0a1de7799..a19b3bd9e 100644 --- a/src/solprop/driver/batch.py +++ b/src/solprop/driver/batch.py @@ -42,23 +42,7 @@ class BatchMember: def _signature(member: BatchMember) -> str: - """Hash the execution layout while keeping member parameter tables independent. - - Parameters - ---------- - member : BatchMember - Independently constructed and translated point. - - Returns - ------- - str - Digest of the shared grid, algorithm, schedule and response structure. - - Raises - ------ - DriverError - If the point has no fixed schedule or uses an unsupported execution contract. - """ + """Hash the execution layout while keeping member parameter tables independent.""" simulation = member.simulation backend = simulation.settings.backend if backend not in {"numpy_ref", "native"}: @@ -113,16 +97,8 @@ def _signature(member: BatchMember) -> str: def _modal_identity(problem: PropagationProblem) -> Mapping[str, object] | None: """Describe which general modal guide a problem carries, or ``None`` for none. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - A translated member. - - Returns - ------- - collections.abc.Mapping or None - The table, metric and response identities, all scalars and strings, or - ``None`` where the problem is not a general modal run at all. + - Returns: the table, metric and response identities, all scalars and strings, or + ``None`` where the problem is not a general modal run at all. """ tables = problem.transform.modal_tables if tables is None: @@ -191,18 +167,7 @@ def refuse_mixed_guides(problems: Sequence[PropagationProblem]) -> None: def _structure(value: object) -> object: - """Describe table layouts without including their member-dependent values. - - Parameters - ---------- - value : object - Compiled response record or one of its data fields. - - Returns - ------- - object - Portable structure retaining types, field names and array layouts. - """ + """Describe table layouts without including their member-dependent values.""" if isinstance(value, np.ndarray): return (value.dtype.str, value.shape) if dataclasses.is_dataclass(value) and not isinstance(value, type): @@ -283,21 +248,6 @@ def _checked(current: Sequence[BatchMember]) -> tuple[BatchMember, ...]: propagating one guide's channels against another's reference. The check is a JSON comparison of identities the members' cached translations already carry, so it costs nothing per group. - - Parameters - ---------- - current : collections.abc.Sequence - The members of one formed group, in group order. - - Returns - ------- - tuple - The same members. - - Raises - ------ - DriverError - Naming the field at which two members part company. """ refuse_mixed_guides([member.simulation.translate() for member in current]) return tuple(current) @@ -320,14 +270,8 @@ def shared_table_bytes(problems: Iterable[PropagationProblem]) -> int: visited: set[int] = set() storage: dict[int, int] = {} - def visit(value: object) -> None: - """Track backing allocations without recounting shared records or views. - - Parameters - ---------- - value : object - Translated record, collection or array whose storage is being counted. - """ + def _visit(value: object) -> None: + """Track backing allocations without recounting shared records or views.""" if id(value) in visited: return visited.add(id(value)) @@ -338,14 +282,14 @@ def visit(value: object) -> None: storage[id(root)] = root.nbytes elif dataclasses.is_dataclass(value) and not isinstance(value, type): for field in dataclasses.fields(value): - visit(getattr(value, field.name)) + _visit(getattr(value, field.name)) elif isinstance(value, Mapping): for item in value.values(): - visit(item) + _visit(item) elif isinstance(value, (tuple, list)): for item in value: - visit(item) + _visit(item) for problem in problems: - visit(problem) + _visit(problem) return sum(storage.values()) diff --git a/src/solprop/driver/estimate.py b/src/solprop/driver/estimate.py index ad060f1e2..304c74fea 100644 --- a/src/solprop/driver/estimate.py +++ b/src/solprop/driver/estimate.py @@ -90,14 +90,8 @@ class RunEstimate: total_calls: int | None @property - def step(self) -> float: - """Return the evaluations' share of an accepted step's time. - - Returns - ------- - float - Seconds per step. - """ + def step(self) -> float: # numpydoc ignore=RT01 + """Evaluations' share of an accepted step's time, s.""" return self.rhs * self.calls_per_step @@ -110,19 +104,7 @@ def _timed_step( entrance that commits nothing, or for the low-storage march its own step, which advances the discarded registers in place. - Parameters - ---------- - backend : solprop.backends.protocol.Backend - The constructed backend, holding no state yet; closed by the caller. - problem : solprop.translate.problem.PropagationProblem - The translated problem. - request : solprop.translate.run_request.RunRequest - Its run's request, whose method decides the evaluations. - - Returns - ------- - tuple - A callable taking one step, and the right-hand-side evaluations in it. + - Returns: a callable taking one step, and the right-hand-side evaluations in it. """ integrator = request.integration.method evaluations = ( @@ -134,18 +116,7 @@ def _timed_step( def _calls_per_step(integrator: object) -> int: - """Return an integrator's right-hand-side evaluations per accepted step. - - Parameters - ---------- - integrator : object - The settings' integrator. - - Returns - ------- - int - One for the Adams method, four for the low-storage march, six for DOPRI5. - """ + """Return an integrator's right-hand-side evaluations per accepted step.""" if isinstance(integrator, Multistep): return 1 if isinstance(integrator, LowStorageRungeKutta): @@ -350,23 +321,10 @@ def field_bytes(problem: PropagationProblem) -> int: def _catalogued_entries(shape: str, sizes: Mapping[str, int]) -> int: """Return how many entries a statistic of a catalogued shape holds. - Parameters - ---------- - shape : str - The catalogue's shape, such as ``'(n_windows, n_modes)'``, ``'(2,)'`` or - ``'()'``; an alternative after ``';'`` is ignored. - sizes : collections.abc.Mapping - The length of every axis name a catalogued shape uses. - - Returns - ------- - int - The product of the axis lengths, one for a scalar. - - Raises - ------ - DriverError - If the shape names an axis `sizes` does not give. + - ``shape``: the catalogue's shape, such as ``'(n_windows, n_modes)'``, ``'(2,)'`` + or ``'()'``; an alternative after ``';'`` is ignored. + - ``sizes``: the length of every axis name a catalogued shape uses. + - Returns: the product of the axis lengths, one for a scalar. """ axes = shape.partition(";")[0].strip().strip("()") count = 1 diff --git a/src/solprop/driver/executors/_queue_state.py b/src/solprop/driver/executors/_queue_state.py index 61c31025b..07ec5ab3a 100644 --- a/src/solprop/driver/executors/_queue_state.py +++ b/src/solprop/driver/executors/_queue_state.py @@ -22,18 +22,7 @@ @contextmanager def locked(path: Path) -> Iterator[None]: - """Hold a kernel-owned lock whose ownership ends when the process dies. - - Parameters - ---------- - path : Path - Queue directory or record filename used by this operation. - - Yields - ------ - None - Control while the exclusive advisory lock is held. - """ + """Hold a kernel-owned lock whose ownership ends when the process dies.""" descriptor = os.open(path, os.O_CREAT | os.O_RDWR, 0o600) try: lock(descriptor) @@ -44,13 +33,7 @@ def locked(path: Path) -> Iterator[None]: def sync_directory(path: Path) -> None: - """Make filename publication durable on a filesystem supporting directory fsync. - - Parameters - ---------- - path : Path - Queue directory or record filename used by this operation. - """ + """Make filename publication durable on a filesystem supporting directory fsync.""" descriptor = os.open(path, os.O_RDONLY) try: os.fsync(descriptor) @@ -59,15 +42,7 @@ def sync_directory(path: Path) -> None: def atomic_json(path: Path, value: object) -> None: - """Replace a JSON record only after its complete content has been fsynced. - - Parameters - ---------- - path : Path - Queue directory or record filename used by this operation. - value : object - JSON-serialisable record to publish atomically. - """ + """Replace a JSON record only after its complete content has been fsynced.""" descriptor, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent) temporary = Path(name) try: @@ -82,18 +57,7 @@ def atomic_json(path: Path, value: object) -> None: def read_json(path: Path) -> dict: - """Read a JSON object while refusing malformed state containers. - - Parameters - ---------- - path : Path - Queue directory or record filename used by this operation. - - Returns - ------- - dict - Validated JSON object. - """ + """Read a JSON object while refusing malformed state containers.""" value = json.loads(path.read_text(encoding="utf-8")) if not isinstance(value, dict): raise DriverError(f"queue record must be a JSON object: {path}") @@ -103,10 +67,7 @@ def read_json(path: Path) -> dict: def owner() -> dict: """Identify this controller independently of each point's ownership token. - Returns - ------- - dict - Host, PID, process creation identity and controller UUID. + - Returns: host, PID, process creation identity and controller UUID. """ return { "controller": uuid.uuid4().hex, @@ -119,17 +80,8 @@ def owner() -> dict: def live_owner(row: Mapping, age: float) -> bool: """Protect live local processes and remotely refreshed leases from takeover. - Parameters - ---------- - row : Mapping - Point state containing owner identity and last refresh time. - age : float - Positive lease duration in seconds. - - Returns - ------- - bool - Whether takeover is forbidden by local liveness or a fresh lease. + - ``row``: point state containing owner identity and last refresh time. + - ``age``: positive lease duration in seconds. """ holder = row.get("owner", {}) if holder.get("host") == socket.gethostname(): @@ -149,32 +101,13 @@ def live_owner(row: Mapping, age: float) -> bool: def state(path: Path) -> dict: - """Read active state or the completion marker published before its removal. - - Parameters - ---------- - path : Path - Queue directory or record filename used by this operation. - - Returns - ------- - dict - Active state or the durable completion record. - """ + """Read active state or the completion marker published before its removal.""" active = path / "queue.json" return read_json(active if active.exists() else path / ".done") def finish(path: Path, record: dict) -> None: - """Publish completion before removing active queue state. - - Parameters - ---------- - path : Path - Queue directory or record filename used by this operation. - record : dict - Complete mutable queue state under its advisory lock. - """ + """Publish completion before removing active queue state.""" atomic_json(path / "queue.json", record) if all(row["status"] == "done" for row in record["rows"]): atomic_json(path / ".done", record) diff --git a/src/solprop/driver/executors/pool.py b/src/solprop/driver/executors/pool.py index 2fa32a581..31bef542a 100644 --- a/src/solprop/driver/executors/pool.py +++ b/src/solprop/driver/executors/pool.py @@ -72,25 +72,13 @@ class ControlledWork(Protocol): """ @property - def stop(self) -> StopFlag | None: - """Return the caller's stop flag, read in the controlling process. - - Returns - ------- - StopFlag or None - The flag; ``None`` never stops. - """ + def stop(self) -> StopFlag | None: # numpydoc ignore=RT01 + """The caller's stop flag, read in the controlling process, or ``None``.""" ... @property - def watched(self) -> bool: - """Return whether a worker sends its point's progress to the controller. - - Returns - ------- - bool - True when the controller has a reporter for points. - """ + def watched(self) -> bool: # numpydoc ignore=RT01 + """Whether a worker sends its point's progress to the controller.""" ... def run_point( @@ -192,18 +180,7 @@ def of(cls, summary: PlaneSummary) -> "PlaneScalars": def _is_stop(message: object) -> bool: - """Return whether a message from the controller asks the worker to stop. - - Parameters - ---------- - message : object - What arrived on the pipe. - - Returns - ------- - bool - True for the stop message. - """ + """Return whether a message from the controller asks the worker to stop.""" return isinstance(message, str) and message == STOP @@ -213,11 +190,6 @@ class _Channel: :meth:`is_set` is the stop flag the worker's run polls, as any run polls its flag. It reads what has arrived without waiting, remembers a stop, and keeps any other message for :meth:`command`, so polling never consumes a command. - - Parameters - ---------- - connection : Connection or PipeConnection - The worker's end of the pipe. """ def __init__(self, connection: _Pipe) -> None: @@ -227,13 +199,7 @@ def __init__(self, connection: _Pipe) -> None: self._stopped = False def is_set(self) -> bool: - """Return whether the controller has asked the worker to stop. - - Returns - ------- - bool - True once a stop message has arrived; it stays set. - """ + """Return whether the controller has asked the worker to stop.""" while not self._stopped and self.connection.poll(): message = self.connection.recv() if _is_stop(message): @@ -245,10 +211,7 @@ def is_set(self) -> bool: def command(self) -> object: """Return the next command, waiting for it; a stop message is remembered. - Returns - ------- - object - ``("run" or "probe", point)``, or ``None`` to exit. + - Returns: ``("run" or "probe", point)``, or ``None`` to exit. """ while not self._held: message = self.connection.recv() @@ -259,15 +222,7 @@ def command(self) -> object: return self._held.popleft() def send(self, call: str, args: tuple[object, ...]) -> None: - """Send one reporter call to the controller. - - Parameters - ---------- - call : str - The reporter method. - args : tuple - Its picklable arguments. - """ + """Send one reporter call to the controller.""" self.connection.send((PROGRESS, call, args)) @@ -278,11 +233,6 @@ class _WorkerReporter: :class:`PlaneScalars` of its summary. An interruption carries its position, whether the worker's stop flag caused it, and its text: the exception itself may not pickle, and the stop flag that stopped it is the worker's, not the caller's. - - Parameters - ---------- - channel : _Channel - The worker's pipe. """ def __init__(self, channel: _Channel) -> None: @@ -290,70 +240,26 @@ def __init__(self, channel: _Channel) -> None: self.channel = channel def summary(self, text: str) -> None: - """Send the run's summary block. - - Parameters - ---------- - text : str - See :meth:`solprop.log.SummarisingReporter.summary`. - """ + """Send the run's summary block.""" self.channel.send("summary", (text,)) def started(self, z_start: float, z_end: float, n_planes: int) -> None: - """Send the run's start. - - Parameters - ---------- - z_start, z_end : float - See :meth:`solprop.log.ProgressReporter.started`. - n_planes : int - See :meth:`solprop.log.ProgressReporter.started`. - """ + """Send the run's start.""" self.channel.send("started", (float(z_start), float(z_end), int(n_planes))) def step(self, z: float, dz: float, accepted: bool) -> None: - """Send nothing: a step is on the hot path. - - Parameters - ---------- - z, dz : float - See :meth:`solprop.log.ProgressReporter.step`. - accepted : bool - See :meth:`solprop.log.ProgressReporter.step`. - """ + """Send nothing: a step is on the hot path.""" def plane(self, z: float, summary: PlaneSummary) -> None: - """Send a plane's position and summary. - - Parameters - ---------- - z : float - See :meth:`solprop.log.ProgressReporter.plane`. - summary : solprop.log.PlaneSummary - See :meth:`solprop.log.ProgressReporter.plane`. - """ + """Send a plane's position and summary.""" self.channel.send("plane", (float(z), PlaneScalars.of(summary))) def finished(self, total: float) -> None: - """Send the run's end. - - Parameters - ---------- - total : float - See :meth:`solprop.log.ProgressReporter.finished`. - """ + """Send the run's end.""" self.channel.send("finished", (float(total),)) def interrupted(self, z: float, error: BaseException) -> None: - """Send that the run stopped before its end. - - Parameters - ---------- - z : float - See :meth:`solprop.log.InterruptibleReporter.interrupted`. - error : BaseException - See :meth:`solprop.log.InterruptibleReporter.interrupted`. - """ + """Send that the run stopped before its end.""" self.channel.send( "interrupted", ( @@ -415,13 +321,7 @@ class _MemoryStatus(ctypes.Structure): def _kernel32() -> ctypes.CDLL: - """Load Windows' kernel32, which carries both memory queries, typed for them. - - Returns - ------- - ctypes.CDLL - The library handle. - """ + """Load Windows' kernel32, which carries both memory queries, typed for them.""" kernel32 = ctypes.WinDLL("kernel32") # pyright: ignore[reportAttributeAccessIssue] # The process pseudo-handle is pointer-sized ((HANDLE)-1): ctypes' default int # return type truncates it to 32 bits, and Windows refuses the truncated handle. @@ -439,15 +339,7 @@ def _kernel32() -> ctypes.CDLL: def _windows_peak_working_set() -> int: """Read this process's peak working set, Windows' counterpart of peak RSS. - Returns - ------- - int - Peak working set, bytes. - - Raises - ------ - DriverError - If Windows refuses the query. + - Returns: peak working set, bytes. """ kernel32 = _kernel32() counters = _ProcessMemoryCounters() @@ -463,15 +355,7 @@ def _windows_peak_working_set() -> int: def _windows_physical_memory() -> int: """Read the installed physical memory on Windows. - Returns - ------- - int - Total physical memory, bytes. - - Raises - ------ - DriverError - If Windows refuses the query. + - Returns: total physical memory, bytes. """ status = _MemoryStatus() status.dwLength = ctypes.sizeof(status) @@ -483,10 +367,7 @@ def _windows_physical_memory() -> int: def _memory_limit() -> int: """Read the smallest physical or scheduler memory allocation. - Returns - ------- - int - Smallest available memory limit, bytes. + - Returns: smallest available memory limit, bytes. """ limits: list[int] = [] if sys.platform == "win32": @@ -512,13 +393,7 @@ def _memory_limit() -> int: def _cpus() -> tuple[int, ...]: - """Restrict CPU choices to affinity, scheduler and cgroup allocations. - - Returns - ------- - tuple of int - CPUs available within the allocation. - """ + """Restrict CPU choices to affinity, scheduler and cgroup allocations.""" try: budget = thread_budget() except ValueError as error: @@ -597,13 +472,7 @@ def workers(self, requested: int | None = None) -> int: def _limit_blas(threads: int) -> None: - """Hold this worker's NumPy and SciPy BLAS libraries to its thread allowance. - - Parameters - ---------- - threads : int - Positive threads per worker. - """ + """Hold this worker's NumPy and SciPy BLAS libraries to its thread allowance.""" # Delayed: only this platform's workers load these before their first command. import scipy.linalg # noqa: F401, PLC0415 - loads SciPy's own BLAS, to be limited import threadpoolctl # noqa: PLC0415 @@ -619,16 +488,9 @@ def _serve( ) -> None: """Process bounded commands inside one spawned interpreter. - Parameters - ---------- - connection : Connection or PipeConnection - Worker command/result pipe. - work : Callable - Pickleable point operation and optional memory probe. - cpus : tuple[int, ...] - Allocated logical CPU identifiers; empty leaves affinity unchanged. - blas_threads : int, optional - Threads to hold BLAS to after loading it, where the environment can't. + - ``cpus``: allocated logical CPU identifiers; empty leaves affinity unchanged. + - ``blas_threads``: threads to hold BLAS to after loading it, where the environment + can't. """ channel = _Channel(connection) try: @@ -665,19 +527,7 @@ def _spawn( ) -> tuple[BaseProcess, _Pipe]: """Set the child environment before interpreter startup. - Parameters - ---------- - work : Callable - Pickleable point operation and optional memory probe. - threads : int - Positive threads per worker, established before imports. - cpus : tuple[int, ...] - Allocated logical CPU identifiers; empty leaves affinity unchanged. - - Returns - ------- - tuple - Worker process and controller pipe. + - ``cpus``: allocated logical CPU identifiers; empty leaves affinity unchanged. """ context = mp.get_context("spawn") parent, child = context.Pipe() @@ -707,18 +557,7 @@ class _Progress: def _receive(connection: _Pipe) -> object: - """Read a completed result or report abnormal worker termination. - - Parameters - ---------- - connection : Connection or PipeConnection - Worker command/result pipe. - - Returns - ------- - object - Worker result, or a `_Progress` call of the run still in progress. - """ + """Read a completed result or report abnormal worker termination.""" try: message = connection.recv() except EOFError as error: @@ -734,24 +573,7 @@ def _receive(connection: _Pipe) -> object: def _failed_work(work: Callable, point: object, error: _WorkerError) -> object: """Let a scan record a crashed point while generic executors still raise. - Parameters - ---------- - work : callable - Worker operation with an optional failure-result constructor. - point : object - Description assigned to the failed worker. - error : _WorkerError - Child failure or unexpected pipe closure. - - Returns - ------- - object - Explicit failed result, if the work supports one. - - Raises - ------ - DriverError - If the worker operation has no failure-result contract. + - Returns: explicit failed result, if the work supports one. """ failed = getattr(work, "failure", None) if not callable(failed): @@ -760,15 +582,7 @@ def _failed_work(work: Callable, point: object, error: _WorkerError) -> object: def _stop(process: BaseProcess, connection: _Pipe) -> None: - """Close a worker and release its pipe even after a failure. - - Parameters - ---------- - process : BaseProcess - Spawned worker process. - connection : Connection or PipeConnection - Worker command/result pipe. - """ + """Close a worker and release its pipe even after a failure.""" try: if process.is_alive(): connection.send(None) @@ -889,17 +703,7 @@ def run[T, R](self, points: Iterable[T], work: Callable[[T], R]) -> Iterator[R]: def _admit(self, work: Callable, first: object) -> tuple[tuple[int, ...], int]: """Probe the first point and calculate the worker allocation. - Parameters - ---------- - work : Callable - Pickleable point operation and optional memory probe. - first : object - First pending point, used for memory admission. - - Returns - ------- - tuple - Allocated CPUs and admitted worker count. + - Returns: allocated CPUs and admitted worker count. """ available = _cpus() # SEP 46: the workers' allocation is calibrated at the standard level before @@ -932,15 +736,7 @@ def _admit(self, work: Callable, first: object) -> tuple[tuple[int, ...], int]: class _Session: - """The workers of one :meth:`Pool.run`, the points they run, and the stop. - - Parameters - ---------- - policy : Pool - The pool's resource controls. - work : callable - The point work, :class:`ControlledWork` or not. - """ + """The workers of one :meth:`Pool.run`, the points they run, and the stop.""" def __init__(self, policy: Pool, work: Callable) -> None: """Hold the work, with no worker started and no stop asked for.""" @@ -955,17 +751,7 @@ def __init__(self, policy: Pool, work: Callable) -> None: def start( self, pending: Iterator[object], available: tuple[int, ...], count: int ) -> None: - """Start up to `count` workers, each with its first point. - - Parameters - ---------- - pending : iterator - The points, claimed lazily as workers become free. - available : tuple of int - The CPUs the workers are pinned to, `threads` each. - count : int - The admitted workers. - """ + """Start up to `count` workers, each with its first point.""" self.pending = pending threads = self.policy.threads for slot in range(count): @@ -982,13 +768,7 @@ def start( self._dispatch(process, connection, cpus, point) def drain(self) -> Iterator[object]: - """Collect results as they finish, and keep the free workers busy. - - Yields - ------ - object - Results in completion order. - """ + """Collect results as they finish, and keep the free workers busy.""" # A short wait while a flag can stop the pool: it is read between messages. timeout = 0.5 if self.stop is None else 0.1 while self.active: @@ -1023,34 +803,14 @@ def _dispatch( cpus: tuple[int, ...], point: object, ) -> None: - """Send a point to a worker and record it. - - Parameters - ---------- - process : BaseProcess - The worker. - connection : Connection or PipeConnection - Its pipe. - cpus : tuple of int - Its CPUs. - point : object - The point it runs. - """ + """Send a point to a worker and record it.""" self.active[connection] = process, cpus, point connection.send(("run", point)) if self.controlled is not None: self.controlled.dispatched(point) def _next(self, connection: _Pipe, *, crashed: bool) -> None: - """Give a worker that returned its result the next point, or shut it down. - - Parameters - ---------- - connection : Connection or PipeConnection - The worker's pipe. - crashed : bool - Whether the worker died, so that a fresh one replaces it. - """ + """Give a worker that returned its result the next point, or shut it down.""" process, cpus, _ = self.active[connection] point: object = None if not self._halt(): @@ -1066,13 +826,7 @@ def _next(self, connection: _Pipe, *, crashed: bool) -> None: self._dispatch(process, connection, cpus, point) def _halt(self) -> bool: - """Ask every busy worker to stop once the caller's flag is set. - - Returns - ------- - bool - Whether the pool is stopping. - """ + """Ask every busy worker to stop once the caller's flag is set.""" if not self.stopping and self.stop is not None and self.stop.is_set(): self.stopping = True for busy in self.active: diff --git a/src/solprop/driver/executors/queue.py b/src/solprop/driver/executors/queue.py index 658ddc8b7..296f67aae 100644 --- a/src/solprop/driver/executors/queue.py +++ b/src/solprop/driver/executors/queue.py @@ -48,18 +48,7 @@ def _point(job: object) -> ScanPoint: - """Require a work description carrying a globally indexed ScanPoint. - - Parameters - ---------- - job : object - Point or work description carrying a globally indexed ScanPoint. - - Returns - ------- - ScanPoint - Validated global point description. - """ + """Require a work description carrying a globally indexed ScanPoint.""" point = job if isinstance(job, ScanPoint) else getattr(job, "point", None) if not isinstance(point, ScanPoint): raise DriverError("Queue work must carry a ScanPoint") @@ -67,35 +56,14 @@ def _point(job: object) -> ScanPoint: def _point_id(point: ScanPoint) -> str: - """Bind a claim to its global index, variable values and derived seed. - - Parameters - ---------- - point : ScanPoint - Globally indexed point with variable values and resolved seed. - - Returns - ------- - str - SHA-256 binding the point values, index and seed. - """ + """Bind a claim to its global index, variable values and derived seed.""" return _hash(_json(point)) def _filename(manifest: Mapping, index: int) -> str: """Keep artifact names independent of task and chunk ordering. - Parameters - ---------- - manifest : Mapping - Immutable scan description and builder identity. - index : int - Zero-based global point index. - - Returns - ------- - str - Stable global point artifact filename. + - ``manifest``: immutable scan description and builder identity. """ return f"{manifest['name']}_{index:05d}.h5" @@ -103,15 +71,7 @@ def _filename(manifest: Mapping, index: int) -> str: def _builder_key(build: object) -> str: """Identify serialised builder parameters and available script source. - Parameters - ---------- - build : object - Pickleable builder, including captured construction parameters. - - Returns - ------- - str - Digest of serialised construction parameters and source bytes. + - Returns: digest of serialised construction parameters and source bytes. """ original = build while isinstance(original, functools.partial): @@ -132,15 +92,8 @@ def _builder_key(build: object) -> str: def _restore_scan(manifest: Mapping) -> Scan: """Reconstruct portable axes without importing recorded classes. - Parameters - ---------- - manifest : Mapping - Immutable scan description and builder identity. - - Returns - ------- - Scan - Full Cartesian scan reconstructed from portable axis metadata. + - ``manifest``: immutable scan description and builder identity. + - Returns: full Cartesian scan reconstructed from portable axis metadata. """ description = json.loads(manifest["description"]) variables = { @@ -169,15 +122,7 @@ class _QueueWork: def probe(self, claim: _Claim) -> Mapping[str, int]: """Measure the wrapped worker before admitting local processes. - Parameters - ---------- - claim : _Claim - Point description and unique ownership token. - - Returns - ------- - mapping of str to int - Baseline RSS, peak RSS and backend allocation bytes. + - Returns: baseline RSS, peak RSS and backend allocation bytes. """ probe = getattr(self.work, "probe", None) if not callable(probe): @@ -187,15 +132,7 @@ def probe(self, claim: _Claim) -> Mapping[str, int]: def __call__(self, claim: _Claim) -> PointResult: """Compute and publish one claimed point. - Parameters - ---------- - claim : _Claim - Point description and unique ownership token. - - Returns - ------- - PointResult - Published result, or skipped if ownership was superseded. + - Returns: published result, or skipped if ownership was superseded. """ result = self.work(claim.job) if not isinstance(result, PointResult): @@ -203,39 +140,13 @@ def __call__(self, claim: _Claim) -> PointResult: return self._publish(claim, result) def failure(self, claim: _Claim, error: str) -> PointResult: - """Publish an abnormal worker exit as a retryable failed point. - - Parameters - ---------- - claim : _Claim - Point description and unique ownership token. - error : str - Captured worker failure or abnormal process exit. - - Returns - ------- - PointResult - Durably recorded worker failure. - """ + """Publish an abnormal worker exit as a retryable failed point.""" return self._publish( claim, PointResult(_point(claim.job).index, "failed", error=error) ) def _publish(self, claim: _Claim, result: PointResult) -> PointResult: - """Commit an artifact only if its ownership token is still current. - - Parameters - ---------- - claim : _Claim - Point description and unique ownership token. - result : PointResult - Validated successful or failed worker result. - - Returns - ------- - PointResult - Published result, or skipped after ownership changed. - """ + """Commit an artifact only if its ownership token is still current.""" manifest = read_json(self.path / "manifest.json") index = _point(claim.job).index if result.index != index: @@ -276,14 +187,8 @@ def _publish(self, claim: _Claim, result: PointResult) -> PointResult: class _Heartbeat: """Refresh this controller's leases independently of point completion. - Parameters - ---------- - path : Path - Queue directory or record filename used by this operation. - controller : str - Unique controller identifier whose leases must be refreshed. - age : float - Positive lease duration in seconds. + - ``controller``: unique controller identifier whose leases must be refreshed. + - ``age``: positive lease duration in seconds. """ def __init__(self, path: Path, controller: str, age: float) -> None: @@ -447,19 +352,7 @@ def _claims( ) -> Iterator[_Claim]: """Claim eligible points atomically while preserving live owners. - Parameters - ---------- - points : Iterable - Lazily supplied point descriptions retaining global indices. - holder : Mapping - Controller host, process creation identity and unique identifier. - heartbeat : _Heartbeat - Running lease refresher whose failure stops further dispatch. - - Yields - ------ - _Claim - Eligible point carrying a newly published ownership token. + - ``holder``: controller host, process creation identity and unique identifier. """ path = Path(cast(Path, self.path)) for job in points: @@ -628,24 +521,7 @@ def collect(path: Path | str, output: Path | str) -> ScanOutput: def _collect_points( path: Path, manifest: Mapping, record: Mapping, writer: ScanWriter ) -> bool: - """Validate and collect the unchanged point artifacts in a queue snapshot. - - Parameters - ---------- - path : pathlib.Path - Shared queue directory. - manifest : mapping - Validated immutable scan and builder identity. - record : mapping - Queue snapshot under the state lock. - writer : ScanWriter - Exclusively held controller writer. - - Returns - ------- - bool - Whether any point changed the collected file. - """ + """Validate and collect the unchanged point artifacts in a queue snapshot.""" changed = False scan = _restore_scan(manifest) for index, row in enumerate(record["rows"]): diff --git a/src/solprop/driver/executors/slurm.py b/src/solprop/driver/executors/slurm.py index 10d45ffc0..99f62ea2c 100644 --- a/src/solprop/driver/executors/slurm.py +++ b/src/solprop/driver/executors/slurm.py @@ -29,15 +29,7 @@ def _memory_bytes(value: str) -> int: """Interpret positive Slurm memory quantities, whose suffix-free unit is MiB. - Parameters - ---------- - value : str - JSON-serialisable record to publish atomically. - - Returns - ------- - int - Memory allocation in bytes. + - Returns: memory allocation in bytes. """ match = re.fullmatch(r"([1-9][0-9]*)([KMGT]?)", value.upper()) if match is None: @@ -124,13 +116,7 @@ def __post_init__(self) -> None: @property def _has_gpu(self) -> bool: - """Report whether the allocation carries a GPU, independently of instances. - - Returns - ------- - bool - Whether the generic resources name a GPU. - """ + """Report whether the allocation carries a GPU, independently of instances.""" return self.gres is not None and "gpu" in self.gres def validate(self, work: Callable) -> None: diff --git a/src/solprop/driver/material_output.py b/src/solprop/driver/material_output.py index 8c5e0c676..7a579e74a 100644 --- a/src/solprop/driver/material_output.py +++ b/src/solprop/driver/material_output.py @@ -181,20 +181,10 @@ def _cross_sections( ) -> np.ndarray | None: """Factor the mode's first-order attribution into density-independent rows. - Parameters - ---------- - mixture : Mixture - Ordered material populations. - modes : sequence of Mode - Capillary modes, or an empty sequence for bulk propagation. - omega : numpy.ndarray - Positive angular frequencies in rad/s within the active band. - - Returns - ------- - numpy.ndarray or None - Cross sections in m^2, shape (n_species, n_modes_or_one, n_omega), or None - when the total loss policy supplies no species attribution. + - ``modes``: capillary modes, or an empty sequence for bulk propagation. + - ``omega``: positive angular frequencies in rad/s within the active band. + - Returns: cross sections in m^2, shape (n_species, n_modes_or_one, n_omega), or + None when the total loss policy supplies no species attribution. """ if not modes: return np.asarray( @@ -247,18 +237,9 @@ def _cross_sections( def _children(value: object) -> list[object]: """Return what the manifest's walk visits next from one object. - Parameters - ---------- - value : object - A dataclass, a ``functools.partial``, a bound method, or any object with a - ``__dict__``. - - Returns - ------- - list - A dataclass's field values in declaration order; a partial's function, - arguments and keywords; a bound method's instance; otherwise the instance - attributes, none for an object that has none. + - Returns: a dataclass's field values in declaration order; a partial's function, + arguments and keywords; a bound method's instance; otherwise the instance + attributes, none for an object that has none. """ if isinstance(value, functools.partial): return [value.func, value.args, value.keywords] @@ -277,16 +258,6 @@ def _walk(roots: Sequence[object]) -> Iterator[object]: user's mode -- but never into an array, a number, a string, a function or an object of a package in :data:`_STOP_PACKAGES`. Each object is visited once, so a cycle ends, and the order is fixed by the objects themselves. - - Parameters - ---------- - roots : sequence - The run's objects: its configuration, modes, responses, media and grids. - - Yields - ------ - object - Each reachable object outside the stopped packages, in depth-first order. """ seen: set[int] = set() stack: list[object] = list(reversed(roots)) @@ -320,17 +291,9 @@ def _single_species_fills( ) -> list[tuple[Species, GasFillMixin]]: """Return each distinct single-species fill among the run's modes. - Parameters - ---------- - found : sequence - The objects the walk reached. - - Returns - ------- - list of tuple - ``(species, mode)`` for each gas fill without a mixture whose species - carries optmat records, one per distinct species, density path and - temperature: the modes of one guide share their fill. + - Returns: ``(species, mode)`` for each gas fill without a mixture whose species + carries optmat records, one per distinct species, density path and temperature: + the modes of one guide share their fill. """ fills: list[tuple[Species, GasFillMixin]] = [] seen: list[tuple[Species, str, object]] = [] @@ -351,15 +314,7 @@ def _single_species_fills( def _records(value: object) -> tuple[MaterialRecord, ...]: """Return an object's optmat records, or none for one that carries none. - Parameters - ---------- - value : object - Any of solprop's objects. - - Returns - ------- - tuple of MaterialRecord - Its ``material_records``, where it has them. + - Returns: its ``material_records``, where it has them. """ found = getattr(value, "material_records", ()) return tuple(found) if isinstance(found, tuple) else () @@ -368,18 +323,9 @@ def _records(value: object) -> tuple[MaterialRecord, ...]: def _solid_entry(glass: OptmatGlass, log: MaterialLog | None) -> dict[str, object]: """Return an optmat solid's manifest entry. - Parameters - ---------- - glass : OptmatGlass - The adapter. - log : MaterialLog or None - The run's evaluation log, whose dropped loss is the run's own; ``None`` - takes what the adapter has dropped over its life. - - Returns - ------- - dict - Its material, axis, model, digest, transparency policy and measurement. + - ``log``: the run's evaluation log, whose dropped loss is the run's own; ``None`` + takes what the adapter has dropped over its life. + - Returns: its material, axis, model, digest, transparency policy and measurement. """ entry = glass.material_records[0] return { @@ -396,16 +342,8 @@ def _solid_entry(glass: OptmatGlass, log: MaterialLog | None) -> dict[str, objec def _citation_entry(value: object) -> dict[str, object] | None: """Return the citation of a material object that is not optmat's, if any. - Parameters - ---------- - value : object - Any of solprop's objects. - - Returns - ------- - dict or None - ``object``, ``name`` and ``citation`` for a glass, a gas susceptibility or - a species that carries no optmat record; ``None`` otherwise. + - Returns: ``object``, ``name`` and ``citation`` for a glass, a gas susceptibility + or a species that carries no optmat record; ``None`` otherwise. """ if not isinstance(value, GlassIndex | GasSusceptibility | Species) or _records( value @@ -421,15 +359,8 @@ def _citation_entry(value: object) -> dict[str, object] | None: def _window(omega: Float[np.ndarray, " n_omega"]) -> float | None: """Return the time window a frequency grid resolves. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s, uniformly spaced in some order. - - Returns - ------- - float or None - ``2 pi / d_omega`` in s, or ``None`` for fewer than two frequencies. + - ``omega``: angular frequencies in rad/s, uniformly spaced in some order. + - Returns: ``2 pi / d_omega`` in s, or ``None`` for fewer than two frequencies. """ spacing = np.diff(np.unique(np.asarray(omega, dtype=float))) return None if spacing.size == 0 else float(2.0 * np.pi / np.min(spacing)) @@ -440,19 +371,10 @@ def _raman_entry( ) -> dict[str, object]: """Return what was measured about an optmat Raman response's assembly. - Parameters - ---------- - response : OptmatRamanResponse - The response. - omega : numpy.ndarray - The run's angular frequencies in rad/s, whose spacing fixes the window over - which a held line shift's phase error is bounded. - - Returns - ------- - dict - The assembly's record with the labels, and the shift's phase bound in rad - over the run's window (``None`` without a held shift). + - ``omega``: the run's angular frequencies in rad/s, whose spacing fixes the window + over which a held line shift's phase error is bounded. + - Returns: the assembly's record with the labels, and the shift's phase bound in rad + over the run's window (``None`` without a held shift). """ assembly = response.assembly entry: dict[str, object] = {"labels": list(response.labels)} @@ -471,18 +393,9 @@ def _raman_entry( def _gnlse_policy(problems: Sequence[GnlseProblem]) -> dict[str, object] | None: """Return a GNLSE run's held-back Raman fraction and where it came from. - Parameters - ---------- - problems : sequence of GnlseProblem - The run's GNLSE mappings: none, or one. - - Returns - ------- - dict or None - ``fraction``, its ``origin`` (``"user"``, ``"bundle"`` or - ``"raman-off"``), whether a Raman response carries it, the silica - ``bundle`` (or ``None``) and the origin of ``n2``; ``None`` for a run - without a GNLSE mapping. + - Returns: ``fraction``, its ``origin`` (``"user"``, ``"bundle"`` or + ``"raman-off"``), whether a Raman response carries it, the silica ``bundle`` (or + ``None``) and the origin of ``n2``; ``None`` for a run without a GNLSE mapping. """ if not problems: return None @@ -508,27 +421,12 @@ def _policies( ) -> dict[str, object]: """Return the approximation policies a run's materials are evaluated under. - Parameters - ---------- - selection : MaterialSelection or None - The run's selection. - dilute : bool - Whether a gas susceptibility from optmat is scaled linearly in density. - carrier : float or None - The carrier in m an optmat Kerr coefficient was evaluated at, or ``None`` - when the run has no optmat Kerr model. - gnlse : dict or None - A GNLSE run's held-back fraction (:func:`_gnlse_policy`). - raman : sequence of OptmatRamanResponse, optional - The run's optmat Raman responses. - dispersive : dict or None, optional - The dispersive electronic law in force (:func:`_dispersive_policy`), which - replaces the carrier law; ``None`` when the run has none. - - Returns - ------- - dict - The policies; one a later phase resolves is recorded as selected. + - ``carrier``: the carrier in m an optmat Kerr coefficient was evaluated at, or + ``None`` when the run has no optmat Kerr model. + - ``dispersive``: the dispersive electronic law in force + (:func:`_dispersive_policy`), which replaces the carrier law; ``None`` when the + run has none. + - Returns: the policies; one a later phase resolves is recorded as selected. """ assemblies = [one.assembly for one in raman if one.assembly is not None] temperatures = sorted({assembly.temperature for assembly in assemblies}) @@ -575,18 +473,7 @@ def _policies( def _dispersive_label(response: DispersiveKerrResponse) -> str: - """Return the population a dispersive response is recorded under. - - Parameters - ---------- - response : DispersiveKerrResponse - The response. - - Returns - ------- - str - Its component label, or its gas for a single-gas run. - """ + """Return the population a dispersive response is recorded under.""" return response.model.material if response.species is None else response.species @@ -598,21 +485,10 @@ def _dispersive_entries( ) -> list[dict[str, object]]: """Return the manifest's electronic entries of a run's dispersive responses. - Parameters - ---------- - responses : sequence of DispersiveKerrResponse - The run's dispersive responses. - band : tuple of float or None - The run's active band in rad/s, or ``None`` when it is not known. - vector : bool - Whether the run's field has two polarisation components at a node. - - Returns - ------- - list of dict - Per response: its label, gas, evaluator, sector and tensor approximation, the - model and support digests, the leg bound and both band edges in m^-1, and the - regions of the band (:meth:`ElectronicRegions.to_dict`), in label order. + - ``band``: the run's active band in rad/s, or ``None`` when it is not known. + - Returns: per response: its label, gas, evaluator, sector and tensor approximation, + the model and support digests, the leg bound and both band edges in m^-1, and the + regions of the band (:meth:`ElectronicRegions.to_dict`), in label order. """ entries = [] for response in responses: @@ -647,31 +523,16 @@ def _dispersive_entries( def _density_identities(found: Sequence[object]) -> dict[str, str]: """Return the identity of each population's density profile, by channel label. - Parameters - ---------- - found : sequence of object - The run's objects: its mixtures, and a general modal guide's regions, whose - gas fills' channels are labelled ``":"``. - - Returns - ------- - dict - Per label, the SHA-256 of the canonical JSON of the profile's record -- its - samples, breakpoints and declarations, as the run's configuration records a - mixture (:meth:`solprop.physdata.mixtures.Mixture.serialise`). + - ``found``: the run's objects: its mixtures, and a general modal guide's regions, + whose gas fills' channels are labelled ``":"``. + - Returns: per label, the SHA-256 of the canonical JSON of the profile's record -- + its samples, breakpoints and declarations, as the run's configuration records a + mixture (:meth:`solprop.physdata.mixtures.Mixture.serialise`). """ identities: dict[str, str] = {} def add(prefix: str, mixture: Mixture) -> None: - """Record a mixture's populations under a label prefix. - - Parameters - ---------- - prefix : str - Prepended to every population label. - mixture : Mixture - The mixture. - """ + """Record a mixture's populations under a label prefix.""" record = cast("list[dict[str, object]]", mixture.serialise()["components"]) for component in record: identities.setdefault( @@ -696,21 +557,11 @@ def _mixture_filters( ) -> list[dict[str, object]]: """Return the symmetric mixture filters a run's leaf builds, for its record. - Parameters - ---------- - leaf : solprop.translate.responses.DispersiveKerrTerms or None - The run's compiled dispersive channels. - densities : Mapping of str to str - Each label's density-profile identity (:func:`_density_identities`). - - Returns - ------- - list of dict - One per group of several species under the symmetric evaluator - (:meth:`~solprop.translate.responses.DispersiveKerrTerms.groups`): the rule, - the general modal region it acts in (``None`` on every other route), and the - composition it is built from -- each species' label and its density profile's - identity. Empty for the polynomial and for a pure gas. + - Returns: one per group of several species under the symmetric evaluator + (:meth:`~solprop.translate.responses.DispersiveKerrTerms.groups`): the rule, the + general modal region it acts in (``None`` on every other route), and the + composition it is built from -- each species' label and its density profile's + identity. Empty for the polynomial and for a pure gas. """ if leaf is None or leaf.evaluator != "symmetric": return [] @@ -740,22 +591,13 @@ def _dispersive_policy( ) -> dict[str, object]: """Return the dispersive electronic law a run's entries record, for its key. - Parameters - ---------- - entries : sequence of Mapping - :func:`_dispersive_entries` of the run. - mixtures : sequence of Mapping, optional - The symmetric evaluator's mixture filters (:func:`_mixture_filters`), written - under ``mixture_filters`` only when there is one, so that a pure gas's record - is what it was. - - Returns - ------- - dict - The law, its evaluator, sector and tensor approximation, the per-leg bound - rule, each population's support digest and regions, and each mixture filter - with the composition it is built from: what the resume key covers, so that a - changed model, support record, band or composition is refused. + - ``mixtures``: the symmetric evaluator's mixture filters + (:func:`_mixture_filters`), written under ``mixture_filters`` only when there is + one, so that a pure gas's record is what it was. + - Returns: the law, its evaluator, sector and tensor approximation, the per-leg + bound rule, each population's support digest and regions, and each mixture filter + with the composition it is built from: what the resume key covers, so that a + changed model, support record, band or composition is refused. """ evaluator = str(entries[0]["evaluator"]) policy: dict[str, object] = { @@ -779,20 +621,7 @@ def _dispersive_policy( def _rate_policies(found: Sequence[object]) -> list[dict[str, object]]: - """Return the policies of a run's optmat ionisation rates. - - Parameters - ---------- - found : sequence of object - The run's objects. - - Returns - ------- - list of dict - :meth:`solprop.nonlinear.optmat_rates.OptmatRate.manifest_entry` of each - rate, each once, in canonical order: the threshold kind, the reference field, - the constant energy per event and its bound, the maximum field. - """ + """Return the policies of a run's optmat ionisation rates.""" entries = [ value.manifest_entry() for value in found if isinstance(value, OptmatRate) ] @@ -801,18 +630,7 @@ def _rate_policies(found: Sequence[object]) -> list[dict[str, object]]: def _canonical_entry(entry: Mapping[str, object]) -> str: - """Return an entry's canonical JSON, for ordering and deduplication. - - Parameters - ---------- - entry : collections.abc.Mapping - Plain values. - - Returns - ------- - str - Sorted-key, compact JSON. - """ + """Return an entry's canonical JSON, for ordering and deduplication.""" return json.dumps(entry, sort_keys=True, separators=(",", ":"), default=str) diff --git a/src/solprop/driver/mixtures.py b/src/solprop/driver/mixtures.py index 1720d3e99..6d7a36d2d 100644 --- a/src/solprop/driver/mixtures.py +++ b/src/solprop/driver/mixtures.py @@ -295,18 +295,8 @@ def _with_initial_populations( ) -> Mixture: """Retain unspecified populations when overriding initial ionised fractions. - Parameters - ---------- - mixture : Mixture - Existing populations. - preionisation : float, ZProfile, Mapping or None - Global or labelled initial fractions in [0, 1], each a number or a path along - z in m. - - Returns - ------- - Mixture - Updated populations with every unspecified fraction retained. + - ``preionisation``: global or labelled initial fractions in [0, 1], each a number + or a path along z in m. """ if preionisation is None: return mixture @@ -374,15 +364,8 @@ class _PartialPressureSum: def __call__(self, z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: """Sum the EOS-inverted partial pressures at the requested positions. - Parameters - ---------- - z : numpy.ndarray - Positions in m, shape (n_z,). - - Returns - ------- - numpy.ndarray - Total pressure diagnostic in bar, shape (n_z,). + - ``z``: positions in m, shape (n_z,). + - Returns: total pressure diagnostic in bar, shape (n_z,). """ temperature = self.mixture.temperature kelvin = ( diff --git a/src/solprop/driver/plane_view.py b/src/solprop/driver/plane_view.py index 817fc2224..b77599951 100644 --- a/src/solprop/driver/plane_view.py +++ b/src/solprop/driver/plane_view.py @@ -94,40 +94,23 @@ def __init__( self._open = True @property - def basis(self) -> BasisCapability: - """Return the run's basis. + def basis(self) -> BasisCapability: # numpydoc ignore=RT01 + """The run's basis. - Returns - ------- - {'modal', 'general_modal', 'spatial'} - The basis capability its record declares. + The basis capability its record declares. """ return capability_of(self._metadata) @property - def quantities(self) -> dict[str, Quantity]: - """Return what the plane's derived quantities are. + def quantities(self) -> dict[str, Quantity]: # numpydoc ignore=RT01 + """What the plane's derived quantities are. - Returns - ------- - dict - Whether its power, energy, density and field are physical fluxes. + Whether its power, energy, density and field are physical fluxes. """ return _quantities(self.basis) def _reader(self) -> MemoryOutput: - """Return the plane as a one-plane output, fetching its field once if needed. - - Returns - ------- - solprop.output.memory.MemoryOutput - The output the processing functions read. - - Raises - ------ - DriverError - If the view is used after its call returned. - """ + """Return the plane as a one-plane output, fetching its field once if needed.""" if not self._open: raise DriverError(_EXPIRED) if self._output is None: diff --git a/src/solprop/driver/run.py b/src/solprop/driver/run.py index 7a0bfbb65..f7cefdbb7 100644 --- a/src/solprop/driver/run.py +++ b/src/solprop/driver/run.py @@ -132,28 +132,6 @@ def _history_of(history: BackendHistory) -> StepHistory: row. The conversion is made here: the start is the end less the length, to round-off, and the per-attempt times are the differences of the clock, the first from zero. - - Parameters - ---------- - history : solprop.backends.protocol.BackendHistory - What the stepper did since the previous observation. - - Returns - ------- - solprop.output.schema.StepHistory - The same attempts, in the columns the output layer stores. - - Examples - -------- - >>> import numpy as np - >>> from solprop.backends.protocol import BackendHistory - >>> from solprop.driver.run import _history_of - >>> history = BackendHistory(np.array([0.5, 1.0]), np.array([0.5, 0.5]), - ... np.array([0.4, 0.5]), np.array([True, True]), - ... np.array([0.25, 0.75])) - >>> stored = _history_of(history) - >>> stored.z.tolist(), stored.wall_time.tolist() - ([0.0, 0.5], [0.25, 0.5]) """ return StepHistory( z=history.z - history.dz, @@ -249,13 +227,10 @@ def __init__(self, session: RunSession) -> None: ) @property - def in_flight(self) -> tuple[float, float] | None: - """Return the in-place step a failure left in flight, or ``None``. + def in_flight(self) -> tuple[float, float] | None: # numpydoc ignore=RT01 + """In-place step a failure left in flight, or ``None``. - Returns - ------- - tuple or None - Where the step started and its length, m, as the session reports it. + Where the step started and its length, m, as the session reports it. """ return getattr(self._session, "in_flight", None) @@ -577,19 +552,7 @@ def _restored( (another engine's rows, another build's shapes) starts afresh, which is warned about once and recorded as a reset (SEP 47 section 5.5). - Parameters - ---------- - march : solprop.output.schema.MarchSnapshot - The history the start carried. - restoration : collections.abc.Mapping - The record of its restoration, as the caller expects it. - refusal : str or None - Why the session could not install it, or ``None`` when it did. - - Returns - ------- - collections.abc.Mapping - The record of what happened. + - ``refusal``: why the session could not install it, or ``None`` when it did. """ if refusal is None: logger.info( @@ -619,26 +582,10 @@ def _announce( # noqa: PLR0917 - called positionally ) -> None: """Show the run's summary through the reporter and record it on a plane store. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The translated problem. - metadata : solprop.output.schema.RunMetadata - The run's record. - backend : solprop.backends.protocol.Backend - The backend about to step it. - output : solprop.output.schema.Output - The open handler. - reporter : solprop.log.ProgressReporter - Where progress goes; only a :class:`solprop.log.SummarisingReporter` is shown - the summary. - start : tuple of int and float - The first save plane beyond the loaded state, and the position the run starts - from, m. - request : solprop.translate.run_request.RunRequest - The run's request. - boundary_check : solprop.monitor.BoundaryCheck or None, optional - The thresholds the window's edges are watched with, or ``None``. + - ``reporter``: where progress goes; only a :class:`solprop.log.SummarisingReporter` + is shown the summary. + - ``start``: the first save plane beyond the loaded state, and the position the run + starts from, m. """ start_plane, z_start = start summary = run_summary( @@ -784,34 +731,17 @@ def _retain( Called once the output is open, so that a file being continued compares the rule with the one it holds. - Parameters - ---------- - output : solprop.output.schema.Output - The run's handler. - retention : solprop.output.schema.FieldRetention - The run's resolved rule. - field_shape : tuple of int - ``(n_modes, n_pol, n_omega)`` of the run's state. + - ``field_shape``: ``(n_modes, n_pol, n_omega)`` of the run's state. """ if isinstance(output, RetainingOutput): output.retain(retention, field_shape) def _check_retainable(output: Output, retention: FieldRetention) -> None: - """Refuse a field-free rule for an output that cannot hold one, before opening it. - - Parameters - ---------- - output : solprop.output.schema.Output - The run's handler. - retention : solprop.output.schema.FieldRetention - The run's resolved rule. + """Refuse a field-free rule for an output that cannot hold one. - Raises - ------ - DriverError - If the rule keeps fewer than every field and the output has no ``retain`` - (:class:`~solprop.output.schema.RetainingOutput`). + Checked before the output is opened. An output can hold one when it has ``retain`` + (:class:`~solprop.output.schema.RetainingOutput`). """ if retention.fields != "all" and not isinstance(output, RetainingOutput): msg = ( @@ -831,22 +761,12 @@ def _resume_position( ) -> tuple[float, bool]: """Resolve the actual checkpoint position and any scheduled observation owed. - Parameters - ---------- - output : Output - Open handler containing the regular saved observations. - checkpoint_output : CheckpointOutput or None - Checkpoint-capable handler, if available, holding any terminal state. - problem : PropagationProblem - Translated problem with the absolute launch position, m. - request : solprop.translate.run_request.RunRequest - The run's request, with the scheduled save positions, m. - - Returns - ------- - tuple of float and bool - Actual restart position in m and whether its scheduled observation still - needs saving because failure occurred before the regular save completed. + - ``checkpoint_output``: Checkpoint-capable handler, if available, holding any + terminal state. + - ``problem``: translated problem with the absolute launch position, m. + - ``request``: the run's request, with the scheduled save positions, m. + - Returns: actual restart position in m and whether its scheduled observation still + needs saving because failure occurred before the regular save completed. """ reached = float(output["z"][-1]) if output.n_saved else problem.initial.z if ( @@ -872,18 +792,6 @@ def _refuse_unread_collar( transverse edge reads; a backend that does not read it refuses the collar by name before the first step (SEP 40 section 6). Its in-stage rate is not the reason: the backends compute it alike. - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The run's problem. - backend : solprop.backends.protocol.Backend - The selected backend. - - Raises - ------ - BackendError - If the problem has a requested collar and the backend does not read the edge. """ design = transverse_of(problem.route) if design is None or not design.has_collar: @@ -903,23 +811,12 @@ def _earlier_execution( ) -> tuple[bool, Mapping[str, object] | None, tuple[tuple[float, float], ...]]: """Return whether this execution resumes another, and the monitor record to go on. - Parameters - ---------- - output : Output - Open handler, before this execution saves or records anything. - checkpoint_output : CheckpointOutput or None - Checkpoint-capable handler, if available, holding the earlier outcome. - - Returns - ------- - resumed : bool - Whether the handler holds an earlier execution's planes or outcome. - record : collections.abc.Mapping or None - The latest earlier execution's boundary-monitor record, searching back past - executions that kept none, or ``None`` when none did. - unwatched : tuple of tuple of float - The stretches, ``(start, end)`` in m, that executions after that record ran - without a monitor; from the launch when no execution kept one. + - ``checkpoint_output``: Checkpoint-capable handler, if available, holding the + earlier outcome. + - Returns ``record``: the latest earlier execution's boundary-monitor record, + searching back past executions that kept none, or ``None`` when none did. + - Returns ``unwatched``: the stretches, ``(start, end)`` in m, that executions after + that record ran without a monitor; from the launch when no execution kept one. """ outcome = None if checkpoint_output is None else checkpoint_output.outcome resumed = output.n_saved > 0 or outcome is not None @@ -968,21 +865,8 @@ def _open_run( ) -> CheckpointOutput | None: """Attach actual runtime facts outside the scientific resume identity. - Parameters - ---------- - output : Output - Handler to open without changing its scientific identity. - metadata : RunMetadata - Scientific description of this run. - backend : Backend - Prepared optical backend supplying actual execution facts. - binding : TDSEBinding or None - Optional atomic service supplying execution provenance. - - Returns - ------- - CheckpointOutput or None - The handler's checkpoint interface, or ``None`` for a handler without one. + - Returns: the handler's checkpoint interface, or ``None`` for a handler without + one. """ # A session that holds its monitor from the start (the native session) reports # the launch's zeros here. The execution record carries the monitor instead @@ -1056,16 +940,8 @@ def _method_history( ) -> Literal["adams", "rk", "low_storage"]: """Return which kind of method history a run's march keeps. - Parameters - ---------- - request : solprop.translate.run_request.RunRequest - The run's request. - - Returns - ------- - {'adams', 'rk', 'low_storage'} - An Adams history a resume can continue; Runge-Kutta controller memory (the - proposal and the norm memory), which a resume resets; or no history at all. + An Adams history a resume can continue; Runge-Kutta controller memory (the proposal + and the norm memory), which a resume resets; or none at all. """ integrator = request.integration.method if isinstance(integrator, Multistep): @@ -1113,20 +989,10 @@ class _MarchKeeper: retained, omitted or is not applicable, and why (SEP 47 section 5.5); the run's execution record carries the latest decision (:meth:`record`). - Parameters - ---------- - output : solprop.output.schema.Output - The run's handler; only one with ``save_march`` keeps a snapshot. - backend : solprop.backends.protocol.HostStepping or SessionFacts - The reference's driver, or a session's facts, whose march has a snapshot to - keep or none. - policy : bool or None - The run's ``resume_history``. - method : {'adams', 'rk', 'low_storage'}, optional - The march's history (:func:`_method_history`). - retention : collections.abc.Mapping or None, optional - The decision already recorded at the position the run starts from, carried - into this execution's record until it persists a position of its own. + - ``output``: the run's handler; only one with ``save_march`` keeps a snapshot. + - ``retention``: the decision already recorded at the position the run starts + from, carried into this execution's record until it persists a position of its + own. """ def __init__( @@ -1156,47 +1022,29 @@ def __init__( def record(self) -> dict[str, object] | None: """Return the latest retention decision, for the execution record. - Returns - ------- - dict or None - ``"state"`` (``"retained"``, ``"omitted"`` or ``"not_applicable"``), - ``"reason"`` and ``"z"``, m, with ``"nbytes"`` where the size was measured; - ``None`` before anything was persisted. + - Returns: ``"state"`` (``"retained"``, ``"omitted"`` or ``"not_applicable"``), + ``"reason"`` and ``"z"``, m, with ``"nbytes"`` where the size was measured; + ``None`` before anything was persisted. """ return None if self._decision is None else dict(self._decision) def _decide(self, z: float, state: str, reason: str, **sizes: int) -> None: """Record a retention decision at `z`. - Parameters - ---------- - z : float - The persisted position, m. - state : str - ``"retained"``, ``"omitted"`` or ``"not_applicable"``. - reason : str - Why. - **sizes : int - ``nbytes``, the history's size where it was measured, and ``limit``, the - automatic limit it was held to, bytes. + - ``z``: the persisted position, m. + - ``state``: ``"retained"``, ``"omitted"`` or ``"not_applicable"``. + - ``**sizes``: ``nbytes``, the history's size where it was measured, and + ``limit``, the automatic limit it was held to, bytes. """ self._decision = {"state": state, "reason": reason, "z": z, **sizes} def keep(self, z: float, *, required: bool = False) -> None: # noqa: PLR0911, PLR0912 - one return per decision """Keep the march's state if it stands at `z`, the position just saved. - Parameters - ---------- - z : float - The saved plane's or the checkpoint's position, m. - required : bool, optional - Require the full Adams state for an immutable snapshot, ignoring the - automatic retention-size cutoff. - - Raises - ------ - OutputError - If required history cannot be persisted at this endpoint. + - ``z``: the saved plane's or the checkpoint's position, m. + - ``required``: an immutable snapshot needs the full Adams state, so the + automatic retention-size cutoff is ignored and a history that cannot be + persisted here is refused. """ if required and self._kept_z == z: return @@ -1275,10 +1123,7 @@ def keep(self, z: float, *, required: bool = False) -> None: # noqa: PLR0911, P def _omit_unavailable(self, z: float) -> None: """Record history that could not be had at `z`, naming an engine's refusal. - Parameters - ---------- - z : float - The persisted position, m. + - ``z``: the persisted position, m. """ refused = self._refusal() is not None self._decide( @@ -1308,25 +1153,9 @@ def _record_abandoned_step( plane: a resume continues from one that kept its field, to rounding, with ``step_planes`` bounding what is lost, and refuses one that did not. - Parameters - ---------- - backend : Backend or SessionFacts - The backend, refusing its state, or a poisoned session's facts. - output : CheckpointOutput - Handler recording the outcome. - binding : TDSEBinding or None - Optional atomic runtime, for the execution record. - failure : _FailedObservation - The original exception and the last saved position. - step : tuple of float - Where the abandoned step started and its length, m. - restart : collections.abc.Mapping or None, optional - The execution's restart record (:func:`_record_execution`). - - Returns - ------- - float or None - The position of the checkpoint fallen back to, m, or ``None`` without one. + - ``step``: where the abandoned step started and its length, m. + - Returns: the position of the checkpoint fallen back to, m, or ``None`` without + one. """ z, dz = step fallback = output.terminal_checkpoint @@ -1416,23 +1245,9 @@ def _record_execution( has spent so far (:func:`solprop._host.usage`): written at every save plane, it keeps the wall time and peak memory of a run that is killed between them. - Parameters - ---------- - output : CheckpointOutput or None - Optional handler for completion and failure records. - backend : Backend - Optical backend supplying cumulative logical calls and execution facts. - binding : TDSEBinding or None - Optional atomic runtime supplying attempted native calls. - status : _Status - Requested status, absolute position in m and any error message. - clock : solprop._host.RunClock - When the run started. - checkpoint : TerminalCheckpoint or None, optional - Accepted field to preserve separately from scheduled observations. - restart : collections.abc.Mapping or None, optional - The execution's restart record (:data:`RESTART_RECORD`); ``None`` or an empty - one records none. + - ``status``: requested status, absolute position in m and any error message. + - ``restart``: the execution's restart record (:data:`RESTART_RECORD`); ``None`` or + an empty one records none. """ if output is None: return @@ -1481,17 +1296,9 @@ def _replayed_steps(request: RunRequest, start_plane: int) -> int | None: this loop, and a total that is wrong by the steps already taken is worse than falling back to the rate of z. - Parameters - ---------- - request : solprop.translate.run_request.RunRequest - The run's request, whose ``replay`` is the schedule if there is one. - start_plane : int - Index of the first save plane to propagate to; above zero the run is a resume. - - Returns - ------- - int or None - The schedule's whole step count, or ``None`` for an adaptive or resumed run. + - ``request``: the run's request, whose ``replay`` is the schedule if there is one. + - Returns: the schedule's whole step count, or ``None`` for an adaptive or resumed + run. """ if request.replay is None or start_plane > 0: return None @@ -1519,18 +1326,6 @@ def _refuse_a_record_that_misnames_the_basis( ``HE_1m`` set the run always had, it builds no ``transform.modal_tables``, and the ordinary modal accessors stay admitted on it. Keying on the identity's presence alone would refuse that run here, for a basis disagreement that does not exist. - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem about to be run. - metadata : solprop.output.schema.RunMetadata - The record about to be written. - - Raises - ------ - DriverError - Naming which of the two says what. """ general = problem.transform.modal_tables is not None if general == (capability_of(metadata) == "general_modal"): @@ -1564,33 +1359,13 @@ def _refuse_unrunnable( ) -> None: """Refuse, by name, every run this loop cannot execute. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The problem about to be run. - request : solprop.translate.run_request.RunRequest - The request the backend was built for. - spec : solprop.stats.StatsSpec - The statistics asked for. A ``periodic`` subset is accepted -- the seam carries - it, and a name no backend can evaluate between planes was refused by - :class:`~solprop.stats.StatsSpec` when the spec was built -- but it must agree - with the request's own, because the channel is armed when the backend is built - and reads the request's spec, not this one. - start_plane : int - Index of the first save plane to propagate to. - n_planes : int - How many save planes the problem has. - metadata : solprop.output.schema.RunMetadata - The record about to be written, which must say the same thing about the - basis as the problem does. - - Raises - ------ - DriverError - Naming what cannot be done. - BackendError - Naming statistics the problem does not carry - (:func:`solprop.backends.session.statistics_unavailable`). + - ``spec``: the statistics asked for. A ``periodic`` subset is accepted -- the seam + carries it, and a name no backend can evaluate between planes was refused by + :class:`~solprop.stats.StatsSpec` when the spec was built -- but it must agree + with the request's own, because the channel is armed when the backend is built and + reads the request's spec, not this one. + - ``metadata``: the record about to be written, which must say the same thing about + the basis as the problem does. """ _refuse_a_record_that_misnames_the_basis(problem, metadata) channel = request.observation.stats diff --git a/src/solprop/driver/scaffold.py b/src/solprop/driver/scaffold.py index c0f0ab981..79de04647 100644 --- a/src/solprop/driver/scaffold.py +++ b/src/solprop/driver/scaffold.py @@ -101,20 +101,16 @@ def gnlse_source( to_field = sqrtw_to_vm(problem.aeff) n_t, n_to = grid.n_t, grid.n_to + # Returned to callers, so it keeps its unprefixed name (and ``__qualname__``); + # numpydoc then checks it as public, but it carries the short private form. def nonlinear( state: Complex[np.ndarray, " n_omega"], - ) -> Complex[np.ndarray, " n_omega"]: + ) -> Complex[np.ndarray, " n_omega"]: # numpydoc ignore=PR01,RT01 """Return the nonlinear term of the propagation equation for one spectrum. - Parameters - ---------- - state : numpy.ndarray - Stored spectrum, shape ``(n_omega,)``, in the square root of watts. - - Returns - ------- - numpy.ndarray - ``N[state]``, shape ``(n_omega,)``, in the square root of watts per metre. + - ``state``: stored spectrum, in the square root of watts. + - Returns: ``N[state]``, shape ``(n_omega,)``, in the square root of watts per + metre. """ fine = fourier.oversample(state, n_to, kind="envelope") envelope = fourier.inverse(fine, kind="envelope") * to_field diff --git a/src/solprop/driver/service.py b/src/solprop/driver/service.py index 44bc72b08..f6c105912 100644 --- a/src/solprop/driver/service.py +++ b/src/solprop/driver/service.py @@ -165,15 +165,7 @@ class ServiceContext: def _rows(rows: BackendRows) -> StepStats: """Return a session's periodic rows in the form an output handler stores. - Parameters - ---------- - rows : solprop.backends.protocol.StepStats - The rows, with a leading row axis and no batch axis. - - Returns - ------- - solprop.output.schema.StepStats - The same rows. + - ``rows``: the rows, with a leading row axis and no batch axis. """ return StepStats(z=rows.z, step=rows.step, values=dict(rows.values)) @@ -194,17 +186,7 @@ def _rows(rows: BackendRows) -> StepStats: def _restart(base: Mapping[str, object], keeper: _MarchKeeper) -> dict[str, object]: """Return an execution's restart record with the latest retention decision. - Parameters - ---------- - base : collections.abc.Mapping - What the execution restored, fixed when it started. - keeper : solprop.driver.run._MarchKeeper - The method-history keeper, holding the latest retention decision. - - Returns - ------- - dict - The record (:data:`solprop.driver.run.RESTART_RECORD`). + - ``base``: what the execution restored, fixed when it started. """ retention = keeper.record() return dict(base) if retention is None else {"retention": retention, **base} @@ -221,12 +203,7 @@ class _Exports: (:func:`solprop.fourier.spectrum_to_internal`), with no second export. An output that is not a plane store is given the consumer field itself. - Parameters - ---------- - session : solprop.backends.session.RunSession - The session whose tickets are exported. - kind : {'field', 'envelope'} - The grid's spectral layout. + - ``kind``: the grid's spectral layout. """ def __init__(self, session: RunSession, kind: GridKind) -> None: @@ -240,13 +217,7 @@ def __init__(self, session: RunSession, kind: GridKind) -> None: self._phased = False def _hold(self, ticket: ExportTicket) -> None: - """Forget what another ticket exported. - - Parameters - ---------- - ticket : solprop.backends.events.ExportTicket - The ticket now asked for. - """ + """Forget what another ticket exported.""" if self._ticket != ticket: self._ticket = ticket self._field = self._stored = self._phase = None @@ -255,15 +226,7 @@ def _hold(self, ticket: ExportTicket) -> None: def field(self, ticket: ExportTicket) -> np.ndarray: """Return the consumer field for a ticket, exporting it once. - Parameters - ---------- - ticket : solprop.backends.events.ExportTicket - A pending ticket. - - Returns - ------- - numpy.ndarray - Read-only output amplitudes in the internal convention. + - Returns: Read-only output amplitudes in the internal convention. """ self._hold(ticket) if self._field is None: @@ -283,18 +246,10 @@ def stored(self, ticket: ExportTicket, out: np.ndarray | None) -> np.ndarray: export is the ticket's first; any consumer after it is given the consumer field from it (:meth:`field`). - Parameters - ---------- - ticket : solprop.backends.events.ExportTicket - A pending ticket. - out : numpy.ndarray or None - The plane store's room (``PlaneStore.field_destination``), or ``None`` - for a new array. - - Returns - ------- - numpy.ndarray - Read-only output amplitudes in the optics convention: `out` when given. + - ``out``: the plane store's room (``PlaneStore.field_destination``), or + ``None`` for a new array. + - Returns: Read-only output amplitudes in the optics convention: `out` when + given. """ self._hold(ticket) assert self._field is None and self._stored is None, "a plane's first export" @@ -317,15 +272,7 @@ def release(self) -> None: def phase(self, ticket: ExportTicket) -> np.ndarray | None: """Return the noise phase for a ticket, read once. - Parameters - ---------- - ticket : solprop.backends.events.ExportTicket - A pending ticket. - - Returns - ------- - numpy.ndarray or None - The phase, read-only, or ``None`` without a noise model. + - Returns: the phase, read-only, or ``None`` without a noise model. """ self._hold(ticket) if not self._phased: @@ -347,12 +294,6 @@ class _Chunks: (:mod:`solprop.backends.events`); this is where they are assembled. An observation takes them all as its plane's history and starts afresh; a running checkpoint includes them without consuming them; a terminal checkpoint includes them. - - Parameters - ---------- - step : callable - The progress reporter's per-attempt call, made with each attempt's end, - length and acceptance as its chunk arrives. """ def __init__(self, step: Callable[[float, float, bool], None]) -> None: @@ -372,16 +313,9 @@ def add( Every attempt arrives exactly once, so the reporter hears each once, in order, at the cadence of the session's yields rather than from inside its march. - Parameters - ---------- - history : solprop.backends.protocol.BackendHistory or None - Its attempts. - rows : solprop.backends.protocol.StepStats or None - Its periodic rows, without a batch axis. - report : bool, optional - Whether the reporter hears the attempts; ``False`` for the attempts no - event delivered, which a failure's checkpoint holds and no reporter call - may lose. + - ``rows``: its periodic rows, without a batch axis. + - ``report``: ``False`` for the attempts no event delivered, which a failure's + checkpoint holds and no reporter call may lose. """ self._chunks.append((history, rows)) if report and history is not None: @@ -396,20 +330,14 @@ def add( def joined(self) -> tuple[BackendHistory, BackendRows]: """Return every chunk held, assembled, without consuming them. - Returns - ------- - tuple - The attempts and rows since the last saved plane. + - Returns: the attempts and rows since the last saved plane. """ return join_attempts(self._chunks) def take(self) -> tuple[BackendHistory, BackendRows]: """Return every chunk held, assembled, and start afresh. - Returns - ------- - tuple - The attempts and rows since the last saved plane. + - Returns: the attempts and rows since the last saved plane. """ joined = self.joined() self._chunks = [] @@ -424,22 +352,11 @@ def _plane( ) -> SavedPlane: """Build the plane an output stores from an observation. - Parameters - ---------- - event : solprop.backends.events.ObservationReady - The observation. - attempts : tuple - Its history and rows, assembled from the chunks since the previous plane. - field : numpy.ndarray or None - Its field in the internal convention, or ``None``: a field-free plane, or one - whose field is exported straight into a plane store. - phase : numpy.ndarray or None - Its noise phase, where it keeps its field and a noise model runs. - - Returns - ------- - solprop.output.schema.SavedPlane - The plane. + - ``attempts``: its history and rows, assembled from the chunks since the previous + plane. + - ``field``: its field in the internal convention, or ``None``: a field-free plane, + or one whose field is exported straight into a plane store. + - ``phase``: its noise phase, where it keeps its field and a noise model runs. """ return SavedPlane( z=event.z, @@ -467,26 +384,9 @@ def _prepare_plane( has one (SEP 47 section 5.4), and the plane carries none; any other output is given the consumer field on the plane (:func:`_save_plane`). - Parameters - ---------- - event : solprop.backends.events.ObservationReady - The observation. - output : solprop.output.schema.Output - The run's output. - exports : _Exports - The pending ticket's exports. - attempts : tuple - Its history and rows. - shape : tuple of int - ``(n_modes, n_pol, n_omega)`` of the field. - keep : bool - Whether the plane keeps its field. - - Returns - ------- - tuple - The plane and, where its field was exported into a plane store, the stored - field (``None`` otherwise). + - ``shape``: ``(n_modes, n_pol, n_omega)`` of the field. + - Returns: the plane and, where its field was exported into a plane store, the + stored field (``None`` otherwise). """ ticket = event.ticket phase = exports.phase(ticket) if keep else None @@ -501,17 +401,7 @@ def _prepare_plane( def _save_plane(output: Output, plane: SavedPlane, stored: StoredField | None) -> None: - """Hand a prepared plane to the output (:func:`_prepare_plane`). - - Parameters - ---------- - output : solprop.output.schema.Output - The run's output. - plane : solprop.output.schema.SavedPlane - The plane. - stored : solprop.output.schema.StoredField or None - Its field exported into the plane store's stored form, or ``None``. - """ + """Hand a prepared plane to the output (:func:`_prepare_plane`).""" if stored is None: output.save(plane) else: @@ -606,19 +496,13 @@ def serve( # noqa: PLR0912, PLR0915 - the commit sequence of SEP 47 section 5.3 shape = tuple(context.problem.shape) current: list[ObservationReady | None] = [None] - def fetch() -> np.ndarray: - """Return the pending observation's field, for the plane observer. - - Returns - ------- - numpy.ndarray - The field of the observation the observer is shown. - """ + def _fetch() -> np.ndarray: + """Return the pending observation's field, for the plane observer.""" event = current[0] assert event is not None # the observer is shown a pending plane only return exports.field(event.ticket) - watch = PlaneWatch(reporter, context.metadata, fetch, clock.observer) + watch = PlaneWatch(reporter, context.metadata, _fetch, clock.observer) pending: SavedPlane | None = None pending_save_count = output.n_saved pending_ticket: ExportTicket | None = None @@ -795,12 +679,7 @@ def fetch() -> np.ndarray: def _warn_coarse(event: ObservationReady, planes: np.ndarray) -> None: """Log a plane whose propagation did not meet its tolerance. - Parameters - ---------- - event : solprop.backends.events.ObservationReady - The observation. - planes : numpy.ndarray - The requested save planes, m. + - ``planes``: the requested save planes, m. """ summary = event.summary if summary.met_tolerance: @@ -837,31 +716,8 @@ def _service_snapshot( # noqa: PLR0917 - the snapshot transaction's context field and the attempts and rows since the last plane are written as a running checkpoint without being consumed (SEP 47 section 5.5). - Parameters - ---------- - event : solprop.backends.events.ServiceRequired - The snapshot request. - output : solprop.output.hdf5.HDF5Output - The live writer. - checkpoint_output : solprop.output.schema.CheckpointOutput or None - The writer as a checkpoint store. - backend : object - The driver, for the execution record. - keeper : _MarchKeeper - Keeps the method history beside the checkpoint. - exports : _Exports - The endpoint's field. - context : ServiceContext - The run's context. - attempts : tuple - The attempts and rows since the last plane, the snapshot's own chunk included: - the running checkpoint's, which does not consume them. - clock : solprop._host.RunClock - When the run started. - adams : bool - Whether the run's method keeps an Adams history, which a snapshot requires. - base : collections.abc.Mapping - What the execution restored, for its restart record. + - ``attempts``: the attempts and rows since the last plane, the snapshot's own chunk + included: the running checkpoint's, which does not consume them. """ snapshots = context.snapshots assert snapshots is not None @@ -929,36 +785,12 @@ def _persist_failure( # noqa: PLR0917 - the failure's whole context ) -> float | None: """Checkpoint the accepted state without replacing the original exception. - Parameters - ---------- - session : solprop.backends.session.RunSession - The session, its last batch still pending. - output : solprop.output.schema.CheckpointOutput or None - Handler supporting terminal checkpoints, when available. - context : ServiceContext - The run's context. - keeper : _MarchKeeper - Keeps the method history beside the checkpoint. - exports : _Exports - Shares a field already exported for the pending plane. - ticket : solprop.backends.events.ExportTicket or None - The committed field to checkpoint, or ``None`` when the state is poisoned or - the failure arose outside the session. - rows : tuple - The attempts and periodic rows since the last saved plane, assembled for the - terminal event or the failed plane, or ``None`` for each. - chunks : _Chunks - The chunks delivered since the last saved plane, for a failure outside the - session, whose own undelivered attempts are joined to them. - base : collections.abc.Mapping - What the execution restored, for its restart record. - failure : solprop.driver.run._FailedObservation - The primary failure and the plane in hand. - - Returns - ------- - float or None - The checkpointed position, m, when one was written. + - ``output``: handler supporting terminal checkpoints, when available. + - ``ticket``: the committed field to checkpoint, or ``None`` when the state is + poisoned or the failure arose outside the session. + - ``rows``: the attempts and periodic rows since the last saved plane, assembled for + the terminal event or the failed plane, or ``None`` for each. + - Returns: the checkpointed position, m, when one was written. """ if output is None: return None @@ -1075,16 +907,8 @@ def _fallback_ticket(session: RunSession) -> ExportTicket | None: nothing is pending, the session issues a failed terminal batch whose ticket exports the committed state (``HostSession.pending_ticket``). - Parameters - ---------- - session : solprop.backends.session.RunSession - The session. - - Returns - ------- - solprop.backends.events.ExportTicket or None - A ticket for the committed state, or ``None`` when the session is closed or - its state was left in flight. + - Returns: a ticket for the committed state, or ``None`` when the session is closed + or its state was left in flight. """ # A session that can end itself at its committed state for the checkpoint says # so (`NativeSession.failure_ticket`); the reference session's pending ticket @@ -1100,16 +924,6 @@ def _facts(session: RunSession) -> HostStepping | SessionFacts: The reference session's driver, or a session's own facts (:class:`~solprop.driver.run.SessionFacts`). - - Parameters - ---------- - session : solprop.backends.session.RunSession - The session. - - Returns - ------- - Any - The driver, or the facts. """ if isinstance(session, HostSession): return session.driver diff --git a/src/solprop/driver/simulation.py b/src/solprop/driver/simulation.py index 84b5691ae..391ddfedc 100644 --- a/src/solprop/driver/simulation.py +++ b/src/solprop/driver/simulation.py @@ -468,20 +468,8 @@ def _held_budget(value: SupportsIndex | None) -> int | None: budget that is not one is refused when the settings are made, rather than when the backend is built after the output has opened. - Parameters - ---------- - value : int or None - The budget given, bytes. - - Returns - ------- - int or None - The budget, or ``None`` for the backend's automatic choice. - - Raises - ------ - solprop.exceptions.DriverError - If the budget is not a positive integer. + - ``value``: the budget given, bytes. + - Returns: the budget, or ``None`` for the backend's automatic choice. """ if value is None: return None @@ -545,20 +533,7 @@ class _AutoBackend(str): precision: str def __new__(cls, name: str, precision: str) -> "_AutoBackend": - """Return the name, marked with the precision it was resolved under. - - Parameters - ---------- - name : str - The concrete backend name. - precision : {'double', 'single'} - The precision ``"auto"`` was resolved for. - - Returns - ------- - _AutoBackend - The marked name. - """ + """Return the name, marked with the precision it was resolved under.""" marked = super().__new__(cls, name) marked.precision = precision return marked @@ -566,10 +541,7 @@ def __new__(cls, name: str, precision: str) -> "_AutoBackend": def __reduce__(self) -> tuple[object, tuple[str, str]]: """Pickle the name with its precision, for a scan worker. - Returns - ------- - tuple - The class and its arguments. + - Returns: the class and its arguments. """ return (_AutoBackend, (str(self), self.precision)) @@ -893,13 +865,7 @@ class SimulationSettings(Displayed): controller_defaulted = False def _planes_text(self) -> str: - """Return the save planes summarised, for printing. - - Returns - ------- - str - How many planes there are and the positions they span. - """ + """Return the save planes summarised, for printing.""" planes = np.asarray(self.save_planes, dtype=float) return summarise(planes, "m").replace("values", "planes") @@ -931,16 +897,8 @@ def __repr__(self) -> str: def _default_text(self, text: str) -> str: """Mark the integrator's name when it is the route's default. - Parameters - ---------- - text : str - The integrator's name. - - Returns - ------- - str - `text`, then ``"(default)"`` or ``"(gnlse default)"`` where no integrator - was named. + - Returns: `text`, then ``"(default)"`` or ``"(gnlse default)"`` where no + integrator was named. """ if self.integrator_named: return text @@ -949,10 +907,7 @@ def _default_text(self, text: str) -> str: def _describe(self) -> tuple[str, list[tuple[str, object]]]: """Return the title and rows of the printed block. - Returns - ------- - tuple - The title and ``(key, value)`` rows. + - Returns: the title and ``(key, value)`` rows. """ schedule = "adaptive" if self.schedule is None else type(self.schedule).__name__ return "SimulationSettings", [ @@ -1087,11 +1042,6 @@ def _resolve_defaults(self, precision: Precision) -> None: :data:`~solprop.driver.defaults.ROUTE_DEFAULTS` when not given. A default carried by a copy is resolved again, so it follows the copy's precision, route and planes; a value the caller gave is kept. - - Parameters - ---------- - precision : solprop.translate.precision.Precision - The settings' resolved precision. """ name = "single" if precision.single else "double" carried = ( @@ -1132,13 +1082,10 @@ def _resolve_defaults(self, precision: Precision) -> None: object.__setattr__(self, "absorber_scheme", _Defaulted(scheme)) @property - def absorber_scheme_named(self) -> bool: + def absorber_scheme_named(self) -> bool: # numpydoc ignore=RT01 """Whether the caller named the collar scheme, rather than taking the route's. - Returns - ------- - bool - False when :attr:`absorber_scheme` is the route's default. + False when :attr:`absorber_scheme` is the route's default. """ return not isinstance(self.absorber_scheme, _Defaulted) @@ -1196,20 +1143,6 @@ def _admit_tdse_execution(settings: SimulationSettings, precision: Precision) -> Each refusal names the setting at fault, in the order a caller would fix them: the precision, then the backend, then the integrator. - - Parameters - ---------- - settings : SimulationSettings - The settings being checked, whose ``tdse_execution`` is given. - precision : solprop.translate.precision.Precision - Their resolved precision. - - Raises - ------ - DriverError - If the resources are not a :class:`~solprop.nonlinear.tdse.TDSEExecution`, - or the run is in single precision, on a backend other than ``numpy_ref`` or - stepped by an integrator other than ``Multistep()``. """ if not isinstance(settings.tdse_execution, TDSEExecution): msg = ( @@ -1246,15 +1179,7 @@ def _admit_tdse_execution(settings: SimulationSettings, precision: Precision) -> def _quiet_collar(settings: SimulationSettings) -> bool: """Return whether a run's settings opt in to the quiet collar (ADR-057). - Parameters - ---------- - settings : SimulationSettings - The settings. - - Returns - ------- - bool - True when the boundary check asks for it. + - Returns: True when the boundary check asks for it. """ check = settings.boundary_check return check is not None and check.quiet_collar @@ -1265,25 +1190,13 @@ def _keyword_controller( ) -> tuple[PIController, PIController | None]: """Return a keyword interface's controller, and it again if its rtol is default. - Parameters - ---------- - route : str - The row of :data:`~solprop.driver.defaults.ROUTE_DEFAULTS` the interface - reads. - rtol : float or None - The caller's relative tolerance, which is never raised silently; ``None`` - takes the route's (:func:`~solprop.driver.defaults.default_rtol`). - atol : float - The caller's absolute tolerance. - precision : {'double', 'single'} - The run's precision. - - Returns - ------- - tuple - The controller, and the same object where `rtol` was the route's default so - that the settings record the default and a copy in the other precision - re-resolves it, else ``None``. + - ``route``: the row of :data:`~solprop.driver.defaults.ROUTE_DEFAULTS` the + interface reads. + - ``rtol``: the caller's relative tolerance, which is never raised silently; + ``None`` takes the route's (:func:`~solprop.driver.defaults.default_rtol`). + - Returns: the controller, and the same object where `rtol` was the route's default + so that the settings record the default and a copy in the other precision + re-resolves it, else ``None``. """ if rtol is not None: return PIController(rtol=rtol, atol=atol), None @@ -1292,23 +1205,7 @@ def _keyword_controller( def _precision(name: PrecisionName) -> Precision: - """Return the record of a public precision value, refusing an unknown one. - - Parameters - ---------- - name : {'double', 'single'} - The settings' value. - - Returns - ------- - solprop.translate.precision.Precision - The record. - - Raises - ------ - DriverError - If `name` is not a public value. - """ + """Return the record of a public precision value, refusing an unknown one.""" if name not in ("double", "single"): # pragma: no cover - refused by its annotation msg = f"precision must be 'double' or 'single', got {name!r}" raise DriverError(msg, parameter="precision") @@ -1322,21 +1219,6 @@ def _single_backend(backend: str) -> str: never resolves to a binary64 backend, and never to ``numpy_ref``, which runs single precision when it is named. An explicit name passes through to :func:`~solprop.translate.precision.admit_precision`. - - Parameters - ---------- - backend : str - The requested backend name. - - Returns - ------- - str - The concrete backend name. - - Raises - ------ - DriverError - If `backend` is ``"auto"``. """ if backend != "auto": return backend @@ -1353,16 +1235,6 @@ def _stepper_record(settings: StepperSettings) -> StepperRecord: The method is recorded in full, and the interval cap and start-up rule with the limits, so that a run and its resume hash the stepping they were taken with. - - Parameters - ---------- - settings : solprop.translate.problem.StepperSettings - The contract's stepper settings. - - Returns - ------- - solprop.output.schema.StepperRecord - The record. """ return StepperRecord.from_settings( settings.tableau, @@ -1399,27 +1271,6 @@ def _admit_low_storage( The march replays a prescribed schedule, lands on its edges and accepts every step, so it needs a schedule and has no interpolated planes. Every registered backend executes it (:data:`LOW_STORAGE_BACKENDS`), so the backend passes as asked. - - Parameters - ---------- - integrator : solprop.stepper.low_storage.LowStorageRungeKutta - The requested march. - backend : str - The requested backend name, or ``"auto"``. - settings : SimulationSettings - The settings being checked, for the schedule and the planes. - - Returns - ------- - str - The backend to run, as requested: every registered backend executes the - march (:data:`LOW_STORAGE_BACKENDS`), and ``"auto"`` resolves as for any - integrator. - - Raises - ------ - DriverError - Naming the setting and its remedy. """ if settings.schedule is None: msg = ( @@ -1448,18 +1299,6 @@ def _admit_integrator(integrator: Multistep, backend: str) -> None: Refused here, before translation, so that no backend -- in particular a separately built one reading the contract by name -- can silently step the problem with its Runge-Kutta march instead (SEP 13 section 6.1). - - Parameters - ---------- - integrator : solprop.stepper.multistep.Multistep - The requested method. - backend : str - The requested backend name. - - Raises - ------ - DriverError - Naming the conflict and the Runge-Kutta march every backend executes. """ if backend not in ("auto", "numpy_ref", "native"): msg = ( @@ -1708,17 +1547,8 @@ def _frame_follows_the_electronic_rule( response other than the electronic ones declares ``thg=True`` and no electronic one does. - Parameters - ---------- - responses : collections.abc.Sequence - The run's declared responses. - grid : solprop.grid.TimeGrid - Its temporal grid. - - Returns - ------- - bool - True when the earlier rule would have kept ``beta_0`` and this one removes it. + - Returns: True when the earlier rule would have kept ``beta_0`` and this one + removes it. """ return ( grid.kind == "envelope" @@ -1739,29 +1569,12 @@ def _plane_reference_record( ) -> dict[str, object]: """Return the plane-reference record of a run (SEP 31). - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The translated problem, whose frame tables are integrated. - request : solprop.translate.run_request.RunRequest - Its run's request, whose save planes are the planned planes. - grid : solprop.grid.TimeGrid - Its temporal grid. - configuration : collections.abc.Mapping - The run's configuration; a ``coupling`` entry supplies the inherited origin - and the parent's provenance, and a ``spatial_grid`` the transverse basis. - frame : solprop.linear.Frame or None - The run's frame, or ``None`` for the co-moving one. - modes : collections.abc.Sequence - The propagated modes of a modal run. - modal : collections.abc.Mapping or None - The general modal identity, for a general guide. - - Returns - ------- - dict - ``carrier``, ``origin``, ``parent``, ``frame`` and ``basis``, as - :attr:`solprop.output.schema.RunMetadata.reference` documents them. + - ``configuration``: the run's configuration; a ``coupling`` entry supplies the + inherited origin and the parent's provenance, and a ``spatial_grid`` the + transverse basis. + - ``frame``: the run's frame, or ``None`` for the co-moving one. + - Returns: ``carrier``, ``origin``, ``parent``, ``frame`` and ``basis``, as + :attr:`solprop.output.schema.RunMetadata.reference` documents them. """ # The co-moving frame keeps the carrier phase exactly when a harmonic term is # present, and then measures its delay from zero frequency (`frame_origin`). @@ -1800,26 +1613,10 @@ def _plane_bases( # noqa: PLR0917 - called positionally (:func:`solprop.coupling.plane.modal_basis_identity`), because a guide may vary, and once for a set whose every mode is ``z_constant``. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The translated problem: its launch position. - request : solprop.translate.run_request.RunRequest - Its run's request: its save planes. - grid : solprop.grid.TimeGrid - Its temporal grid. - configuration : collections.abc.Mapping - The run's configuration, carrying a spatial run's ``spatial_grid``. - modes : collections.abc.Sequence - The propagated modes of a modal run. - modal : collections.abc.Mapping or None - The general modal identity. - - Returns - ------- - dict - ``route``, ``z`` (the launch plane first, then the save planes, m) and - ``identity`` (one digest per plane, ``None`` on a planar run). + - ``configuration``: the run's configuration, carrying a spatial run's + ``spatial_grid``. + - Returns: ``route``, ``z`` (the launch plane first, then the save planes, m) and + ``identity`` (one digest per plane, ``None`` on a planar run). """ planes = [ float(problem.initial.z), @@ -1866,34 +1663,11 @@ def _one_material_context[**P, R](build: Callable[P, R]) -> Callable[P, R]: (:class:`solprop.physdata.selection.MaterialLog`), and its translation, record and run resume it, so that every range, status and dilute-scaling warning of its materials is raised once per run, however its work is spread in time. - - Parameters - ---------- - build : callable - A ``Simulation.from_*`` constructor. - - Returns - ------- - callable - The constructor, run inside the context. """ @functools.wraps(build) def inside(*args: P.args, **kwargs: P.kwargs) -> R: - """Build the simulation with the run's material context open. - - Parameters - ---------- - *args : object - The constructor's positional arguments. - **kwargs : object - Its keywords. - - Returns - ------- - object - What the constructor returns. - """ + """Build the simulation with the run's material context open.""" with _optmat.material_context(): return build(*args, **kwargs) @@ -1911,20 +1685,11 @@ def _about( none; the interface knows which keyword it passed, so it names that one here. The exception keeps its object, class, message and traceback. - Parameters - ---------- - parameter : str or tuple of str or None, optional - The path given to a :class:`~solprop.exceptions.SolpropError` that names no - parameter; ``None`` leaves it unnamed. - renamed : mapping of str to str or None, optional - The inner call's argument names that differ from the keywords that supplied - them, each mapped to its keyword: a path the error names already is renamed - through it (each path of a conflict), and kept where it has no entry. - - Yields - ------ - None - Control, for the block. + - ``parameter``: the path given to a :class:`~solprop.exceptions.SolpropError` that + names no parameter; ``None`` leaves it unnamed. + - ``renamed``: the inner call's argument names that differ from the keywords that + supplied them, each mapped to its keyword: a path the error names already is + renamed through it (each path of a conflict), and kept where it has no entry. """ try: yield @@ -1960,18 +1725,6 @@ def _warnings_about( ``"error"`` filter still raises and an ``"ignore"`` one still hides them: a warning of one of `categories` that names no parameter is given `parameter` first, and every other warning is issued as it was. - - Parameters - ---------- - parameter : str or tuple of str - The keyword's path. - categories : tuple of type - The warning classes that concern the keyword's value. - - Yields - ------ - None - Control, for the block. """ caught: list[warnings.WarningMessage] = [] try: @@ -2007,15 +1760,7 @@ def _warnings_about( def _path(*names: str) -> ParameterPath: """Return the path of one keyword, or the tuple of several in conflict. - Parameters - ---------- - *names : str - The keywords, at least one. - - Returns - ------- - str or tuple of str - The one name, or the names. + - ``*names``: the keywords, at least one. """ return names[0] if len(names) == 1 else names @@ -2023,15 +1768,7 @@ def _path(*names: str) -> ParameterPath: def _unset(**keywords: object) -> ParameterPath: """Return the path of the keywords among `keywords` left unset. - Parameters - ---------- - **keywords : object - The keywords a refusal needs, by name; at least one is ``None``. - - Returns - ------- - str or tuple of str - The unset one, or the unset ones. + - ``**keywords``: the keywords a refusal needs, by name; at least one is ``None``. """ return _path(*(name for name, value in keywords.items() if value is None)) @@ -2039,17 +1776,9 @@ def _unset(**keywords: object) -> ParameterPath: def _same_setting(old: object, new: object) -> bool: """Return whether two values of one settings field are the same setting. - Parameters - ---------- - old, new : object - The field's current and proposed values. - - Returns - ------- - bool - True for the same object, equal arrays, or values that compare equal; an - object compared by identity (``eq=False``) is the same only as itself, which - is what :func:`dataclasses.replace` keeps for every field it is not given. + - Returns: True for the same object, equal arrays, or values that compare equal; an + object compared by identity (``eq=False``) is the same only as itself, which is + what :func:`dataclasses.replace` keeps for every field it is not given. """ if old is new: return True @@ -2313,24 +2042,9 @@ def _built_input( A spatial pulse is built on the spatial grid in the medium, in the simulation's frame, which it sets to the comoving frame when none was given. - Parameters - ---------- - initial : Pulse, SpatialPulse or numpy.ndarray - The constructor's input. - grid : solprop.grid.TimeGrid - The temporal grid. - initial_z : float - The launch position, m. - - Returns - ------- - numpy.ndarray - The spectrum, complex128, read-only and the simulation's own (`_built`). - - Raises - ------ - DriverError - If a spatial pulse comes without a spatial grid and a medium. + - ``initial_z``: the launch position, m. + - Returns: the spectrum, complex128, read-only and the simulation's own + (`_built`). """ if not isinstance(initial, SpatialPulse | SpatialPulseSum): return _built(initial, grid) @@ -2353,8 +2067,8 @@ def _built_input( ) @property - def settings(self) -> SimulationSettings: - """Return how the run steps, records and executes; fixed once translated. + def settings(self) -> SimulationSettings: # numpydoc ignore=RT01 + """How the run steps, records and executes; fixed once translated. Assigning a new :class:`SimulationSettings` before the first :meth:`translate` (or :meth:`run`) takes effect. After it the translated problem is cached, so an @@ -2362,10 +2076,7 @@ def settings(self) -> SimulationSettings: it could no longer change the run; the ``backend`` and ``tdse_execution`` resources, which are read when the run executes, may still change. - Returns - ------- - SimulationSettings - The settings the run is, or will be, translated with. + The settings the run is, or will be, translated with. """ return self._settings @@ -2426,14 +2137,7 @@ def settings(self, settings: SimulationSettings) -> None: self._settings = settings def _kind(self) -> str: - """Return what kind of run this is, for printing. - - Returns - ------- - str - ``"modal"`` with its mode count, the guide's channel count, or the - spatial grid's class. - """ + """Return what kind of run this is, for printing.""" if self.spatial_grid is not None: return type(self.spatial_grid).__name__ count = len(self.modes) @@ -2464,10 +2168,7 @@ def __repr__(self) -> str: def _describe(self) -> tuple[str, list[tuple[str, object]]]: """Return the title and rows of the printed block. - Returns - ------- - tuple - The title and ``(key, value)`` rows. + - Returns: the title and ``(key, value)`` rows. """ names = [type(response).__name__ for response in self.responses] initial = ( @@ -2610,11 +2311,6 @@ def _run_request(self) -> RunRequest: error control, the itinerary, the planes and the statistics (SEP 47 section 5.2). A run adapts it to its start, retention, monitor and history (:meth:`~solprop.translate.run_request.RunRequest.for_run`). - - Returns - ------- - solprop.translate.run_request.RunRequest - The request. """ self.translate() assert self._request is not None # set with the problem @@ -2895,26 +2591,10 @@ def _launched( ) -> tuple[np.ndarray, float | None, NoiseRealisation | None]: """Return what a coupling launch means for :meth:`with_input`, or refuse it. - Parameters - ---------- - launch : solprop.coupling.CouplingLaunch - The launch. - initial_z : float or None - The position asked for beside it, which it refuses. - noise : NoiseRealisation, {'keep'} or None - The noise asked for: a launch carries its source's realised field, so - ``'keep'`` draws no second floor. - - Returns - ------- - tuple - The launch's spectrum, its interior position (``None`` at this run's own - entrance) and its noise. - - Raises - ------ - DriverError - If a position is given beside it, or it is bound to another port. + - ``noise``: the noise asked for: a launch carries its source's realised field, + so ``'keep'`` draws no second floor. + - Returns: the launch's spectrum, its interior position (``None`` at this run's + own entrance) and its noise. """ if initial_z is not None: msg = "a launch carries its own entrance: give no initial_z beside it" @@ -2935,24 +2615,7 @@ def _derived_configuration( ) -> dict[str, object]: """Return a derived run's extra configuration: this run's, the caller's keys. - Parameters - ---------- - initial : Pulse, SpatialPulse or numpy.ndarray - The replacement input. - configuration : collections.abc.Mapping or None - The caller's keys. - coupling : collections.abc.Mapping or None, optional - The provenance of a coupling launch, recorded as ``coupling``. - - Returns - ------- - dict - The configuration the derived simulation is built with. - - Raises - ------ - DriverError - If a caller's key is one the run records itself. + - ``coupling``: the provenance of a coupling launch, recorded as ``coupling``. """ recorded = dict(self.configuration) # Nor does the constructor's incident-beam account describe it, nor a @@ -2988,17 +2651,8 @@ def _check_replaced_array( ) -> None: """Validate a replaced array input before it is copied; a pulse is built first. - Parameters - ---------- - initial : Pulse, SpatialPulse or numpy.ndarray - The replacement input. - noise : solprop.noise.NoiseRealisation or None - A replacement realisation, or ``None`` when the noise is kept or removed. - - Raises - ------ - DriverError - As :meth:`_check_replaced_input`, for an array. + - ``noise``: a replacement realisation, or ``None`` when the noise is kept or + removed. """ if isinstance(initial, np.ndarray): self._check_replaced_input(initial, noise) @@ -3014,17 +2668,8 @@ def _check_replaced_input( validated when it was translated (SEP 22 section 5.2); the dtype is fixed by the build. - Parameters - ---------- - field0 : numpy.ndarray - The replacement's built spectrum. - noise : solprop.noise.NoiseRealisation or None - A replacement realisation, or ``None`` when the noise is kept or removed. - - Raises - ------ - DriverError - If a shape differs from this run's input or a sample is not finite. + - ``noise``: a replacement realisation, or ``None`` when the noise is kept or + removed. """ if field0.shape != self._field0.shape: raise DriverError("with_input requires the existing mode and grid shape") @@ -3057,18 +2702,7 @@ def runner(self) -> "SimulationRunner": return SimulationRunner(self) def _drop_recipe(self, reason: str) -> None: - """Forget the recipe, which no longer describes this simulation, and warn. - - Parameters - ---------- - reason : str - What changed, for the warning. - - Warns - ----- - SolpropWarning - Saying the run will record no recipe. - """ + """Forget the recipe, which no longer describes this simulation, and warn.""" self._recipe = None msg = ( f"this Simulation was built from a recipe, but {reason}, which the recipe " @@ -3078,18 +2712,7 @@ def _drop_recipe(self, reason: str) -> None: warnings.warn(msg, SolpropWarning, stacklevel=3) def _warn_recipe_dropped(self, mutator: str) -> None: - """Warn that a derived simulation does not carry this one's recipe. - - Parameters - ---------- - mutator : str - The method that derived it. - - Warns - ----- - SolpropWarning - Saying the derived run will record no recipe. - """ + """Warn that a derived simulation does not carry this one's recipe.""" msg = ( f"{mutator} derives a simulation the recipe this one was built from does " "not describe: its output will record no recipe, and resuming it needs " @@ -3246,14 +2869,7 @@ def metadata(self) -> RunMetadata: return self._metadata def _recorded_input(self) -> object: - """Return the input as this run's record holds it, digesting a large array. - - Returns - ------- - object - The caller's statement of it, or what - :func:`~solprop.output.schema.recorded_input` makes of it. - """ + """Return the input as this run's record holds it, digesting a large array.""" if self._input_identity is not None: return StatedInput.of( cast("np.ndarray", self.initial), self._input_identity @@ -3266,11 +2882,6 @@ def _recorded_keys(self) -> set[str]: What a derived run's caller keys are checked against, which a parent that is only a placeholder for its inputs should not pay a digest of itself for. - - Returns - ------- - set of str - The keys. """ if self._record_keys is None: record = self._metadata or self._record(None) @@ -3278,18 +2889,7 @@ def _recorded_keys(self) -> set[str]: return self._record_keys def _record(self, initial: object) -> RunMetadata: - """Build this run's record around what it says of its input. - - Parameters - ---------- - initial : object - The input's entry in the configuration record. - - Returns - ------- - solprop.output.schema.RunMetadata - What produced the run, and the key a resume is checked against. - """ + """Build this run's record around what it says of its input.""" configuration = dict(self.configuration) configuration.update( { @@ -3444,10 +3044,7 @@ def _modal_identity(self) -> Mapping[str, JsonValue] | None: 50 to 30 micrometre linear taper, 40 rather than 30 micrometres, a third optimistic in every ratio (:func:`solprop.modes.capillary.worst_wall_limits`). - Returns - ------- - collections.abc.Mapping or None - The ``modal`` record, or ``None``. + - Returns: the ``modal`` record, or ``None``. """ if self.general is not None: return modal_identity( @@ -3512,14 +3109,7 @@ def _modal_identity(self) -> Mapping[str, JsonValue] | None: ) def _boundary_monitor(self) -> BoundaryMonitor | None: - """Return the boundary monitor of a fresh run, or ``None`` without a check. - - Returns - ------- - solprop.monitor.BoundaryMonitor or None - Weights for this grid, its temporal collar and, on a radial run, the - Hankel quadrature of its columns. - """ + """Return the boundary monitor of a fresh run, or ``None`` without a check.""" check = self.settings.boundary_check if check is None: return None @@ -3554,22 +3144,7 @@ def _transverse_edge( space: RadialGrid | FreeGrid | PlanarGrid, medium: SpatialMedium, ) -> TransverseEdge: - """Return the transverse edge a free-space run's monitor reads (SEP 40 5.3). - - Parameters - ---------- - check : solprop.monitor.BoundaryCheck - The run's thresholds. - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - Its transverse grid. - medium : solprop.spatial.SpatialMedium - Its medium. - - Returns - ------- - solprop.transverse_monitor.TransverseEdge - The edge, with nothing recorded. - """ + """Return the transverse edge a free-space run's monitor reads (SEP 40 5.3).""" problem = self.translate() planes = self._run_request().observation.save_planes length = float(np.max(planes)) - float(problem.initial.z) @@ -3715,31 +3290,10 @@ def _run( # noqa: PLR0915 - run's controls, and the backend it may be handed ) -> Output: """Execute the run, on a new backend or on one a caller keeps between runs. - Parameters - ---------- - output : solprop.output.schema.Output or pathlib.Path or str or None, optional - As for :meth:`run`. - progress : ProgressReporter, {'auto', 'bar', 'log', 'none'} or None, optional - As for :meth:`run`. - progress_period : float, optional - As for :meth:`run`, s. - resume : bool or str, optional - As for :meth:`run`. - tdse_archive : object or None, optional - As for :meth:`run`. - snapshots : object or None, optional - As for :meth:`run`. - stop : object or None, optional - As for :meth:`run`. - kept : solprop.backends.protocol.Backend or None, optional - A backend built for this problem but for its input, which is loaded and - left open for the caller; ``None`` constructs one and closes it. It serves - a fresh run only, with no TDSE response. - - Returns - ------- - solprop.output.schema.Output - The handler the run was written to. + - ``progress_period``: as for :meth:`run`, s. + - ``kept``: a backend built for this problem but for its input, which is loaded + and left open for the caller; ``None`` constructs one and closes it. It serves + a fresh run only, with no TDSE response. """ _check_resume_choice(resume) with _optmat.material_context(self._material_log): @@ -3954,20 +3508,8 @@ def _prepare_atomic( ) -> tuple[TDSEBinding, TDSEArchiveWriter | None]: """Open a TDSE run's archive and prepare its binding. - Parameters - ---------- - problem : PropagationProblem - The translated problem, with its TDSE descriptor. - context : _AtomicContext - The optical output, resume state and record. - tdse_archive : TDSEArchive or None - The archive request. - - Returns - ------- - tuple - The binding and the open archive session, or None without a request. - A failure closes the archive before it propagates. + - Returns: the binding and the open archive session, or None without a request. + A failure closes the archive before it propagates. """ descriptor = tdse_descriptor(problem.route) assert descriptor is not None @@ -4009,12 +3551,7 @@ def _restore_atomic(binding: TDSEBinding | None, start: Start) -> None: has begun work refuses to be restored. The start keeps only the logical ledger, which the session restores when it loads the start. - Parameters - ---------- - binding : TDSEBinding or None - The run's open atomic binding; None for a run without one. - start : solprop.backends.session.Start - Where the run starts; its atomic ledger is taken. + - ``binding``: the run's open atomic binding; None for a run without one. """ ledgers = start.ledgers if binding is None or not ledgers or not ledgers.get("atomic"): @@ -4040,18 +3577,7 @@ def _consistency_gate( output, as the opened run record without any plane or checkpoint, before :class:`~solprop.exceptions.TDSEConsistencyError` is raised (SEP 20, 5.4). - Parameters - ---------- - binding : TDSEBinding or None - The run's open atomic binding; None for a run without one. - problem : PropagationProblem - The translated problem. - handler : Output - The run's output handler, not yet opened. - metadata : RunMetadata - The run's record. - backend : Backend - The prepared optical backend. + - ``binding``: the run's open atomic binding; None for a run without one. """ descriptor = tdse_descriptor(problem.route) if binding is None or descriptor is None or descriptor.model != "hacc": @@ -4093,18 +3619,6 @@ def _reseed(self, seed: int) -> bool: resume (:meth:`run`) and a scan point (:func:`~solprop.driver.scan.run_scan`) pass the seed that makes the run the one they continue or reproduce. A realisation the caller seeded or supplied is never redrawn. - - Parameters - ---------- - seed : int - The entropy to draw from. - - Returns - ------- - bool - Whether the realisation was drawn again. A translation keeps every leaf - but the initial noise, which is all a realisation reaches; the record is - rebuilt on its next use. """ if self._noise_redraw is None or self.noise is None or self.noise.seed == seed: return False @@ -4121,17 +3635,9 @@ def _reseeded(self, seed: int) -> "Simulation": member by reference, so a point's realisation must not land on the object the other points share. Translated first, so that they share its translation. - Parameters - ---------- - seed : int - The entropy to draw from. - - Returns - ------- - Simulation - A copy carrying the new realisation and sharing the translation's other - leaves, with its own material log as any derived run has; or this run, - when there is nothing to draw again (:meth:`_reseed`). + - Returns: a copy carrying the new realisation and sharing the translation's + other leaves, with its own material log as any derived run has; or this run, + when there is nothing to draw again (:meth:`_reseed`). """ if self._noise_redraw is None or self.noise is None or self.noise.seed == seed: return self @@ -4155,15 +3661,7 @@ def _rebound_noise( redraw gives, or a resume drawing from the recorded seed would disagree with its own file. A realisation seeded or supplied by the caller stays as it is. - Parameters - ---------- - medium : solprop.spatial.SpatialMedium or None - The updated run's medium; ``None`` on a modal route. - - Returns - ------- - tuple - The realisation and the redraw the updated run carries. + - ``medium``: the updated run's medium; ``None`` on a modal route. """ noise, redraw = self.noise, self._noise_redraw if noise is None or redraw is None or medium is None: @@ -4180,11 +3678,6 @@ def _adopt_recorded_seed(self, output: Output | Path | str | None) -> None: the file recorded no seed or another noise model, or the realisation was seeded or supplied by the caller; :func:`~solprop.output.hdf5.check_cache` then judges the run as before. - - Parameters - ---------- - output : solprop.output.schema.Output or pathlib.Path or str or None - Where the resumed run is written. """ if ( self._noise_redraw is None @@ -4218,28 +3711,12 @@ def _start( The run's session loads it (SEP 47): the field, the noise, a resume's call ledgers and the method history this run continues. - Parameters - ---------- - backend : solprop.backends.protocol.Backend - The backend about to step, asked only whether its march can carry a - method history. - problem : solprop.translate.problem.PropagationProblem - The translated problem, which carries the initial state. - state : solprop.output.hdf5.ResumeState or None - What a file the run is continuing holds, or ``None`` to start at the - entrance. - kept : bool, optional - Whether the backend was built for another input, so that the noise of - this one has to be handed to it. - restoration : dict or None, optional - Filled, on a resume, with what was restored of the method history - (:meth:`_continue_march`), for the resumed execution record. - - Returns - ------- - solprop.backends.session.Start - The start, whose ``plane_index`` is the first save plane still to propagate - to. + - ``backend``: the backend about to step, asked only whether its march can carry + a method history. + - ``state``: what a file the run is continuing holds, or ``None`` to start at + the entrance. + - ``kept``: the backend was built for another input, so this one's noise has to + be handed to it. """ if state is None: return Start.entrance( @@ -4303,28 +3780,11 @@ def _continue_march( session, which warns and records a reset if it cannot (:func:`solprop.driver.run.run_problem`). - Parameters - ---------- - backend : solprop.backends.protocol.Backend - The backend, asked whether its march can carry a history. - state : solprop.output.hdf5.ResumeState - What the file holds. - adams : bool, optional - Whether this run steps with an Adams method. Another integrator keeps no - history, by its own choice, which needs no warning. - low_storage : bool, optional - Whether this run steps with the low-storage march, which keeps none at - all; a Runge-Kutta march resets its proposal and norm memory. - - Returns - ------- - record : dict - ``"state"`` (``"restored"``, ``"reset"`` or ``"not_applicable"``), - ``"reason"`` and ``"z"``, m; for an Adams method also ``"retention"``, what - the file states it kept there, ``{"state": "unknown"}`` where it states - nothing. - march : solprop.output.schema.MarchSnapshot or None - The history the run's session is to install, or ``None``. + - Returns ``record``: ``"state"`` (``"restored"``, ``"reset"`` or + ``"not_applicable"``), ``"reason"`` and ``"z"``, m; for an Adams method also + ``"retention"``, what the file states it kept there, ``{"state": "unknown"}`` + where it states nothing. + - Returns ``march``: the history the run's session is to install, or ``None``. """ z = state.plane.z if low_storage: @@ -4351,15 +3811,8 @@ def _continue_march( def reset(reason: str) -> dict[str, object]: """Return the restoration record of a history that starts afresh. - Parameters - ---------- - reason : str - Why the history starts afresh. - - Returns - ------- - dict - The record's ``"state"``, ``"reason"``, ``"z"`` and ``"retention"``. + - Returns: the record's ``"state"``, ``"reason"``, ``"z"`` and + ``"retention"``. """ return {"state": "reset", "reason": reason, "z": z, "retention": retention} @@ -4392,20 +3845,7 @@ def _missing_history( retention: Mapping[str, object], reset: Callable[[str], dict[str, object]], ) -> dict[str, object]: - """Record, and warn about, an Adams resume whose file holds no history. - - Parameters - ---------- - retention : collections.abc.Mapping - What the file states it kept at the resume position. - reset : callable - Builds the reset record for a reason. - - Returns - ------- - dict - The restoration record. - """ + """Record, and warn about, an Adams resume whose file holds no history.""" kept, why = retention.get("state"), retention.get("reason") if kept == "omitted" and why == "automatic_size_limit": size = retention.get("nbytes") @@ -4822,46 +4262,15 @@ def _from_spatial( ) -> "Simulation": """Build a radial or Cartesian propagation using physical fields. - Parameters - ---------- - grid : solprop.grid.TimeGrid - Field or envelope time grid, in SI units. - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - Radial nodes, outer radius and spatial collar, or the Cartesian axes and - their collar. - medium : solprop.spatial.SpatialMedium - Homogeneous transverse material; gas density may vary along z. - responses : collections.abc.Sequence - Existing Kerr, plasma or explicitly declared Raman response objects. - initial : SpatialPulse, SpatialPulseSum or numpy.ndarray - Beam specification or internal spectrum, a Hankel spectrum in V m or a - raw Cartesian DFT spectrum in V/m, with shape ``(n_columns, n_pol, - n_omega)``. - settings : SimulationSettings - Step tolerances, positive save positions in m, statistics and backend. - Absolute tolerance has the input spectrum's units. - frame : solprop.linear.Frame or None, optional - Moving frame. None uses the declared responses' THG choice. - noise : {'default', None, 'input', 'modified'}, optional - One photon per transverse column, polarisation and active bin. - ``"default"``, the default, resolves to ``"input"`` on a radial grid and to - no noise on a Cartesian or line one - (:data:`~solprop.driver.defaults.FREE_NOISE`), and records the resolution - (SEP 37). - seed : int or None, optional - Random generator seed; None draws and records system entropy. - materials : solprop.physdata.selection.MaterialSelection or None, optional - The run's material selection, recorded in its manifest. - - Returns - ------- - Simulation - Cached translation and the ordinary resumable execution interface. - - Raises - ------ - DriverError - If a noise seed is given without noise or the input shape is invalid. + - ``space``: radial nodes, outer radius and spatial collar, or the Cartesian + axes and their collar. + - ``initial``: beam specification or internal spectrum, a Hankel spectrum in V m + or a raw Cartesian DFT spectrum in V/m, with shape ``(n_columns, n_pol, + n_omega)``. + - ``settings``: step tolerances, positive save positions in m, statistics and + backend. Absolute tolerance has the input spectrum's units. + - ``frame``: moving frame. None uses the declared responses' THG choice. + - ``seed``: random generator seed; None draws and records system entropy. """ chosen_frame = frame if chosen_frame is None: @@ -6362,86 +5771,23 @@ def _from_general( normalisation boundary. Every keyword of the gas-filled path arrives in `refused` and is refused by name if it was set. - Parameters - ---------- - provider : solprop.modes.guide.ModalGuide - The guide, carrying its sampling, its channels and its regions. - modes : sequence of solprop.modes.protocol.Mode - Empty, or exactly the guide's own ordered channels. - length : float - Guide length, m. - channel : int or str or None - Which channel the pulse enters; ``None`` takes the first. - incident : solprop.pulses.SpatialPulse or None - An incident beam to couple into the channels instead, as - :meth:`from_waveguide` documents it. - coupling : solprop.coupling.models.CouplingModel or None - The model it is coupled under; required beside `incident` and refused - without it. - kerr_form : {'real', 'analytic'} - Which instantaneous cubic the regions respond with, as - :meth:`from_waveguide` documents it. - observable_rtol, observable_atol : float or None - Accepted only as ``None``, which means the solver's own tolerances. - wavelength, wavelength_limits, trange, grid : object - The grid, as :func:`_capillary_grid` takes them; a **field** grid. - fwhm, width, shape, energy, peak_power, phases, pulses : object - The input pulse, as :func:`_shaped_pulse` takes it. - absorber : solprop.boundaries.Absorber, False or None - The collar rates; ``None`` builds the grid's default collar, and ``False`` - switches both soft collars off and keeps the hard band mask. - absorber_scheme : {'nonlinear', 'split_step'} or None - How the temporal collar is applied; ``None`` is the default scheme. - boundary_check : solprop.monitor.BoundaryCheck or None - How the run watches its time window's edges, as :meth:`from_waveguide` - takes it. - noise : {'default', None, 'input', 'modified'} - Which shot-noise model to carry (``docs/guide/media/noise.md``). - ``"default"``, the default, resolves to ``"input"`` wherever the run can - carry noise, and records the resolution (SEP 37). - seed : int or None - Entropy of the noise generator. - rtol, atol, dz_max, dz_init, schedule, save_planes, n_save : object - The step controller and the schedule, as the other entry points take them. - stats : solprop.stats.StatsSpec or None - Which statistics to record; ``None`` takes :func:`_general_stats`. - backend : str - Registry name, or ``"auto"``. - precision : {'double', 'single'} - The arithmetic the run is held in; the general route is refused under - single precision. - materials : solprop.physdata.selection.MaterialSelection or None, optional - The run's material selection, recorded in its manifest. Under it a - region's default :class:`~solprop.nonlinear.raman.SilicaTensorResponse` - is optmat's tensor bundle (SEP 3, Q8), and a region's gas fill - (:class:`~solprop.modes.material.GasKerr`) takes its electronic policy: - the Kerr coefficient at the carrier, or SEP 6's dispersive cubic. - integrator : Multistep, RungeKutta or LowStorageRungeKutta, optional - The method that steps the run; ``None`` takes ``Multistep()``, and - ``LowStorageRungeKutta()`` replays `schedule` in three registers. - launch : mapping, sequence of mapping, or None, optional - The channels each pulse enters, with their shares and phases, as - :meth:`from_waveguide` documents it; refused beside `channel`, - `incident` and `coupling`. - interpolate_planes : bool, optional - Observe the save planes inside accepted steps instead of landing on them. - step_planes : int or None, optional - Also save the end of every `step_planes`-th accepted step; ``None`` is - ``0``. - polarisation_taper : bool, optional - Whether the spectral collar tapers the nonlinear polarisation. - **refused : object - Every gas-filled keyword; each is refused by name when it is set. - - Returns - ------- - Simulation - A configured general modal simulation, ready to translate or run. - - Raises - ------ - DriverError - Naming the keyword, the grid or the channel that cannot be carried. + - ``length``: guide length, m. + - ``channel``: which channel the pulse enters; ``None`` takes the first. + - ``observable_rtol``, ``observable_atol``: accepted only as ``None``, which + means the solver's own tolerances. + - ``fwhm``, ``width``, ``shape``, ``energy``, ``peak_power``, ``phases``, + ``pulses``: the input pulse, as :func:`_shaped_pulse` takes it. + - ``absorber``: the collar rates; ``None`` builds the grid's default collar, and + ``False`` switches both soft collars off and keeps the hard band mask. + - ``absorber_scheme``: how the temporal collar is applied; ``None`` is the + default scheme. + - ``stats``: which statistics to record; ``None`` takes :func:`_general_stats`. + - ``integrator``: the method that steps the run; ``None`` takes ``Multistep()``, + and ``LowStorageRungeKutta()`` replays `schedule` in three registers. + - ``step_planes``: also save the end of every `step_planes`-th accepted step; + ``None`` is ``0``. + - ``**refused``: every gas-filled keyword; each is refused by name when it is + set. """ _refuse_gas_keywords(refused) provider = _selected_guide( @@ -6700,307 +6046,184 @@ def _from_hollow( # noqa: PLR0915 - the interface's keyword surface See ``docs/guide/setup/running.md`` for the discussion and a worked example. - Parameters - ---------- - radius : float or solprop.profiles.ZProfile - Core radius, m, constant or along z. - length : float - Capillary length, m. - gas : Gas, str, Mapping or Mixture - Built-in gas, mole fractions keyed by species name, or ordered populations - carrying their own density paths. A mapping uses total pressure. - pressure : float, sequence, ZProfile or None, optional - None uses one bar for a gas or fractions. Omit pressure beside a Mixture; - every explicit pressure is refused because it already carries its paths. - Fill pressure in **bar**: a number for a constant fill, a pair - ``(p_in, p_out)`` for the steady-flow gradient of - :meth:`solprop.profiles.PressureGradient.two_point`, or ``(z, p)`` with the - positions and the pressures there for - :meth:`solprop.profiles.PressureGradient.from_stages`. - waveguide : bool, optional - Bind supplied geometry instead of constructing a capillary. - pressure_floor : float or None, optional - Lowest pressure the fill is allowed to reach, bar; ``None`` takes - :data:`solprop.profiles.DEFAULT_PRESSURE_FLOOR_FRACTION` of the highest - stage pressure. With ``vacuum_end="floor"``, pass ``0.0`` for a true - vacuum end, which warns, because ``dp/dz`` diverges there. - Refused beside a constant pressure or a pressure profile, which have - no end to apply it to. - vacuum_end : {'choked', 'floor'} or None, optional - How a pressure gradient's outer end below its choked pressure is treated. - ``None`` (the default) means ``"choked"`` for a circular hollow guide: the - fill follows the bore (Pennetta et al. 2019, eq. 6) and an end exhausting - into vacuum takes the choked pressure of the flow - (:func:`solprop.gasflow.steady_fill`), with a warning if the flow is not - laminar. ``"floor"`` concerns the ends only: it keeps the declared end - pressures, raised only to `pressure_floor`, for a comparison with a model - that imposes them, while a taper's fill still follows the bore (on a - uniform bore, bitwise the uniform law). Refused beside a constant - pressure, a pressure profile or a Mixture, which have no end to choke, - and on the general route. Every mode declaring ``core_radius`` -- every - ``MarcatiliMode`` subclass, a ``TabulatedMode`` built from arrays - included -- is taken as a circular bore of that radius; a rectangular - guide keeps the uniform law. - wavelength : float - Reference wavelength, m, and the pulse's centre wavelength. - wavelength_limits : tuple of float or None, optional - Shortest and longest wavelength of the frequency window, m. Required unless - `grid` is given. - trange : float or None, optional - Requested width of the time window, s. Required unless `grid` is given. - grid : solprop.grid.TimeGrid or None, optional - A grid of one's own, instead of the three keywords above; the only way to - reach ``dt``, ``ffac`` or the transform size. - envelope : bool, optional - Propagate an envelope. Plasma is off by default; explicit ``plasma=True`` - selects cycle averaging of instantaneous rates. Plasma forbids THG. - fwhm : float or None, optional - Pulse duration at half maximum, s. - width : float or None, optional - Natural width of a sech pulse, s; give this or `fwhm`. - shape : {'gauss', 'sech'}, optional - Transform-limited shape of the input. - energy : float or None, optional - Pulse energy, J. Give this or `peak_power`. - peak_power : float or None, optional - Peak power, W, realised after the spectral phases. - phases : sequence of float, optional - Taylor spectral phases about `wavelength`, optics convention: CEP in rad, - group delay in s, GDD in s^2, and so on. - pulses : sequence of solprop.pulses.Pulse or None, optional - Several pulses launched together, for two-colour and multi-colour - inputs: each a :mod:`solprop.pulses` object with its own wavelength, - duration, energy or peak power, phase and delay; the input is the sum of - their fields (:class:`~solprop.pulses.PulseSum`). Replaces the - single-pulse keywords `fwhm`, `width`, `shape`, `energy`, - `peak_power` and `phases`, which must then be left unset; `wavelength` still - sets the grid's reference wavelength. - launch : mapping, sequence of mapping, or None, optional - The modes each pulse enters, with their shares and phases, as - :meth:`from_capillary` documents it; ``None`` puts the input in the - first mode. - modes : int or sequence of Mode, optional - How many HE_1m modes to propagate, or the mode set itself. A set is checked - 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 - ``model="reduced"``. None selects ordinary capillary modes. - wall : solprop.modes.CoherentWall or None, optional - Declare the scalar **coherent** wall block: the outgoing Robin boundary - condition condensed onto the retained basis, which couples every ``HE_1m`` - channel to every other with a definite phase instead of giving each its own - Marcatili attenuation. It needs the contiguous set ``HE_11`` to ``HE_1N`` -- - or, with ``impedance="te_tm"``, one azimuthal order -- on a constant or a - varying guide, and a scalar run; it carries the default ``"input"`` noise - and refuses ``"modified"``. ``numpy_ref`` and ``native`` execute it (a - ``method="cf4"`` wall ``numpy_ref`` alone); every other mode set or - geometry is refused by name. ``None``, the default, carries no wall: - the modes keep their own Marcatili loss, and a multimode taper its taper - coupling (below). - taper_coupling : bool, optional - On a multimode capillary whose radius varies along z, carry the - **taper coupling** -- the connection ```` that moves - power between the ``HE_1m`` channels as the bore changes -- lossless or - not. ``True``, the default; ``False`` is the explicit opt-out to the - **adiabatic** route, on which each channel follows its local mode and - exchanges no power. It is refused beside `wall`, which always carries - the connection, and has no effect on one channel, a constant bore (a - pressure gradient included, which does not change the modes' shape) or - a set that is not the contiguous ``HE_11`` to ``HE_1N`` block - (``docs/guide/waveguides/capillaries.md``, "Tapers"). - model : {'full', 'reduced'}, optional - Which Marcatili model the modes use; beside a supplied set, refused unless - it is the default or the modes' own. - loss : bool, float or callable, optional - Propagation loss of the modes; beside a supplied set, refused unless it - is the default or the modes' own. - cladding : GlassIndex, complex or None, optional - Cladding index; ``None`` is the package's cited silica. Beside a - supplied set, refused unless it is the default or the modes' own. - susceptibility : GasSusceptibility or None, optional - A gas susceptibility replacing the one `gas` selects, for a comparison - against another code's refractive data. Refused for a fraction mapping - or an existing mixture, which own their species models. - temperature : float, ZProfile or None, optional - Gas temperature, K, or a path of it along z in m. Sets the density the - pressure path maps to, so it changes both the dispersion and the - nonlinearity. A path also weights a steady-flow gradient by the gas's - ``eta(T) T`` (docs/guide/waveguides/profiles.md), makes a shared mixture's - populations independent parameters, and holds the rotational Raman - populations at one reference temperature with a - :class:`~solprop.exceptions.RamanTemperatureWarning` - (:func:`solprop.nonlinear.mixtures.raman_temperature`). A constant path - is the number, bit for bit. ``None`` (the default) is 293.15 K. A Mixture - owns its temperature: beside one, a temperature must be omitted or be the - mixture's own (the same number or path object), and any other is refused by - name. - kerr : bool, optional - Include the Kerr response. - thg : bool or None, optional - Include third-harmonic generation in the Kerr term; ``None`` takes - ``not envelope``. - plasma : bool, IonisationRate, Mapping or None, optional - Include photoionisation. ``None`` takes ``not envelope``. ``True`` selects - optmat's recommended rate model of each built-in gas (SEP 3, D1; - :class:`~solprop.nonlinear.optmat_rates.OptmatRate`, or the model the - selection's ``ionisation`` names), for a single gas and for every - component of a mixture; a custom species needs its own rate. A - rate object is used as given; a mapping selects a rate or switch per - component label, and omitted labels are off. - preionisation : float, ZProfile, Mapping or None, optional - Initial ionised fraction in [0, 1], globally or per label: a number, or a - path along z in m giving the fraction ionised before the pulse arrives at - each position. None and omitted mapping labels retain the component's - existing declaration. - plasma_integration : {'band', 'trapezoid'} or None, optional - How the field-grid plasma forms its population and free-current integrals - (SEP 18): ``'band'`` filters each integrand with taps designed for the - grid, exact to a certified 1e-5 across the active band; ``'trapezoid'`` - is the plain cumulative trapezoid, short by up to 11 % per integral at - the top of the default active band, which the frozen witness comparisons - use. ``None`` takes :class:`~solprop.nonlinear.responses.PlasmaResponse`'s - default. Refused with envelope plasma, whose currents are converted - exactly, and when the plasma is off. - raman : bool, sequence of str, Mapping or None, optional - ``None`` (the default) turns the Raman response on for every species - whose optmat response is curated -- N2, O2, H2, D2, CH4 and SF6 -- leaves - an atom without one, and warns (:class:`solprop.exceptions.SolpropWarning`) - for a molecule whose response is provisional (N2O, unless the selection's - ``raman`` is ``"provisional"``) rather than leaving it out silently; with - ``kerr=False`` the default is off. ``False`` disables it; ``True`` selects - the 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. - n_r : int or None, optional - Radial nodes of the fixed rule. ``None`` takes - :func:`default_radial_order` of the mode count. - n_theta : int or None, optional - Azimuthal nodes. Refused for a set of purely radial modes, which need none. - radial_rule : str, optional - The radial rule family (SEP 19): ``"gauss_kronrod"`` (the default), whose - embedded Gauss subset is the quadrature alarm's error estimate; - ``"gauss"``, with no alarm; or ``"field_scale"`` and - ``"field_scale_radau"``, Gaussian in the HE_11 field scale for expensive - responses, which need exactly one Marcatili HE_11 mode and have no alarm - (``docs/guide/waveguides/radial-quadrature.md``). - ionisation_field_range : tuple of float or None, optional - Field amplitudes in V/m the rate table must cover. ``None`` derives them - from the input's peak field over the axis and the transverse nodes; see - :func:`solprop.driver.simulation.default_field_range`. - absorber : solprop.boundaries.Absorber, False or None, optional - The collar rates; ``None`` builds the default collar of the grid, and - ``False`` switches both soft collars off and keeps the hard band mask - (:func:`solprop.boundaries.no_absorber`; SEP 21). - absorber_scheme : {'nonlinear', 'split_step'} or None, optional - How the temporal collar is applied (ADR-025): inside every stage, the - default, or once per accepted step. The low-call Adams methods of - SEP 13 step much further with ``'split_step'``, whose first-order - splitting error stays inside the collar. - boundary_check : solprop.monitor.BoundaryCheck or None, optional - How the run watches its time window's edges (SEP 21): the default warns, - then stops, when light above the shot-noise floor reaches them; ``None`` - watches nothing, for a window kept narrow on purpose. - noise : {'default', None, 'input', 'modified'}, optional - Which shot-noise model to carry. - ``"default"``, the default, resolves to ``"input"`` wherever the run can - carry noise, and records the resolution (SEP 37). - seed : int or None, optional - Entropy of the noise generator. - rtol : float or None, optional - Relative step tolerance; ``None`` takes the route's default - (:func:`~solprop.driver.defaults.default_rtol`). Calibrate it for a - production run (``docs/dev/stepper.md`` section 3). - atol : float, optional - Absolute step tolerance, in the square root of watts. - dz_max : float or None, optional - Cap on the step, m; ``None`` takes half the capillary. - dz_init : float or None, optional - First step, m; ``None`` takes ``length * 1e-4``. - schedule : solprop.stepper.schedule.FixedSchedule or None, optional - A fixed schedule to replay instead of stepping adaptively. - integrator : Multistep, RungeKutta or LowStorageRungeKutta, optional - The method that steps the run - (``docs/guide/numerics/choosing-a-stepper.md``). - ``None``, the default, takes ``Multistep()``: the low-call exponential - Adams method, one response evaluation per step. ``RungeKutta()`` is the - interaction-picture DOPRI5 march, for a cheap response or a state too - large for the Adams history. ``LowStorageRungeKutta()`` replays - `schedule` in three registers (SEP 24). - route : {'capillary', 'waveguide'}, optional - The interface building the run, whose row of - :data:`~solprop.driver.defaults.ROUTE_DEFAULTS` gives what the caller - left: ``"capillary"`` (its TDSE variant beside a TDSE response) or - ``"waveguide"`` (its slab variant for a slab mode set). - interpolate_planes : bool, optional - Observe the save planes inside accepted steps instead of landing on them - (SEP 14): through the Adams march's own polynomial, or DOPRI5's - continuous extension with ``RungeKutta()``. Default ``False``. - step_planes : int or None, optional - Also save the end of every `step_planes`-th accepted step (SEP 14); - ``None`` is ``0``, or ``1`` with `tdse` (SEP 20 section 5.8). - save_planes : sequence of float or None, optional - Positions to save at, m. Give this or `n_save`. - n_save : int or None, optional - How many planes to save, counting ``z = 0``. - 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, - or for a mode-averaged one under `mode_average`, plus every statistic - only a backend can compute. - polarisation_taper : bool, optional - Whether the spectral collar tapers the nonlinear polarisation. - node_budget : int or None, optional - Bytes of node-chunk buffers the response pass may hold, an execution - setting; ``None``, the default, leaves the backend's automatic choice. - backend : str, optional - Registry name, or ``"auto"`` (the default) for ``native``, which refuses if - it isn't installed or this processor is below its baseline. There is no - fallback, and import or construction errors stay visible. - precision : {'double', 'single'}, optional - The arithmetic the run is held in (SEP 33): ``"double"``, the default, or - ``"single"``, which takes ``RungeKutta()``, carries no noise, and runs on - ``numpy_ref``, the reference, with ``backend="auto"`` refused - (:attr:`SimulationSettings.precision`); `rtol` comes resolved. - polarisation : str, float or None, optional - Refused; this path is scalar. - materials : solprop.physdata.selection.MaterialSelection or None, optional - Take the run's built-in materials from optmat under this selection: a gas or - fraction mapping of built-in gases, ``"air"`` and ``Gas.Air`` resolve - through :func:`solprop.physdata.species` and - :meth:`solprop.physdata.mixtures.Mixture.air` with it, and the default - cladding (``cladding=None`` with a numbered mode set) is optmat's - recommended silica. A supplied mixture, mode set, cladding or susceptibility - is taken as it is. The run records its material manifest. - ``None`` (the default) is ``MaterialSelection()``. - - tdse : solprop.nonlinear.tdse.TDSEGasResponse or None, optional - The complete electronic response of a capillary fill - (:meth:`from_capillary`). - tdse_execution : solprop.nonlinear.tdse.TDSEExecution or None, optional - Its atomic runtime resources. - - Returns - ------- - Simulation - The simulation, ready to translate and run. - - Raises - ------ - DriverError - If a keyword is missing, inconsistent, or names something this path does - not carry. + - ``radius``: core radius, m, constant or along z. + - ``length``: capillary length, m. + - ``gas``: Built-in gas, mole fractions keyed by species name, or ordered + populations carrying their own density paths. A mapping uses total pressure. + - ``pressure``: None uses one bar for a gas or fractions. Omit pressure beside a + Mixture; every explicit pressure is refused because it already carries its + paths. Fill pressure in **bar**: a number for a constant fill, a pair ``(p_in, + p_out)`` for the steady-flow gradient of + :meth:`solprop.profiles.PressureGradient.two_point`, or ``(z, p)`` with the + positions and the pressures there for + :meth:`solprop.profiles.PressureGradient.from_stages`. + - ``pressure_floor``: lowest pressure the fill is allowed to reach, bar; + ``None`` takes :data:`solprop.profiles.DEFAULT_PRESSURE_FLOOR_FRACTION` of the + highest stage pressure. With ``vacuum_end="floor"``, pass ``0.0`` for a true + vacuum end, which warns, because ``dp/dz`` diverges there. Refused beside a + constant pressure or a pressure profile, which have no end to apply it to. + - ``vacuum_end``: how a pressure gradient's outer end below its choked pressure + is treated. ``None`` (the default) means ``"choked"`` for a circular hollow + guide: the fill follows the bore (Pennetta et al. 2019, eq. 6) and an end + exhausting into vacuum takes the choked pressure of the flow + (:func:`solprop.gasflow.steady_fill`), with a warning if the flow is not + laminar. ``"floor"`` concerns the ends only: it keeps the declared end + pressures, raised only to `pressure_floor`, for a comparison with a model that + imposes them, while a taper's fill still follows the bore (on a uniform bore, + bitwise the uniform law). Refused beside a constant pressure, a pressure + profile or a Mixture, which have no end to choke, and on the general route. + Every mode declaring ``core_radius`` -- every ``MarcatiliMode`` subclass, a + ``TabulatedMode`` built from arrays included -- is taken as a circular bore of + that radius; a rectangular guide keeps the uniform law. + - ``wavelength``: reference wavelength, m, and the pulse's centre wavelength. + - ``wavelength_limits``: shortest and longest wavelength of the frequency + window, m. Required unless `grid` is given. + - ``trange``: requested width of the time window, s. Required unless `grid` is + given. + - ``grid``: a grid of one's own, instead of the three keywords above; the only + way to reach ``dt``, ``ffac`` or the transform size. + - ``fwhm``: pulse duration at half maximum, s. + - ``width``: natural width of a sech pulse, s; give this or `fwhm`. + - ``energy``: pulse energy, J. Give this or `peak_power`. + - ``peak_power``: peak power, W, realised after the spectral phases. + - ``phases``: Taylor spectral phases about `wavelength`, optics convention: CEP + in rad, group delay in s, GDD in s^2, and so on. + - ``pulses``: several pulses launched together, for two-colour and multi-colour + inputs: each a :mod:`solprop.pulses` object with its own wavelength, duration, + energy or peak power, phase and delay; the input is the sum of their fields + (:class:`~solprop.pulses.PulseSum`). Replaces the single-pulse keywords + `fwhm`, `width`, `shape`, `energy`, `peak_power` and `phases`, which must then + be left unset; `wavelength` still sets the grid's reference wavelength. + - ``launch``: the modes each pulse enters, with their shares and phases, as + :meth:`from_capillary` documents it; ``None`` puts the input in the first + mode. + - ``mode_average``: 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``: tube geometry for an integer mode count, selecting Vincetti + modes. Refused with supplied modes, which carry their own geometry, and with + ``model="reduced"``. None selects ordinary capillary modes. + - ``wall``: declare the scalar **coherent** wall block: the outgoing Robin + boundary condition condensed onto the retained basis, which couples every + ``HE_1m`` channel to every other with a definite phase instead of giving each + its own Marcatili attenuation. It needs the contiguous set ``HE_11`` to + ``HE_1N`` -- or, with ``impedance="te_tm"``, one azimuthal order -- on a + constant or a varying guide, and a scalar run; it carries the default + ``"input"`` noise and refuses ``"modified"``. ``numpy_ref`` and ``native`` + execute it (a ``method="cf4"`` wall ``numpy_ref`` alone); every other mode set + or geometry is refused by name. ``None``, the default, carries no wall: the + modes keep their own Marcatili loss, and a multimode taper its taper coupling + (below). + - ``taper_coupling``: on a multimode capillary whose radius varies along z, + carry the **taper coupling** -- the connection ```` that + moves power between the ``HE_1m`` channels as the bore changes -- lossless or + not. ``True``, the default; ``False`` is the explicit opt-out to the + **adiabatic** route, on which each channel follows its local mode and + exchanges no power. It is refused beside `wall`, which always carries the + connection, and has no effect on one channel, a constant bore (a pressure + gradient included, which does not change the modes' shape) or a set that is + not the contiguous ``HE_11`` to ``HE_1N`` block + (``docs/guide/waveguides/capillaries.md``, "Tapers"). + - ``model``: which Marcatili model the modes use; beside a supplied set, refused + unless it is the default or the modes' own. + - ``loss``: propagation loss of the modes; beside a supplied set, refused unless + it is the default or the modes' own. + - ``cladding``: cladding index; ``None`` is the package's cited silica. Beside a + supplied set, refused unless it is the default or the modes' own. + - ``temperature``: gas temperature, K, or a path of it along z in m. Sets the + density the pressure path maps to, so it changes both the dispersion and the + nonlinearity. A path also weights a steady-flow gradient by the gas's ``eta(T) + T`` (docs/guide/waveguides/profiles.md), makes a shared mixture's populations + independent parameters, and holds the rotational Raman populations at one + reference temperature with a + :class:`~solprop.exceptions.RamanTemperatureWarning` + (:func:`solprop.nonlinear.mixtures.raman_temperature`). A constant path is the + number, bit for bit. ``None`` (the default) is 293.15 K. A Mixture owns its + temperature: beside one, a temperature must be omitted or be the mixture's own + (the same number or path object), and any other is refused by name. + - ``thg``: include third-harmonic generation in the Kerr term; ``None`` takes + ``not envelope``. + - ``plasma``: include photoionisation. ``None`` takes ``not envelope``. ``True`` + selects optmat's recommended rate model of each built-in gas (SEP 3, D1; + :class:`~solprop.nonlinear.optmat_rates.OptmatRate`, or the model the + selection's ``ionisation`` names), for a single gas and for every component of + a mixture; a custom species needs its own rate. A rate object is used as + given; a mapping selects a rate or switch per component label, and omitted + labels are off. + - ``preionisation``: initial ionised fraction in [0, 1], globally or per label: + a number, or a path along z in m giving the fraction ionised before the pulse + arrives at each position. None and omitted mapping labels retain the + component's existing declaration. + - ``plasma_integration``: how the field-grid plasma forms its population and + free-current integrals (SEP 18): ``'band'`` filters each integrand with taps + designed for the grid, exact to a certified 1e-5 across the active band; + ``'trapezoid'`` is the plain cumulative trapezoid, short by up to 11 % per + integral at the top of the default active band, which the frozen witness + comparisons use. ``None`` takes + :class:`~solprop.nonlinear.responses.PlasmaResponse`'s default. Refused with + envelope plasma, whose currents are converted exactly, and when the plasma is + off. + - ``raman``: ``None`` (the default) turns the Raman response on for every + species whose optmat response is curated -- N2, O2, H2, D2, CH4 and SF6 -- + leaves an atom without one, and warns + (:class:`solprop.exceptions.SolpropWarning`) for a molecule whose response is + provisional (N2O, unless the selection's ``raman`` is ``"provisional"``) + rather than leaving it out silently; with ``kerr=False`` the default is off. + ``False`` disables it; ``True`` selects the 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. + - ``n_r``: radial nodes of the fixed rule. ``None`` takes + :func:`default_radial_order` of the mode count. + - ``ionisation_field_range``: field amplitudes in V/m the rate table must cover. + ``None`` derives them from the input's peak field over the axis and the + transverse nodes; see :func:`solprop.driver.simulation.default_field_range`. + - ``absorber``: the collar rates; ``None`` builds the default collar of the + grid, and ``False`` switches both soft collars off and keeps the hard band + mask (:func:`solprop.boundaries.no_absorber`; SEP 21). + - ``boundary_check``: how the run watches its time window's edges (SEP 21): the + default warns, then stops, when light above the shot-noise floor reaches them; + ``None`` watches nothing, for a window kept narrow on purpose. + - ``rtol``: relative step tolerance; ``None`` takes the route's default + (:func:`~solprop.driver.defaults.default_rtol`). Calibrate it for a production + run (``docs/dev/stepper.md`` section 3). + - ``dz_max``: cap on the step, m; ``None`` takes half the capillary. + - ``dz_init``: first step, m; ``None`` takes ``length * 1e-4``. + - ``integrator``: the method that steps the run + (``docs/guide/numerics/choosing-a-stepper.md``). ``None``, the default, takes + ``Multistep()``: the low-call exponential Adams method, one response + evaluation per step. ``RungeKutta()`` is the interaction-picture DOPRI5 march, + for a cheap response or a state too large for the Adams history. + ``LowStorageRungeKutta()`` replays `schedule` in three registers (SEP 24). + - ``route``: the interface building the run, whose row of + :data:`~solprop.driver.defaults.ROUTE_DEFAULTS` gives what the caller left: + ``"capillary"`` (its TDSE variant beside a TDSE response) or ``"waveguide"`` + (its slab variant for a slab mode set). + - ``step_planes``: also save the end of every `step_planes`-th accepted step + (SEP 14); ``None`` is ``0``, or ``1`` with `tdse` (SEP 20 section 5.8). + - ``save_planes``: positions to save at, m. Give this or `n_save`. + - ``stats``: which statistics to record; ``None`` takes + :func:`solprop.stats.default_spec` for a multimode run with the profiles, or + for a mode-averaged one under `mode_average`, plus every statistic only a + backend can compute. + - ``node_budget``: bytes of node-chunk buffers the response pass may hold, an + execution setting; ``None``, the default, leaves the backend's automatic + choice. + - ``backend``: registry name, or ``"auto"`` (the default) for ``native``, which + refuses if it isn't installed or this processor is below its baseline. There + is no fallback, and import or construction errors stay visible. + - ``materials``: take the run's built-in materials from optmat under this + selection: a gas or fraction mapping of built-in gases, ``"air"`` and + ``Gas.Air`` resolve through :func:`solprop.physdata.species` and + :meth:`solprop.physdata.mixtures.Mixture.air` with it, and the default + cladding (``cladding=None`` with a numbered mode set) is optmat's recommended + silica. A supplied mixture, mode set, cladding or susceptibility is taken as + it is. The run records its material manifest. ``None`` (the default) is + ``MaterialSelection()``. """ # The TDSE response owns the electronic response: build no other. atomic, plasma, raman, backend, n_save, step_planes = _entry_overrides( @@ -7487,10 +6710,7 @@ def __repr__(self) -> str: def _describe(self) -> tuple[str, list[tuple[str, object]]]: """Return the title and rows of the printed block. - Returns - ------- - tuple - The title and ``(key, value)`` rows. + - Returns: the title and ``(key, value)`` rows. """ return "SimulationRunner", [ ("simulation", repr(self._simulation)), @@ -7520,25 +6740,13 @@ def __exit__(self, *details: object) -> None: self.close() @property - def closed(self) -> bool: - """Return whether the runner, and its backend, are closed. - - Returns - ------- - bool - True once :meth:`close` has run. - """ + def closed(self) -> bool: # numpydoc ignore=RT01 + """Whether the runner, and its backend, are closed.""" return self._closed @property - def backend(self) -> Backend: - """Return the backend every run is stepped on. - - Returns - ------- - solprop.backends.protocol.Backend - The one backend the runner constructed. - """ + def backend(self) -> Backend: # numpydoc ignore=RT01 + """Backend every run is stepped on.""" return self._backend def close(self) -> None: @@ -7642,18 +6850,7 @@ def run( self._lock.release() def _takes(self, simulation: Simulation) -> bool: - """Return whether a simulation is this runner's but for its input. - - Parameters - ---------- - simulation : Simulation - A simulation that may share this one's translation. - - Returns - ------- - bool - Whether its problem passes :func:`check_derived_problem`. - """ + """Return whether a simulation is this runner's but for its input.""" try: check_derived_problem( self._problem, @@ -7679,17 +6876,6 @@ def _run_point( For a scan's worker, which has built the point's simulation and holds its record already. Unlike :meth:`run` it honours `stop`, and the handler's record is the simulation's own. - - Parameters - ---------- - simulation : Simulation - The point. - output : solprop.output.schema.Output - Where its planes go. - progress : ProgressReporter, {'auto', 'bar', 'log', 'none'} or None - Where progress goes. - stop : object or None - The point's stop flag. """ with self._lock: simulation._run(output, progress=progress, stop=stop, kept=self._backend) @@ -7709,24 +6895,6 @@ def _load( The derived simulation and its problem are this frame's alone: they go when it returns, and the start alone holds the converted launch of a modal route or the view of the input, until the run's session takes it. - - Parameters - ---------- - initial : Pulse, SpatialPulse or numpy.ndarray - The input. - output : solprop.output.schema.Output or pathlib.Path or str or None - Where its planes go. - configuration : collections.abc.Mapping or None - Caller keys for its record. - noise : NoiseRealisation, {'keep'} or None - Its noise. - input_identity : str or None - What the caller states an array input to be. - - Returns - ------- - _LoadedRun - What the march needs, which holds no input. """ derived = self._simulation.with_input( _borrowed_input(initial, stated=input_identity is not None), @@ -7765,19 +6933,7 @@ def _march( ) -> Output: """Run the loaded input's save-plane loop and finish its handler. - Parameters - ---------- - loaded : _LoadedRun - The run :meth:`_load` left ready. - progress : ProgressReporter, {'auto', 'bar', 'log', 'none'} or None - Where progress goes. - progress_period : float - Seconds between periodic lines, s. - - Returns - ------- - solprop.output.schema.Output - The handler. + - ``progress_period``: seconds between periodic lines, s. """ settings = self._simulation.settings problem, handler = loaded.problem, loaded.handler @@ -7816,19 +6972,9 @@ def _borrowed_input( inside the call, so an array needs no copy unless the record outlives it: one recorded inline stays a copy, in the handler's record. - Parameters - ---------- - initial : Pulse, SpatialPulse or numpy.ndarray - The runner's input. - stated : bool - Whether the caller states its identity, which is then all the record keeps. - - Returns - ------- - Pulse, SpatialPulse or numpy.ndarray - A read-only view of a complex128 array the record does not hold, borrowed - (:func:`solprop._ownership.borrow`) so that nothing copies it; `initial` - itself otherwise. The caller's own flags are not touched. + - Returns: a read-only view of a complex128 array the record does not hold, borrowed + (:func:`solprop._ownership.borrow`) so that nothing copies it; `initial` itself + otherwise. The caller's own flags are not touched. """ if ( isinstance(initial, np.ndarray) @@ -7845,16 +6991,6 @@ def _without_input(problem: PropagationProblem) -> PropagationProblem: The save-plane loop reads the launch position and never the launch, so the state is replaced by a broadcast of one zero, which holds no volume (:class:`~solprop. translate.problem.Initial`). The problem is good for the march alone. - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The derived problem, whose input is already in the backend. - - Returns - ------- - solprop.translate.problem.PropagationProblem - The problem, sharing every leaf but ``initial.field0``. """ # A broadcast of a held zero is itself held, so Initial takes it uncopied. placeholder = np.broadcast_to( @@ -7868,16 +7004,7 @@ def _without_input(problem: PropagationProblem) -> PropagationProblem: def _realisation(problem: PropagationProblem) -> NoiseRealisation | None: """Return a derived problem's noise as the backend loads it, or ``None``. - Parameters - ---------- - problem : PropagationProblem - The derived problem, whose ``initial.noise0`` is already in the backend's - amplitude units. - - Returns - ------- - solprop.noise.NoiseRealisation or None - Its realisation, or ``None`` without noise. + - Returns: its realisation, or ``None`` without noise. """ initial = problem.initial if initial.noise0 is None or initial.noise_model == "none": @@ -8002,23 +7129,7 @@ def check_derived_problem( def _simulation_mixture(simulation: Simulation) -> Mixture: - """Require a material declaration the update can identify by component. - - Parameters - ---------- - simulation : Simulation - Existing modal or spatial propagation. - - Returns - ------- - Mixture - The material shared by its modes, or carried by its spatial medium. - - Raises - ------ - DriverError - If no mixture declaration is present or modes carry different mixtures. - """ + """Require a material declaration the update can identify by component.""" if simulation.medium is not None and isinstance( simulation.medium.material, Mixture ): @@ -8041,23 +7152,6 @@ def _refuse_plasma_settings( Envelope plasma cannot carry the third harmonic, and a running-integral rule belongs to the field-grid plasma column: given beside envelope plasma, or with the plasma off, nothing would read it. - - Parameters - ---------- - integration : {'band', 'trapezoid'} or None - The ``plasma_integration`` keyword. - envelope : bool - Whether the plasma is the envelope form. - active : bool - Whether any plasma response is selected. - thg : bool - Whether the third harmonic is kept. - - Raises - ------ - DriverError - If envelope plasma meets the harmonic, or a rule is given beside envelope - plasma or with the plasma off. """ if envelope and active and thg: msg = "envelope plasma cannot be combined with THG" @@ -8095,19 +7189,6 @@ def _refuse_idle_preionisation(mixture: Mixture, responses: Sequence[object]) -> A component's preionisation reaches the run only through its plasma or envelope-plasma response. Without one the declaration would be recorded in the run's metadata and ignored by the propagation, so it is refused by name. - - Parameters - ---------- - mixture : Mixture - The ordered populations with their declared fractions. - responses : sequence of object - The responses the run will compile. - - Raises - ------ - DriverError - Naming the first component whose preionisation is a path or a nonzero number - and which has no plasma response. """ carried = { _population_label(one, mixture) @@ -8129,20 +7210,7 @@ def _refuse_idle_preionisation(mixture: Mixture, responses: Sequence[object]) -> def _population_label( response: PlasmaResponse | EnvelopePlasmaResponse, mixture: Mixture ) -> str: - """Resolve an existing plasma declaration to its already-validated population. - - Parameters - ---------- - response : PlasmaResponse or EnvelopePlasmaResponse - Explicit label or single-species rate declaration. - mixture : Mixture - Ordered component labels and species. - - Returns - ------- - str - The unique matching label. - """ + """Resolve an existing plasma declaration to its already-validated population.""" if response.species is not None: return response.species if len(mixture.components) == 1: @@ -8164,19 +7232,9 @@ def _same_operator_function( ) -> ParameterExpansion | None: """Keep terms only when every dependency outside their axes is unchanged. - Parameters - ---------- - source, updated : Simulation - Geometry-preserving simulations on the same grid and frame. - old, new : Mixture - Species-equivalent material declarations. - expansion : ParameterExpansion or None - Previously sampled coefficients and axis names. - - Returns - ------- - ParameterExpansion or None - The old coefficients when the function is identical, else None with a log. + - ``expansion``: previously sampled coefficients and axis names. + - Returns: the old coefficients when the function is identical, else None with a + log. """ if source.settings.prefactor_index != updated.settings.prefactor_index: logger.info("Mixture update: expansion reuse refused; prefactor_index changed") @@ -8230,22 +7288,7 @@ def _mixture_reuse( new: Mixture, problem: PropagationProblem, ) -> TranslationReuse: - """Collect eligible immutable translation data after a material-path update. - - Parameters - ---------- - source, updated : Simulation - Geometry-preserving simulations with identical grids and noise. - old, new : Mixture - Species-equivalent material declarations. - problem : PropagationProblem - Source translation whose immutable arrays may be shared. - - Returns - ------- - TranslationReuse - Transverse transform, eligible expansion terms and unchanged Raman samples. - """ + """Collect eligible immutable translation data after a material-path update.""" before = { response.species or old.labels[0]: response for response in source.responses @@ -8279,19 +7322,7 @@ def _mixture_reuse( def _undamped_model_key(response: RamanResponse) -> object: - """Describe only the inputs to the undamped Raman samples. - - Parameters - ---------- - response : RamanResponse - The component response whose kind and label the update preserves. - - Returns - ------- - object - Serialised model data, excluding the separate dephasing field of the - built-in oscillator and rotational-line models. Other models keep all data. - """ + """Describe only the inputs to the undamped Raman samples.""" models = [] for term in response.tensor_terms: model = term.model @@ -8328,26 +7359,7 @@ def _check_wall_declaration( down -- in :func:`solprop.modes.capillary.wall_operator` and at the backend -- so nothing depends on this one being reached. - Parameters - ---------- - wall : solprop.modes.capillary.CoherentWall or None - What was declared. ``None`` leaves every other argument alone. - backend : str - The ``backend=`` keyword, before :func:`solprop.backends.registry.resolve_name` - turns ``"auto"`` into a concrete choice. - model : {'full', 'reduced'} - The ``model=`` keyword. - loss : bool, float or callable - The ``loss=`` keyword. - lattice : solprop.modes.antiresonant.TubeLattice or None - The ``lattice=`` keyword. - taper_coupling : bool, optional - The ``taper_coupling=`` keyword, which a wall cannot decline. - - Raises - ------ - DriverError - Naming the keyword to change and why the block cannot carry its value. + - ``wall``: what was declared. ``None`` leaves every other argument alone. """ if wall is None: return @@ -8422,16 +7434,6 @@ def _warn_adiabatic_taper(modes: Sequence[Mode]) -> None: capillary's contiguous ``HE_1m`` set carries it by default (:func:`solprop.modes.capillary.taper_coupling_block`), so what warns is a moving set the coupling is not derived for. - - Parameters - ---------- - modes : sequence of Mode - The run's mode set. - - Warns - ----- - AdiabaticTaperWarning - When more than one channel is propagated and any channel's shape moves. """ channels = tuple(modes) if len(channels) < 2: @@ -8473,23 +7475,7 @@ def _flow_radius( are not modelled: they keep the uniform law, and warn if their widths or gap vary under a pressure gradient. - Parameters - ---------- - modes : int or sequence of Mode - The supplied set. - pressure : float, sequence, ZProfile or None - The declared pressure, to tell a gradient from a constant fill. - - Returns - ------- - ZProfile or None - The first mode's core radius in m, or ``None`` for a guide without one. - - Warns - ----- - GasFlowWarning - For a rectangular guide whose widths, or a slab whose gap, vary beside a - pressure gradient. + - Returns: the first mode's core radius in m, or ``None`` for a guide without one. """ if isinstance(modes, int) or not modes: return None @@ -8527,18 +7513,7 @@ def _warn_boreless_gradient(mixture: Mixture, radius: ZProfile | None) -> None: Mixture, was built by its caller; if it does not know the radius, its ``p^2`` is linear in ``z`` although a taper's is linear in ``int a^-4 dz``. - Parameters - ---------- - mixture : Mixture - The resolved fill. - radius : ZProfile or None - The guide's core radius in m, if it has one. - - Warns - ----- - GasFlowWarning - When the shared fill is a varying gradient without a radius and the core - radius varies. + - ``radius``: the guide's core radius in m, if it has one. """ shared = mixture.shared if ( @@ -8575,15 +7550,8 @@ def _warn_capillary_walkoff( reference frequency alone: a band-wide span would be dominated by band edges carrying nothing. - Parameters - ---------- - modes : sequence of solprop.modes.protocol.Mode - The mode set, the first mode being the one the frame follows. - grid : solprop.grid.TimeGrid - The grid, for its length, its reference frequency and its temporal collar. - boundary : solprop.boundaries.Absorber - The collar the run will use, for the length that sets its temporal strength. - A spectral-only collar has no temporal part to check. + - ``boundary``: the collar the run will use, for the length that sets its temporal + strength. A spectral-only collar has no temporal part to check. """ if len(modes) < 2 or not np.any(boundary.temporal_rate): return @@ -8605,12 +7573,7 @@ def _check_initial_position( ) -> None: """Validate an absolute launch before translation can normalize its field. - Parameters - ---------- - simulation : Simulation - Declared optical input, grid and absolute launch position in m. - settings : SimulationSettings - Candidate save schedule and stepping choices. + - ``simulation``: declared optical input, grid and absolute launch position in m. """ z = simulation.initial_z if ( @@ -8646,21 +7609,9 @@ def _validate_resume( ) -> int: """Validate restart position and budgets before preparing atomic resources. - Parameters - ---------- - problem : PropagationProblem - Translated scientific problem. - request : solprop.translate.run_request.RunRequest - Its run's request, with the absolute save positions in m. - state : ResumeState - Saved field, accepted position and cumulative accounting. - execution : TDSEExecution or None - Current atomic budget, if this is a TDSE run. - - Returns - ------- - int - Index of the next scheduled save strictly beyond the accepted position. + - ``request``: its run's request, with the absolute save positions in m. + - ``execution``: current atomic budget, if this is a TDSE run. + - Returns: index of the next scheduled save strictly beyond the accepted position. """ planes = request.observation.save_planes next_plane = int(np.searchsorted(planes, state.plane.z, side="right")) @@ -8714,20 +7665,7 @@ def _check_tdse_archive( ) -> None: """Refuse an archive request this run cannot honour, before any work. - Parameters - ---------- - archive : object - The request, checked here to be a ``TDSEArchive`` or None. - problem : PropagationProblem - The translated problem. - handler : Output - The optical output, not yet opened. - - Raises - ------ - DriverError - If the request is not a :class:`~solprop.output.TDSEArchive`, the run has - no TDSE response, or the archive's path is the optical file's. + - ``archive``: the request, checked here to be a ``TDSEArchive`` or None. """ if archive is None: return @@ -8747,15 +7685,7 @@ def _check_tdse_archive( def _same_file(first: Path, second: Path) -> bool: """Return whether two paths name the same file, existing or not. - Parameters - ---------- - first, second : pathlib.Path - The paths. - - Returns - ------- - bool - True when they resolve to one location. + - Returns: True when they resolve to one location. """ if first.exists() and second.exists(): return first.samefile(second) @@ -8770,15 +7700,7 @@ def _recorded_archive_uuid(state: ResumeState) -> str | None: in that execution's outcome, not in the file's original record. A TDSE resume always has an outcome (:func:`_validate_resume`). - Parameters - ---------- - state : ResumeState - What the file holds. - - Returns - ------- - str or None - The UUID, or None when no execution of the run archived. + - Returns: the UUID, or None when no execution of the run archived. """ outcome = state.outcome # `_validate_resume` has refused a TDSE file without its ledgers and outcome. @@ -8810,25 +7732,8 @@ def _open_tdse_archive( # noqa: PLR0917 - called positionally ) -> TDSEArchiveWriter | None: """Open the run's archive session, continuing its own run's archive on a resume. - Parameters - ---------- - archive : TDSEArchive or None - The request, already checked by :func:`_check_tdse_archive`. - problem : PropagationProblem - The translated problem, with its TDSE descriptor. - handler : Output - The optical output. - state : ResumeState or None - What a resumed file holds, or None for a fresh run. - metadata : RunMetadata - The run's record. - execution : TDSEExecution - The atomic resources. - - Returns - ------- - TDSEArchiveWriter or None - The open session, or None without a request. + - ``state``: what a resumed file holds, or None for a fresh run. + - Returns: the open session, or None without a request. """ descriptor = tdse_descriptor(problem.route) if archive is None or descriptor is None: @@ -8873,18 +7778,7 @@ def _close_run( The backend before the atomic binding, the binding before the archive that records it, and the optical output last. - Parameters - ---------- - backend : Backend or None - The optical backend. - binding : TDSEBinding or None - The atomic binding. - archive : TDSEArchiveWriter or None - The archive session. - handler : Output - The optical output. - failure : BaseException or None - The error the run is raising, if any. + - ``failure``: the error the run is raising, if any. """ closing = failure for resource in (backend, binding, archive, handler): @@ -8906,17 +7800,7 @@ def _close_tdse_archive( ) -> None: """Close the archive session, never masking the run's own error. - Parameters - ---------- - archive : TDSEArchiveWriter or None - The session. - failure : BaseException or None - The error the run is raising, if any. - - Raises - ------ - TDSEArchiveError - If closing fails and the run is not already raising. + - ``failure``: the error the run is raising, if any. """ if archive is None: return @@ -8934,19 +7818,7 @@ def _resolve_tdse_grid( ) -> tuple[TimeGrid, SimulationSettings]: """Resolve the response lattice once, before building the optical input. - Parameters - ---------- - grid : TimeGrid - Original optical grid, before response refinement. - responses : sequence of object - Declared physical responses. - settings : SimulationSettings - Original collar and execution settings. - - Returns - ------- - tuple - Resolved grid and settings with a matching fine temporal collar. + - Returns: resolved grid and settings with a matching fine temporal collar. """ atomic = [one for one in responses if isinstance(one, TDSEGasResponse)] if not atomic: @@ -8962,15 +7834,7 @@ def _resolve_tdse_grid( def _check_tdse_execution(simulation: Simulation, settings: SimulationSettings) -> None: - """Refuse unsupported execution choices before any atomic preparation. - - Parameters - ---------- - simulation : Simulation - Declared response, geometry and optical input. - settings : SimulationSettings - Candidate execution and integration choices. - """ + """Refuse unsupported execution choices before any atomic preparation.""" atomic = any(isinstance(one, TDSEGasResponse) for one in simulation.responses) if not atomic: if settings.tdse_execution is not None: @@ -9007,19 +7871,7 @@ def _refined_tdse_collar( and the band from the reference length, the temporal rates from the collar's in-stage length, or as zeros for a spectral-only collar. - Parameters - ---------- - settings : SimulationSettings - Original absorber definition and its physical lengths in m. - parent : TimeGrid - Grid on which an inherited standard collar was defined. - refined : TimeGrid - Resolved response grid with unchanged coarse coordinates and period. - - Returns - ------- - SimulationSettings - Settings carrying rates sampled on the resolved fine lattice. + - ``settings``: original absorber definition and its physical lengths in m. """ boundary = settings.absorber if boundary is None or boundary.temporal_rate_fine.shape == (refined.n_to,): @@ -9030,18 +7882,9 @@ def _refined_tdse_collar( def temporal(grid: TimeGrid, axis: np.ndarray) -> np.ndarray: """Return the collar's temporal rate on a time axis of a grid, 1/m. - Parameters - ---------- - grid : TimeGrid - The grid whose temporal collar the rate follows. - axis : numpy.ndarray - Its coarse or fine time axis, s. - - Returns - ------- - numpy.ndarray - The bounded profile at the in-stage length, or zeros for a - spectral-only collar. + - ``axis``: its coarse or fine time axis, s. + - Returns: the bounded profile at the in-stage length, or zeros for a + spectral-only collar. """ if not boundary.temporal: return np.zeros(axis.size) @@ -9080,20 +7923,8 @@ def _replaced_launch( own copy; on a modal route it is the power conversion of that copy, a second array beside it. - Parameters - ---------- - original : PropagationProblem - The problem the input replaces the launch of. - field0 : numpy.ndarray - The replacement input, in true sqrt(W) on a modal route. - launch_z : float - Where it is launched, m. - - Returns - ------- - numpy.ndarray - The launch, which :class:`~solprop.translate.problem.Initial` takes without a - further copy. + - ``field0``: the replacement input, in true sqrt(W) on a modal route. + - ``launch_z``: where it is launched, m. """ if original.transform.geometry is not None: return _state(field0, original.shape[-1]) @@ -9108,20 +7939,7 @@ def _check_input_identity( identity: str | None, initial: Pulse | SpatialPulse | SpatialPulseSum | np.ndarray | CouplingLaunch, ) -> None: - """Refuse a stated identity that is empty, or of anything but an array input. - - Parameters - ---------- - identity : str or None - What the caller states the input to be. - initial : Pulse, SpatialPulse, numpy.ndarray or solprop.coupling.CouplingLaunch - The input. - - Raises - ------ - DriverError - Saying which. - """ + """Refuse a stated identity that is empty, or of anything but an array input.""" if identity is None: return if not identity: @@ -9143,19 +7961,9 @@ def _given_input( an input before the simulation copied it still resumes; a view of a read-only owner cannot be made writeable, so the record and the run cannot be changed through it. - Parameters - ---------- - given : numpy.ndarray - The caller's array. - held : numpy.ndarray - The simulation's complex128 copy of it. - - Returns - ------- - numpy.ndarray - A read-only view of `held` when `given` is complex128, otherwise of a held copy - of `given` in its own dtype; :class:`~solprop.translate.problem.Initial` takes - either as held. + - Returns: a read-only view of `held` when `given` is complex128, otherwise of a + held copy of `given` in its own dtype; :class:`~solprop.translate.problem.Initial` + takes either as held. """ # The whole dtype, byte order included: the record serialises its exact string. source = held if given.dtype == held.dtype else _held_block(given, given.dtype) @@ -9165,19 +7973,7 @@ def _given_input( def _check_planar_input(shape: tuple[int, ...], n_columns: int, n_omega: int) -> None: """Refuse a planar input whose shape is not ``(n_p*n_y, 1, n_omega)``. - Parameters - ---------- - shape : tuple of int - The input's shape. - n_columns : int - The planar grid's ``(p, k_y)`` columns. - n_omega : int - The grid's frequency bins. - - Raises - ------ - DriverError - Naming both shapes. + - ``shape``: the input's shape. """ if shape != (n_columns, 1, n_omega): msg = ( @@ -9190,19 +7986,7 @@ def _check_planar_input(shape: tuple[int, ...], n_columns: int, n_omega: int) -> def _check_spatial_input(shape: tuple[int, ...], n_columns: int, n_omega: int) -> None: """Refuse a spatial input whose shape is not ``(n_columns, n_pol, n_omega)``. - Parameters - ---------- - shape : tuple of int - The input's shape. - n_columns : int - The spatial grid's columns. - n_omega : int - The grid's frequency bins. - - Raises - ------ - DriverError - Naming the shape it requires. + - ``shape``: the input's shape. """ if len(shape) != 3 or shape[0] != n_columns or shape[2] != n_omega: msg = "spatial input must have shape (n_columns, n_pol, n_omega)" @@ -9214,24 +7998,9 @@ def _built( ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: """Return the initial spectrum, building a pulse against the grid when given one. - Parameters - ---------- - initial : solprop.pulses.Pulse or numpy.ndarray - A pulse, or an already-built spectrum. - grid : solprop.grid.TimeGrid - The grid to build against. - - Returns - ------- - numpy.ndarray - The spectrum, complex128, shape ``(n_modes, n_pol, n_omega)``, read-only and - the simulation's own: a copy of an array (of its repeated block for a - broadcast), or a solprop pulse's new build. - - Raises - ------ - DriverError - If `initial` is neither an array nor a pulse. + - Returns: the spectrum, complex128, shape ``(n_modes, n_pol, n_omega)``, read-only + and the simulation's own: a copy of an array (of its repeated block for a + broadcast), or a solprop pulse's new build. """ if isinstance(initial, np.ndarray): # Copied now, so that changing the caller's array later changes nothing, as @@ -9259,17 +8028,7 @@ def _built_state( as it is; any other pulse's build may be an array the pulse keeps, and is copied (:class:`~solprop.translate.problem.Initial`). - Parameters - ---------- - pulse : object - The pulse that built it. - built : object - What its ``build`` returned. - - Returns - ------- - numpy.ndarray - The spectrum, complex128, read-only and held. + - Returns: the spectrum, complex128, read-only and held. """ spectrum = np.asarray(built) build = getattr(type(pulse), "build", None) @@ -9290,16 +8049,6 @@ def _interpolated(state: ResumeState) -> bool: A landed plane is the end of an accepted step of the run; an interpolated one lies inside a step. The entrance is the initial field. - - Parameters - ---------- - state : solprop.output.hdf5.ResumeState - What the file holds. - - Returns - ------- - bool - Whether the plane is not a state the march passed through. """ return state.interpolated @@ -9314,28 +8063,6 @@ def _open_output( The resume gate (ADR-066): the file's configuration hash must equal `metadata`'s. Whether work is left is decided by :func:`_continues`. - - Parameters - ---------- - output : solprop.output.schema.Output or pathlib.Path or str or None - The caller's choice. - metadata : solprop.output.schema.RunMetadata - The record of the run about to start, which a file is compared with. - resume : bool or {'interpolated'} - Whether to continue an existing file; ``"interpolated"`` also from a plane - interpolated inside a step. - - Returns - ------- - tuple - The handler, and the state to resume from or ``None``. - - Raises - ------ - DriverError - If a resume is asked for and `output` is not a path. - OutputError - If the file exists and its hash differs; the message names what changed. """ if resume and metadata.configuration.get("_benchmark_plasma_source"): raise DriverError("gated plasma benchmark output cannot be resumed") @@ -9356,16 +8083,7 @@ def _open_output( def _check_resume_choice(resume: object) -> None: """Refuse a ``resume`` value that is not one of its three. - Parameters - ---------- - resume : object - The caller's value. - - Raises - ------ - ValueError - If it is not ``False``, ``True`` or ``"interpolated"``: a mistyped word would - otherwise read as ``True``. + A mistyped word would otherwise read as ``True``. """ if resume is True or resume is False or resume == "interpolated": return @@ -9376,21 +8094,9 @@ def _check_resume_choice(resume: object) -> None: def _work_left(request: RunRequest, state: ResumeState, next_plane: int) -> bool: """Return whether a file has anything left to compute, by position. - Parameters - ---------- - request : solprop.translate.run_request.RunRequest - The run's request, for its save planes. - state : ResumeState - What the file holds. - next_plane : int - The next save plane beyond the file's position. - - Returns - ------- - bool - True while a save plane lies ahead or a terminal checkpoint is still owed - its plane. Decided by position, not status: a file killed after its last - plane has no work left, though its status reads ``running``. + Decided by position, not status: a file killed after its last plane has no work + left, though its status reads ``running``; a terminal checkpoint still owes its + plane. """ return ( state.checkpoint is not None @@ -9404,20 +8110,6 @@ def _refuse_interpolated(path: Path, state: ResumeState, resume: _ResumeChoice) An interpolated plane is not a state the march passed through, so a run restarted there is not the run it continues (SEP 14 section 5); ``resume="interpolated"`` accepts that. - - Parameters - ---------- - path : pathlib.Path - The file. - state : ResumeState - What it holds. - resume : bool or {'interpolated'} - The caller's request. - - Raises - ------ - DriverError - If the file ends on an interpolated plane and the request does not accept it. """ if resume == "interpolated" or not _interpolated(state): return @@ -9438,29 +8130,7 @@ def _continues( ) -> bool: """Say whether a resume of a file computes anything, once its hash has passed. - Parameters - ---------- - path : pathlib.Path - The file. - request : solprop.translate.run_request.RunRequest - The run's request, for its save planes. - state : ResumeState - What the file holds. - next_plane : int - The next save plane beyond the file's position. - resume : bool or {'interpolated'} - The caller's request. - - Returns - ------- - bool - ``True`` when work is left, and ``False`` for a finished file. - - Raises - ------ - DriverError - If work is left from a plane interpolated inside a step and the request does - not accept that. + - Returns: ``True`` when work is left, and ``False`` for a finished file. """ if not _work_left(request, state, next_plane): return False @@ -9477,28 +8147,11 @@ def _gnlse_grid( ) -> TimeGrid: """Return the envelope grid a GNLSE run propagates on. - Parameters - ---------- - grid : solprop.grid.TimeGrid or None - A grid given by the caller, or ``None`` to build one. - length : float - Fibre length, m. - wavelength : float - Reference wavelength, m. - wavelength_limits : tuple of float or None - Window limits, m. - trange : float or None - Time window, s. - - Returns - ------- - solprop.grid.TimeGrid - The envelope grid. - - Raises - ------ - DriverError - If a field grid is given, or a grid can be neither taken nor built. + - ``grid``: a grid given by the caller, or ``None`` to build one. + - ``length``: fibre length, m. + - ``wavelength``: reference wavelength, m. + - ``wavelength_limits``: window limits, m. + - ``trange``: time window, s. """ if grid is not None: if grid.kind != "envelope": @@ -9542,44 +8195,18 @@ def _gnlse_fibre( ) -> GnlseProblem: """Map one dispersion source and one nonlinear coefficient onto the GNLSE. - Parameters - ---------- - gamma : float or None - Nonlinear coefficient in 1/(W m), absent beside `n2`. - betas : array_like or None - Taylor coefficients about the reference frequency, s^n/m. - beta : object - A pair of frequency and beta arrays, a callable, or None. - grid : solprop.grid.TimeGrid - Envelope grid whose active band samples a callable dispersion. - wavelength : float - Positive reference wavelength, m. - loss : float or None - Power loss in dB/m; None means omitted. Refused beside `fibre`. - shock : bool - Keep the prefactor's frequency dependence. - fibre : Mode or IndexTable or None, optional - Whole dispersion and loss, including a positive propagation area for `n2`. - n2 : float, str or None, optional - Nonlinear index in m^2/W, or ``"recommended"`` under a `selection`; - requiring `fibre`. - raman : bool, optional - Include the silica Raman response. - raman_fraction : float or None, optional - Share of the nonlinearity held back from the Kerr term, between zero and one; - None resolves as in :func:`solprop.nonlinear.gnlse.problem_from`. - selection : MaterialSelection or None, optional - The run's material selection, which takes silica's response from optmat. - - Returns - ------- - solprop.nonlinear.gnlse.GnlseProblem - Resolved mode, nonlinear responses and their coefficient mapping. - - Raises - ------ - DriverError - If dispersion, nonlinearity, area, loss or z dependence is incompatible. + - ``gamma``: nonlinear coefficient in 1/(W m), absent beside `n2`. + - ``betas``: Taylor coefficients about the reference frequency, s^n/m. + - ``beta``: a pair of frequency and beta arrays, a callable, or None. + - ``wavelength``: positive reference wavelength, m. + - ``loss``: power loss in dB/m; None means omitted. Refused beside `fibre`. + - ``fibre``: whole dispersion and loss, including a positive propagation area for + `n2`. + - ``n2``: nonlinear index in m^2/W, or ``"recommended"`` under a `selection`; + requiring `fibre`. + - ``raman_fraction``: share of the nonlinearity held back from the Kerr term, + between zero and one; None resolves as in + :func:`solprop.nonlinear.gnlse.problem_from`. """ if sum(value is not None for value in (betas, beta, fibre)) != 1: raise DriverError( @@ -9648,26 +8275,8 @@ def _gnlse_mode( ) -> Mode: """Resolve a supplied table or mode with its propagation area at the pump. - Parameters - ---------- - fibre : Mode or IndexTable - Whole linear response. - wavelength : float - Positive reference wavelength, m: where a table's area column and a - provider channel's surrogate area are evaluated. - needs_area : bool - Whether the caller supplied n2 rather than gamma. - - Returns - ------- - Mode - Mode with its area selected at the reference wavelength, or carrying the - declared dispersion and area paths of a fibre whose geometry varies along z. - - Raises - ------ - DriverError - If n2 has no table area, or the fibre's entrance area is not usable. + - ``wavelength``: positive reference wavelength, m: where a table's area column and + a provider channel's surrogate area are evaluated. """ if isinstance(fibre, IndexTable): if needs_area and fibre.area is None: @@ -9710,22 +8319,7 @@ def _dispersion_table( (:func:`solprop.linear.dispersion_operator`) and a spline refuses to extrapolate beyond its data. - Parameters - ---------- - beta : object - A pair of arrays, or a callable of ``omega``. - grid : solprop.grid.TimeGrid - The grid supplying the sampling points. - - Returns - ------- - tuple - The frequencies in rad/s and the propagation constant in 1/m. - - Raises - ------ - DriverError - If `beta` is neither a pair of arrays nor a callable. + - Returns: the frequencies in rad/s and the propagation constant in 1/m. """ if callable(beta): nodes = np.unique(grid.omega[grid.active_mask]) @@ -9756,36 +8350,12 @@ def _shaped_pulse( ) -> GaussianPulse | SechPulse | PulseSum: """Return the shaped input pulse a keyword interface starts from. - Parameters - ---------- - shape : {'gauss', 'sech'} - Shape of the input. - wavelength : float - Centre wavelength, m, and the reference of the spectral phase. - fwhm : float or None - Intensity full width at half maximum, s. - width : float or None - Natural width of a ``sech**2``, s. - energy : float or None - Pulse energy, J. - peak_power : float or None - Peak power, W. - phases : array_like - Taylor coefficients of the optics-convention spectral phase. - pulses : sequence of solprop.pulses.Pulse or None, optional - Several pulses launched together, in place of the single-pulse keywords. - - Returns - ------- - solprop.pulses.GaussianPulse or solprop.pulses.SechPulse or solprop.pulses.PulseSum - The pulse specification. - - Raises - ------ - DriverError - If the shape is unknown, a Gaussian is asked for by a natural width, which - solprop specifies by its full width at half maximum only, or `pulses` is given - beside a single-pulse keyword. + - ``wavelength``: centre wavelength, m, and the reference of the spectral phase. + - ``fwhm``: intensity full width at half maximum, s. + - ``width``: natural width of a ``sech**2``, s. + - ``energy``: pulse energy, J. + - ``peak_power``: peak power, W. + - ``phases``: Taylor coefficients of the optics-convention spectral phase. """ if pulses is not None: given = [ @@ -9854,24 +8424,9 @@ def _save_planes( ) -> Float[np.ndarray, " n_z"]: """Return the save planes a keyword interface asks for. - Parameters - ---------- - length : float - Fibre length, m. - save_planes : array_like or None - Positions given by the caller, m. - n_save : int or None - Planes to store, counting the one at ``z = 0``. - - Returns - ------- - numpy.ndarray - The planes after ``z = 0``, m, shape ``(n_z,)``. - - Raises - ------ - DriverError - If both or neither way of asking is usable, or the count is below two. + - ``length``: fibre length, m. + - ``save_planes``: positions given by the caller, m. + - Returns: the planes after ``z = 0``, m, shape ``(n_z,)``. """ if save_planes is not None: if n_save is not None: @@ -9891,43 +8446,22 @@ def _save_planes( def _scheme(settings: SimulationSettings) -> AbsorberScheme: - """Return the collar scheme the settings resolve to. - - Parameters - ---------- - settings : SimulationSettings - The run's settings. - - Returns - ------- - {'nonlinear', 'split_step'} - The scheme, as a plain string: the named one or the route's default. - """ + """Return the collar scheme the settings resolve to.""" return cast("AbsorberScheme", str(settings.absorber_scheme)) def _spatial_settings(settings: SimulationSettings, route: str) -> SimulationSettings: """Give a spatial run's settings the route's defaults (SEP 37, SEP 21). - Parameters - ---------- - settings : SimulationSettings - The caller's settings. - route : {'radial', 'free', 'planar'} - The interface they are for. - - Returns - ------- - SimulationSettings - `settings`, reading the route's row of - :data:`~solprop.driver.defaults.ROUTE_DEFAULTS` for whatever the caller left: - the controller (:data:`~solprop.driver.defaults.SPATIAL_RTOL` for radial and - planar runs, :data:`~solprop.driver.defaults.FREE_RTOL` for Cartesian and line - ones, in the run's precision), the integrator (``RungeKutta()`` in free - space, whose Adams history would be too large) and the collar scheme - (``"split_step"`` unless planes are interpolated). Settings that already - belong to a route, this one or another (settings copied from a modal run, for - a comparison), keep what that route resolved and are returned as they are. + - Returns: `settings`, reading the route's row of + :data:`~solprop.driver.defaults.ROUTE_DEFAULTS` for whatever the caller left: the + controller (:data:`~solprop.driver.defaults.SPATIAL_RTOL` for radial and planar + runs, :data:`~solprop.driver.defaults.FREE_RTOL` for Cartesian and line ones, in + the run's precision), the integrator (``RungeKutta()`` in free space, whose Adams + history would be too large) and the collar scheme (``"split_step"`` unless planes + are interpolated). Settings that already belong to a route, this one or another + (settings copied from a modal run, for a comparison), keep what that route + resolved and are returned as they are. """ if settings._route is not None: return settings @@ -9942,30 +8476,11 @@ def _with_boundary( ) -> SimulationSettings: """Apply a spatial interface's absorber keywords to its settings (SEP 21 section 5). - Parameters - ---------- - settings : SimulationSettings - The settings given. - grid : TimeGrid - The run's temporal grid. - absorber : Absorber, False or None - The collar given; ``False`` zeroes the temporal and spectral rates of the - settings' absorber and keeps its lengths (without one it builds the hard mask - alone); ``None`` leaves the settings' own. The transverse boundary is - ``spatial_absorber``'s (SEP 40). - scheme : {'nonlinear', 'split_step'} or None - The temporal scheme given; ``None`` leaves the settings' own. - - Returns - ------- - SimulationSettings - The settings with the keywords applied. - - Raises - ------ - DriverError - If a keyword conflicts with what the settings already declare, or a scheme is - given beside ``absorber=False``. + - ``absorber``: the collar given; ``False`` zeroes the temporal and spectral rates + of the settings' absorber and keeps its lengths (without one it builds the hard + mask alone); ``None`` leaves the settings' own. The transverse boundary is + ``spatial_absorber``'s (SEP 40). + - ``scheme``: the temporal scheme given; ``None`` leaves the settings' own. """ changes: dict[str, object] = {} if absorber is False: @@ -10010,25 +8525,7 @@ def _with_boundary( def _watched( settings: SimulationSettings, check: BoundaryCheck | None ) -> SimulationSettings: - """Apply a spatial constructor's ``boundary_check`` keyword to its settings. - - Parameters - ---------- - settings : SimulationSettings - The caller's settings. - check : solprop.monitor.BoundaryCheck or None - The keyword's value; the default leaves the settings' own. - - Returns - ------- - SimulationSettings - `settings`, watching the edges as the keyword asks. - - Raises - ------ - DriverError - If the keyword and the settings name two different checks. - """ + """Apply a spatial constructor's ``boundary_check`` keyword to its settings.""" if check is DEFAULT_BOUNDARY_CHECK or check == settings.boundary_check: return settings if settings.boundary_check != DEFAULT_BOUNDARY_CHECK: @@ -10054,26 +8551,7 @@ def _rechecked( is linear and normalised by the launch's energy. A new launch whose rms wavenumber lies above the one the band was derived from warns as well. - Parameters - ---------- - simulation : Simulation - The parent, for its grids and medium. - original : solprop.translate.problem.PropagationProblem - The parent's translated problem. - derived : solprop.translate.problem.PropagationProblem - The derived problem, with the new launch. - planes : numpy.ndarray - The derived run's save planes, m. - - Returns - ------- - solprop.spatial_absorber.SpatialAbsorberDesign or None - The design with the new launch's check. - - Raises - ------ - DriverError - If the check refuses the design for the new launch. + - ``planes``: the derived run's save planes, m. """ design = transverse_of(original.route) if design is None or not design.has_collar: @@ -10128,23 +8606,9 @@ def _launch_band( ) -> float | None: """Return the widest protected band a launch would derive, or ``None``. - Parameters - ---------- - specification : solprop.boundaries.SpatialAbsorber - A launch-derived specification. - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The grid. - dispersion : solprop.spatial.SpatialDispersion - The medium and the temporal grid. - launch : numpy.ndarray - The launch, canonical layout. - length : float - The run's length, m. - - Returns - ------- - float or None - The widest axis's band edge, rad/m; ``None`` when it cannot be resolved. + - ``launch``: the launch, canonical layout. + - ``length``: the run's length, m. + - Returns: the widest axis's band edge, rad/m; ``None`` when it cannot be resolved. """ try: return max( @@ -10159,16 +8623,6 @@ def _refuse_pending_check(problem: PropagationProblem) -> None: A coupled child is translated before its launch exists; its check runs when :meth:`Simulation.with_input` gives it one (SEP 40 section 5.2). - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The run's problem. - - Raises - ------ - DriverError - If its transverse design's check is pending. """ design = transverse_of(problem.route) if design is not None and design.check.get("status") == "pending": @@ -10185,22 +8639,7 @@ def _bounded( ) -> SimulationSettings: """Apply a spatial constructor's ``spatial_absorber`` keyword to its settings. - Parameters - ---------- - settings : SimulationSettings - The caller's settings. - spatial_absorber : solprop.boundaries.SpatialAbsorber, False or None - The keyword's value; ``None`` leaves the settings' own. - - Returns - ------- - SimulationSettings - `settings`, with the transverse boundary the keyword asks for. - - Raises - ------ - DriverError - If the keyword and the settings name two different boundaries. + - ``spatial_absorber``: the keyword's value; ``None`` leaves the settings' own. """ if spatial_absorber is None or spatial_absorber == settings.spatial_absorber: return settings @@ -10223,27 +8662,9 @@ def _shot_noise( that ignore the annotation, and the test session's ``beartype`` hook would otherwise reject the bad value before the check below is reached. - Parameters - ---------- - noise : {None, 'input', 'modified'} - Which model to use, or ``None`` for a noiseless run. - grid : solprop.grid.TimeGrid - The grid to draw on. - seed : int or None - Entropy of the generator; ``None`` draws from the operating system, and the - entropy it drew is recorded on the realisation either way. - n_modes : int, optional - How many modes the state carries, so that every mode gets its own realisation. - - Returns - ------- - solprop.noise.NoiseRealisation or None - The realisation. - - Raises - ------ - DriverError - If the model is not one this driver knows. + - ``noise``: which model to use, or ``None`` for a noiseless run. + - ``seed``: entropy of the generator; ``None`` draws from the operating system, and + the entropy it drew is recorded on the realisation either way. """ if noise is None: return None @@ -10273,21 +8694,7 @@ def _general_noise( basis would be a different stochastic model, because a fixed-modulus random-phase draw is not invariant in distribution under unitary mixing. - Parameters - ---------- - noise : {None, 'input', 'modified'} - Which model to use, or ``None`` for a noiseless run. - grid : solprop.grid.TimeGrid - The grid to draw on. - seed : int or None - Entropy of the generator. - guide : solprop.modes.guide.ModalGuide - The guide, whose sampling carries any declared change of basis. - - Returns - ------- - solprop.noise.NoiseRealisation or None - The realisation in the run's own channel coordinates. + - ``noise``: which model to use, or ``None`` for a noiseless run. """ drawn = _shot_noise(noise, grid, seed, n_modes=len(guide.mode_collection)) sampling = guide.sampling @@ -10301,21 +8708,7 @@ def _spatial_shot_noise( ) -> NoiseRealisation: """Draw a radial or Cartesian run's shot noise at its entrance plane. - Parameters - ---------- - noise : {'input', 'modified'} - Which model to use. - flux : solprop.spatial.SpatialFlux - The entrance plane's measure. - n_pol : int - Independent polarisation components. - seed : int or None - Entropy of the generator; ``None`` draws from the operating system. - - Returns - ------- - solprop.noise.NoiseRealisation - The realisation (:func:`solprop.noise.spatial_noise`). + - ``seed``: entropy of the generator; ``None`` draws from the operating system. """ return spatial_noise(flux, np.random.default_rng(seed), model=noise, n_pol=n_pol) @@ -10323,22 +8716,7 @@ def _spatial_shot_noise( def _spatial_draw( noise: NoiseModel, simulation: Simulation, medium: SpatialMedium ) -> Callable[[int | None], NoiseRealisation | None]: - """Return the draw of a spatial run's shot noise, measured at its entrance plane. - - Parameters - ---------- - noise : {'input', 'modified'} - Which model to use. - simulation : Simulation - The run whose grids, entrance and polarisation the draw is on. - medium : solprop.spatial.SpatialMedium - The medium whose flux measures the draw. - - Returns - ------- - collections.abc.Callable - Draws the realisation from a seed (:func:`_spatial_shot_noise`). - """ + """Return the draw of a spatial run's shot noise, measured at its entrance plane.""" flux = SpatialFlux.resolve( simulation._base_grid, cast("RadialGrid | FreeGrid | PlanarGrid", simulation.spatial_grid), @@ -10359,17 +8737,10 @@ def _with_noise( ``has_noise`` flag; what else consults it is a refusal keyed on the model, and a replacement that changes the model is checked by its caller. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The translated problem. - noise : solprop.noise.NoiseRealisation or None - The realisation in true sqrt(W) on a modal route, or ``None`` for no noise. - - Returns - ------- - solprop.translate.problem.PropagationProblem - The problem with the realisation in its internal, vacuum-normalised amplitude. + - ``noise``: the realisation in true sqrt(W) on a modal route, or ``None`` for no + noise. + - Returns: the problem with the realisation in its internal, vacuum-normalised + amplitude. """ noise0 = None if noise is None else noise_spectrum(noise, problem.shape) if noise0 is not None and problem.transform.geometry is None: @@ -10399,19 +8770,7 @@ def _redrawable( seed its file recorded and a scan point take its own (:meth:`Simulation._reseed`); a seeded realisation keeps the one it was given. - Parameters - ---------- - simulation : Simulation - The run a constructor built, carrying `draw`'s realisation. - draw : collections.abc.Callable - Draws the realisation from a seed. - seed : int or None - The seed the caller gave. - - Returns - ------- - Simulation - `simulation`, able to redraw its noise when `seed` was ``None``. + - Returns: `simulation`, able to redraw its noise when `seed` was ``None``. """ if seed is None and simulation.noise is not None: simulation._noise_redraw = draw @@ -10523,33 +8882,10 @@ def _capillary_grid( ) -> TimeGrid: """Return the grid a capillary run propagates on. - Parameters - ---------- - grid : solprop.grid.TimeGrid or None - A grid of one's own, which wins over the three keywords. - length : float - Capillary length, m. - wavelength : float - Reference wavelength, m. - wavelength_limits : tuple of float or None - Window limits, m. - trange : float or None - Requested time window, s. - envelope : bool - Whether to build an envelope grid rather than a field grid. - thg : bool - Whether the responses reach the third harmonic, which sets an envelope grid's - oversampling. - - Returns - ------- - solprop.grid.TimeGrid - The grid. - - Raises - ------ - DriverError - If a grid is given beside the keywords it replaces, or neither is complete. + - ``length``: capillary length, m. + - ``wavelength``: reference wavelength, m. + - ``wavelength_limits``: window limits, m. + - ``trange``: requested time window, s. """ if grid is not None: if wavelength_limits is not None or trange is not None: @@ -10602,36 +8938,8 @@ def _capillary_modes( ) -> ModeCollection: """Return the mode set under the declared capillary material. - Parameters - ---------- - modes : int or sequence of Mode - Number of HE_1m modes, or a supplied set of capillary geometries. - radius : float or ZProfile - Core radius in m, used when constructing a numbered mode set. - mixture : Mixture - Ordered species and their density paths. - length : float - The run's length in m, over which a supplied set's radius is compared. - lattice : TubeLattice or None, optional - Tube geometry selecting Vincetti modes for an integer count. - model : {'full', 'reduced'} - Marcatili model for a numbered mode set. - loss : bool, float or callable - Total loss policy for a numbered mode set. - cladding : GlassIndex, complex or None - Cladding index for a numbered mode set. - - Returns - ------- - ModeCollection - Validated geometries with the same material declaration. - - Raises - ------ - DriverError - If the mode count is invalid, a mode cannot carry a mixture, a supplied set - is not orthonormal or disagrees with a declared radius, model, loss or - cladding, or a lattice is given beside supplied modes or the reduced model. + - ``radius``: core radius in m, used when constructing a numbered mode set. + - ``length``: the run's length in m, over which a supplied set's radius is compared. """ if isinstance(modes, int) and modes < 1: raise DriverError( @@ -10713,25 +9021,9 @@ def _refuse_ignored_geometry( their defaults, which is the only way this layer can tell a declaration from an omission: a default beside a mode that says otherwise was not asked for. - Parameters - ---------- - modes : sequence of Mode - The supplied set, non-empty. - radius : float or ZProfile - The declared core radius, m. - length : float - The run's length, m, over which the radius paths are compared. - model : {'full', 'reduced'} - The declared Marcatili model. - loss : bool, float or callable - The declared loss policy. - cladding : GlassIndex, complex or None - The declared cladding index. - - Raises - ------ - DriverError - Naming the argument and the first mode it disagrees with. + - ``modes``: the supplied set, non-empty. + - ``radius``: the declared core radius, m. + - ``length``: the run's length, m, over which the radius paths are compared. """ declared = as_profile(radius) for index, mode in enumerate(modes): @@ -10763,24 +9055,8 @@ def _refuse_ignored_setting( ) -> None: """Refuse a non-default capillary setting that a supplied mode does not carry. - Parameters - ---------- - index : int - The mode's position in the supplied set. - mode : Mode - The supplied mode. - name : str - The keyword, which is also the mode's attribute: ``"model"``, ``"loss"`` or - ``"cladding"``. - declared : object - The value the call declared. - default : object - The keyword's default, which is never refused. - - Raises - ------ - DriverError - If `declared` is not the default and is not the mode's own value. + - ``name``: the keyword, which is also the mode's attribute: ``"model"``, ``"loss"`` + or ``"cladding"``. """ if declared is default or declared == default: return @@ -10801,20 +9077,11 @@ def _refuse_ignored_setting( def _same_radius(declared: ZProfile, own: ZProfile, length: float) -> bool: """Return whether two radius paths agree over a run of `length`. - Parameters - ---------- - declared : ZProfile - The declared core radius, m. - own : ZProfile - A supplied mode's core radius, m. - length : float - The run's length, m. - - Returns - ------- - bool - Whether they agree to ``_RADIUS_RTOL`` at 65 equally spaced positions, both - paths' breakpoints and the midpoints between all of them. + - ``declared``: the declared core radius, m. + - ``own``: a supplied mode's core radius, m. + - ``length``: the run's length, m. + - Returns: whether they agree to ``_RADIUS_RTOL`` at 65 equally spaced positions, + both paths' breakpoints and the midpoints between all of them. """ if declared is own: return True @@ -10832,20 +9099,7 @@ def _same_radius(declared: ZProfile, own: ZProfile, length: float) -> bool: def _given_gas_modes(modes: Sequence[Mode], mixture: Mixture) -> ModeCollection: - """Bind supplied modes and retain the existing orthonormality gate. - - Parameters - ---------- - modes : sequence of Mode - Supplied geometry, in propagation row order. - mixture : Mixture - New ordered material populations. - - Returns - ------- - ModeCollection - Rebound orthonormal modes. - """ + """Bind supplied modes and retain the existing orthonormality gate.""" if not modes: raise DriverError( "a waveguide mode set needs at least one mode", parameter="modes" @@ -10892,19 +9146,6 @@ def _warn_sampled_peak(rule: TransverseQuadrature | None, spec: StatsSpec) -> No the axis, so a node-maximum plasma statistic samples the axial peak at its innermost node, which a steep rate under-reports. The value is unchanged; the warning names the node and the on-axis statistics that read the axis. - - Parameters - ---------- - rule : solprop.modes.quadrature.TransverseQuadrature or None - The run's transverse rule. - spec : solprop.stats.StatsSpec - The statistics the run records. - - Warns - ----- - SampledPeakWarning - If a node-maximum plasma statistic is requested and the rule's innermost - node has a field scale below :data:`_SAMPLED_PEAK_THRESHOLD`. """ if rule is None or rule.radial_rule != "field_scale": return @@ -10933,29 +9174,10 @@ def _capillary_quadrature( ) -> TransverseQuadrature: """Return the fixed transverse rule of a capillary run. - Parameters - ---------- - modes : solprop.modes.collection.ModeCollection - The mode set, whose azimuthal order decides whether the angle is resolved. - n_r : int or None - Radial nodes; ``None`` takes the module's - :func:`~solprop.driver.simulation.default_radial_order`. - n_theta : int or None - Azimuthal nodes; ``None`` takes the rule's own default where one is needed. - radial_rule : str - The radial rule family, one of :data:`solprop.modes.quadrature.RadialRule`. - - Returns - ------- - solprop.modes.quadrature.TransverseQuadrature - The rule. - - Raises - ------ - DriverError - If azimuthal nodes are asked for by a set that has no angular structure to - resolve, where they would multiply the cost and change nothing, or y nodes - by a slab set, which is uniform in y. + - ``n_r``: radial nodes; ``None`` takes the module's + :func:`~solprop.driver.simulation.default_radial_order`. + - ``n_theta``: azimuthal nodes; ``None`` takes the rule's own default where one is + needed. """ if modes.modes[0].dimlimits(0.0)[0] == "slab": if n_theta is not None: @@ -11010,21 +9232,10 @@ def _no_quadrature( 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. + - ``modes``: the mode set, which must hold exactly one mode. + - ``n_r``: radial nodes, which must not be given. + - ``n_theta``: azimuthal nodes, which must not be given. + - ``radial_rule``: the radial rule family, which must be the default. """ if len(modes) != 1: msg = ( @@ -11072,44 +9283,17 @@ def _capillary_responses( The Raman response, optmat's, is the fill's (:func:`_optmat_raman`). - Parameters - ---------- - gas : solprop.physdata.Gas or str - Filling species, by name, for the rate. - material : solprop.physdata.species.Species - The fill's species as resolved under the run's selection, which the plasma - response carries: it is never rebuilt from the name with the default - selection, so the material manifest lists only the models that acted (Q45). - kerr : bool - Include the Kerr response. - plasma : bool or IonisationRate - Include photoionisation, and with which rate. - preionisation : float or ZProfile - Fraction already ionised before the pulse, a number or a path along z in m. - integration : {'band', 'trapezoid'} or None - The field plasma column's running-integral rule; ``None`` takes the - response's default. - wavelength : float - Reference wavelength, m, the rate's carrier unless the selection names one. - envelope : bool - Select cycle-averaged envelope plasma instead of real-field plasma. - electronic : KerrResponse or DispersiveKerrResponse or None - The gas's electronic response under the selection's policy - (:func:`solprop.nonlinear.mixtures.electronic_response`); ``None`` without - one. - selection : MaterialSelection - The run's material selection, under which ``plasma=True`` takes optmat's - rate. - - Returns - ------- - list - The responses, Kerr first. - - Raises - ------ - DriverError - If a run is asked for with no nonlinearity at all. + - ``material``: the fill's species as resolved under the run's selection, which the + plasma response carries: it is never rebuilt from the name with the default + selection, so the material manifest lists only the models that acted (Q45). + - ``preionisation``: fraction already ionised before the pulse, a number or a path + along z in m. + - ``integration``: the field plasma column's running-integral rule; ``None`` takes + the response's default. + - ``wavelength``: reference wavelength, m, the rate's carrier unless the selection + names one. + - ``electronic``: the gas's electronic response under the selection's policy + (:func:`solprop.nonlinear.mixtures.electronic_response`); ``None`` without one. """ responses: list[object] = [] if kerr: @@ -11153,18 +9337,6 @@ def _default_raman(mixture: Mixture, *, provisional: bool = False) -> bool: oxide is the one curated only provisionally. Under ``MaterialSelection(raman="provisional")`` it takes that response instead (Q17), which warns once per run with its status and is recorded. - - Parameters - ---------- - mixture : solprop.physdata.mixtures.Mixture - The resolved fill. - provisional : bool, optional - Whether the run's selection admits optmat's provisional Raman responses. - - Returns - ------- - bool - Whether the run carries a Raman response by default. """ on = False for component in mixture.components: @@ -11203,31 +9375,9 @@ def _optmat_raman( # noqa: PLR0917 - called positionally ) -> list[object]: """Return a single-gas fill's Raman response from optmat (SEP 3). - Parameters - ---------- - gas : solprop.physdata.Gas or str - Filling species, for the message. - mixture : Mixture - The fill, one optmat-resolved population. - selection : MaterialSelection - The run's selection. - thg : bool - Whether the field grid keeps the third harmonic. - wavelength : float - The grid reference wavelength in m, the carrier unless the selection names one. - length : float - The run's length in m. - - Returns - ------- - list - The response, or nothing for a fill that is vacuum along the whole run. - - Raises - ------ - MaterialError - If the species has no molecular Raman response (a noble gas has no - Raman-active mode: call with ``raman=False``), or optmat refuses it. + - ``wavelength``: the grid reference wavelength in m, the carrier unless the + selection names one. + - ``length``: the run's length in m. """ logger.debug("optmat Raman response of the %s fill", gas) response = mixture_raman( @@ -11255,21 +9405,8 @@ def _ionisation_rate( rate object passes through unchanged. The models' physical limits are discussed in ``docs/guide/media/ionisation.md``. - Parameters - ---------- - gas : solprop.physdata.Gas or str - Filling species. - plasma : bool or IonisationRate - ``True`` for the default rate of the species, or the rate itself. - wavelength : float - Reference wavelength, m, the rate's carrier unless the selection names one. - selection : MaterialSelection - The run's material selection. - - Returns - ------- - solprop.nonlinear.ionisation.IonisationRate - The rate. + - ``wavelength``: reference wavelength, m, the rate's carrier unless the selection + names one. """ if isinstance(plasma, bool): return selected_rate(species(gas, selection=selection), selection, wavelength) @@ -11284,18 +9421,6 @@ def _with_integration_statistics( The undershoot, work defect and boundary level of SEP 18 are recorded by default wherever the field plasma column integrates by the band rule, so the run's warning can read them back; a run on the plain trapezoid records none of them. - - Parameters - ---------- - spec : solprop.stats.StatsSpec - The route's default statistics. - responses : sequence of object - The run's responses. - - Returns - ------- - solprop.stats.StatsSpec - `spec`, with the three names appended under the band rule. """ if not any( isinstance(one, PlasmaResponse) and one.integration == "band" @@ -11352,41 +9477,14 @@ def _tdse_entry( ) -> _TDSEEntry | None: """Resolve the ``tdse=`` capillary keywords (SEP 20 sections 5.2 and 5.8). - Parameters - ---------- - tdse : TDSEGasResponse or None - The declared response, or None for a rate-based run. - execution : TDSEExecution or None - Its resources. - trange : float or None - Requested time window in s, the default atomic-window duration. - kerr : bool - The Kerr switch, which must stay true beside a TDSE response. - mode_average : bool - The mode-averaged switch, which must stay false beside a TDSE response: its - translation needs the resolved radial quadrature. - unset : mapping - Electronic-response keywords that must be left unset beside it. - backend : str - Requested backend; ``auto`` resolves to ``numpy_ref``. - step_planes : int or None - Requested natural-step interval. - save_planes : sequence of float or None - Requested save positions in m. - n_save : int or None - Requested plane count. - - Returns - ------- - _TDSEEntry or None - The resolution, or None without `tdse`. - - Raises - ------ - DriverError - For `tdse_execution` without `tdse`, an electronic keyword or `mode_average` - beside it, an - unresolvable window duration, or a non-TDSE record. + - ``tdse``: the declared response, or None for a rate-based run. + - ``trange``: requested time window in s, the default atomic-window duration. + - ``kerr``: the Kerr switch, which must stay true beside a TDSE response. + - ``mode_average``: the mode-averaged switch, which must stay false beside a TDSE + response: its translation needs the resolved radial quadrature. + - ``unset``: Electronic-response keywords that must be left unset beside it. + - ``save_planes``: requested save positions in m. + - Returns: the resolution, or None without `tdse`. """ if tdse is None: if execution is not None: @@ -11459,24 +9557,9 @@ def _entry_overrides[P, R]( # noqa: PLR0917 - called positionally ) -> tuple[_TDSEEntry | None, P, R, str, int | None, int]: """Return a TDSE entry with the capillary settings it overrides, or the originals. - Parameters - ---------- - atomic : _TDSEEntry or None - The resolved TDSE entry. - plasma, raman : object - The rate-based switches; off beside a TDSE response, which owns them. - backend : str - Requested backend. - n_save : int or None - Requested plane count. - step_planes : int or None - Requested natural-step interval; ``None`` is 0 without TDSE. - - Returns - ------- - tuple - The entry, the plasma and Raman switches, the backend, the plane count and - the step interval. + - ``step_planes``: requested natural-step interval; ``None`` is 0 without TDSE. + - Returns: the entry, the plasma and Raman switches, the backend, the plane count + and the step interval. """ if atomic is None: steps = 0 if step_planes is None else step_planes @@ -11485,20 +9568,9 @@ def _entry_overrides[P, R]( # noqa: PLR0917 - called positionally off = cast("P", False), cast("R", False) return atomic, *off, atomic.backend, atomic.n_save, atomic.step_planes - -def _tdse_stats(spec: StatsSpec) -> StatsSpec: - """Remove the statistics a TDSE response cannot supply from a default spec. - - Parameters - ---------- - spec : solprop.stats.StatsSpec - The route's default statistics. - - Returns - ------- - solprop.stats.StatsSpec - `spec` without :data:`TDSE_UNAVAILABLE_STATISTICS`. - """ + +def _tdse_stats(spec: StatsSpec) -> StatsSpec: + """Remove the statistics a TDSE response cannot supply from a default spec.""" return dataclasses.replace( spec, names=tuple( @@ -11508,13 +9580,7 @@ def _tdse_stats(spec: StatsSpec) -> StatsSpec: def _warn_tdse_cost(quadrature: TransverseQuadrature | None) -> None: - """Warn once when a TDSE run integrates over a many-node radial rule. - - Parameters - ---------- - quadrature : TransverseQuadrature or None - The run's transverse rule. - """ + """Warn once when a TDSE run integrates over a many-node radial rule.""" if quadrature is None or quadrature.radial_rule in ( "field_scale", "field_scale_radau", @@ -11540,23 +9606,9 @@ def _capillary_stats( compute: it holds the nodes the plasma was evaluated at, the mode set the axis needs, and the embedded quadrature estimate a later pass cannot reconstruct. - Parameters - ---------- - with_plasma : bool - Whether the run carries a plasma response, whose two statistics the backend - refuses to compute without one. - 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 - ------- - solprop.stats.StatsSpec - The spec, with no energy windows; add those for the bands of interest. + - ``averaged``: 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. """ wanted = [ name @@ -11658,17 +9710,7 @@ def _capillary_stats( def _means_not_given(value: object, default: object) -> bool: """Return whether a supplied keyword carries the value that means "not given". - Parameters - ---------- - value : object - What the caller passed. - default : object - The table's entry: the signature's own default. - - Returns - ------- - bool - True when the keyword was, in effect, not given. + - Returns: True when the keyword was, in effect, not given. """ if value is default: return True @@ -11681,19 +9723,7 @@ def _means_not_given(value: object, default: object) -> bool: def _refuse_gas_keywords(supplied: Mapping[str, object]) -> None: - """Refuse, by name, every gas-filled keyword given beside a provider. - - Parameters - ---------- - supplied : collections.abc.Mapping - The keywords :meth:`Simulation.from_waveguide` collected, by name. - - Raises - ------ - DriverError - Naming the first keyword that departs from its "not given" value, and why - a general modal guide cannot carry it. - """ + """Refuse, by name, every gas-filled keyword given beside a provider.""" for name, value in supplied.items(): # Indexed, not `.get`: a keyword this table does not know is a keyword # `from_waveguide` would silently ignore on this route, and @@ -11726,11 +9756,6 @@ def _general_stats() -> StatsSpec: declared basis as well as on the identity one. ``quadrature_error`` is not here either: a provider's node rule has no embedded coarse pair to difference. - - Returns - ------- - solprop.stats.StatsSpec - The default specification. """ return StatsSpec( names=("omega0", "physical_energy", "analytic_flux", "fwhm_t", "fwhm_t_outer") @@ -11749,25 +9774,7 @@ def _in_channel( amplitude and the launched energy is the pulse's; there is no ``sqrt(Re n_eff)`` boundary on this route and nothing here crosses one. - Parameters - ---------- - built : numpy.ndarray - The pulse, shape ``(1, n_pol, n_omega)`` in the square root of watts. - guide : solprop.modes.guide.ModalGuide - The guide, whose channel labels name the rows. - channel : int or str or None - Row index, display label, or ``None`` for the first channel. - - Returns - ------- - numpy.ndarray - The state, shape ``(n_modes, n_pol, n_omega)``, zero in every other row. - - Raises - ------ - DriverError - If the pulse is not one channel wide, or the channel is not one of the - guide's. + - ``channel``: row index, display label, or ``None`` for the first channel. """ labels = [label.display_label for label in guide.mode_collection.channel_labels] if built.shape[0] != 1: @@ -11813,26 +9820,10 @@ def _general_pulse( ) -> GaussianPulse | SechPulse | PulseSum | SpatialPulse: """Return a general modal run's input: a shaped pulse, or an incident beam. - Parameters - ---------- - incident : solprop.pulses.SpatialPulse or None - The incident beam, which carries its own temporal pulse; ``None`` builds the - pulse from the keywords. - channel : int or str or None - The named channel, refused beside `incident`. - shape, wavelength, fwhm, width, energy, peak_power, phases, pulses : object - The pulse keywords, as :func:`_shaped_pulse` takes them. - - Returns - ------- - solprop.pulses.GaussianPulse or SechPulse or PulseSum or SpatialPulse - The input. - - Raises - ------ - DriverError - If `incident` is given beside `channel` or a pulse keyword, is a planar - beam, or is launched away from its waist. + - ``incident``: the incident beam, which carries its own temporal pulse; ``None`` + builds the pulse from the keywords. + - ``shape``, ``wavelength``, ``fwhm``, ``width``, ``energy``, ``peak_power``, + ``phases``, ``pulses``: the pulse keywords, as :func:`_shaped_pulse` takes them. """ if incident is None: return _shaped_pulse( @@ -11898,23 +9889,8 @@ def _core_index( The incident medium of a beam launched into the guide: the innermost material region, whose law does not move with z, so it is the entrance's. - Parameters - ---------- - regions : sequence of solprop.modes.material.MaterialRegion - The guide's ordered material regions. - grid : solprop.grid.TimeGrid - The run's grid. - - Returns - ------- - numpy.ndarray - ``Re sqrt(eps)`` on the active positive band and zero elsewhere, shape - ``(n_omega,)``. - - Raises - ------ - DriverError - If the guide's first region does not start on the axis. + - Returns: ``Re sqrt(eps)`` on the active positive band and zero elsewhere, shape + ``(n_omega,)``. """ core = regions[0] if not isinstance(core.inner_radius, float | int) or core.inner_radius != 0: @@ -11950,18 +9926,7 @@ def _core_index( def _beam_grid(pulse: SpatialPulse) -> RadialGrid: - """Return the radial plane an incident beam is sampled on before coupling. - - Parameters - ---------- - pulse : solprop.pulses.SpatialPulse - The beam. - - Returns - ------- - solprop.grid.RadialGrid - A grid that resolves the beam and holds it to rounding. - """ + """Return the radial plane an incident beam is sampled on before coupling.""" radius = _BEAM_MARGIN * pulse.waist * _BEAM_EXTENT ** (1.0 / (2.0 * pulse.order)) return RadialGrid(radius, _BEAM_NODES) @@ -11983,25 +9948,6 @@ def _incident_launch( :meth:`Simulation.with_input`, which records its provenance as ``configuration["coupling"]``; the coupling's report is recorded as ``configuration["launch"]`` and the beam as ``configuration["pulse"]``. - - Parameters - ---------- - simulation : Simulation - The general modal simulation, built with a zero input. - pulse : solprop.pulses.SpatialPulse - The incident beam and its temporal pulse. - model : solprop.coupling.models.CouplingModel - The coupling model. - - Returns - ------- - Simulation - The simulation launching the coupled amplitudes, sharing the translation. - - Raises - ------ - DriverError - If the beam cannot be built or the coupling refuses it. """ port = simulation.input_port() index = ( @@ -12048,23 +9994,8 @@ def _hollow_input( ) -> tuple[Complex[np.ndarray, "n_modes n_pol n_omega"], dict[str, object] | None]: """Return a hollow guide's initial state, and its launch record if it has one. - Parameters - ---------- - pulse : solprop.pulses.Pulse - The input. - launch : mapping, sequence of mapping, or None - The ``launch`` keyword; ``None`` puts the input in the first mode. - grid : solprop.grid.TimeGrid - The run's grid. - modes : sequence of solprop.modes.protocol.Mode - The mode set, in row order. - - Returns - ------- - numpy.ndarray - The state, shape ``(n_modes, n_pol, n_omega)``. - dict or None - ``configuration["modal_launch"]``, or ``None`` without a launch. + - ``launch``: the ``launch`` keyword; ``None`` puts the input in the first mode. + - Returns: ``configuration["modal_launch"]``, or ``None`` without a launch. """ if launch is None: return _in_lowest_mode(pulse.build(grid), len(modes)), None @@ -12082,23 +10013,6 @@ def _in_lowest_mode( A single pulse enters the fundamental and the higher modes start empty, filling through the nonlinearity. - - Parameters - ---------- - built : numpy.ndarray - The pulse, shape ``(1, n_pol, n_omega)``. - n_modes : int - How many modes the set carries. - - Returns - ------- - numpy.ndarray - The state, shape ``(n_modes, n_pol, n_omega)``. - - Raises - ------ - DriverError - If the input already carries more modes than the set does. """ if built.shape[0] > n_modes: msg = ( @@ -12126,15 +10040,7 @@ def _hollow_components(*, supplied: bool) -> tuple[str, ...] | None: components. ``from_waveguide`` refuses a mode count before this, so only the capillary entry reaches the first branch. - Parameters - ---------- - supplied : bool - Whether the set was supplied rather than built from a mode count. - - Returns - ------- - tuple of str or None - ``("x",)``, or ``None`` to infer the components from the modes. + - Returns: ``("x",)``, or ``None`` to infer the components from the modes. """ return None if supplied else DEFAULT_COMPONENTS @@ -12156,23 +10062,12 @@ def _capillary_input_peak( those the nodes carry the peak. The same peak is the reference field of an optmat rate's constant energy per event (SEP 3, D8). - Parameters - ---------- - field0 : numpy.ndarray - The input state, shape ``(n_modes, n_pol, n_omega)``. - modes : solprop.modes.collection.ModeCollection - 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 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 - ------- - tuple of float - The peak field amplitude in V/m, and the circular share ``|S3| / S0`` of the - field's transverse components where it peaks, zero for a linear field. + - ``modes``: the mode set, whose profiles are evaluated on the axis and at the + nodes. + - ``rule``: 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: the peak field amplitude in V/m, and the circular share ``|S3| / S0`` of + the field's transverse components where it peaks, zero for a linear field. """ analytic = analytic_field(grid, field0) limits = modes.dimlimits()[0] @@ -12223,13 +10118,7 @@ class _InputPeak: rule: TransverseQuadrature | None def evaluate(self) -> tuple[float, float]: - """Return the peak field in V/m and its circular share. - - Returns - ------- - tuple of float - :func:`_capillary_input_peak` of the input. - """ + """Return the peak field in V/m and its circular share.""" return _capillary_input_peak(self.field0, self.modes, self.grid, self.rule) @@ -12243,20 +10132,10 @@ def _ionisation_inputs( The input's peak field is found once, when the table's range is the default one (:func:`default_field_range`) or an optmat rate waits for its reference field. - Parameters - ---------- - responses : sequence of object - The run's responses. - field_range : tuple of float or None - The caller's table range in V/m, or ``None`` for the default. - peak : _InputPeak or None - Where the input's peak is found from; ``None`` for a run without plasma. - - Returns - ------- - tuple - The responses (optmat rates bound to the peak), the table range, and the - reference field in V/m the rates were bound to, or ``None``. + - ``field_range``: the caller's table range in V/m, or ``None`` for the default. + - ``peak``: where the input's peak is found from; ``None`` for a run without plasma. + - Returns: the responses (optmat rates bound to the peak), the table range, and the + reference field in V/m the rates were bound to, or ``None``. """ unbound = _unbound_rates(responses) if peak is None or not (field_range is None or unbound): @@ -12281,26 +10160,8 @@ def _peak_field_reports( population -- and inverted through each rate (:func:`solprop.nonlinear.optmat_rates.peak_field_report`). - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The translated problem, whose populations name the statistics' rows. - output : solprop.output.schema.Output - The handler the run wrote; a closed file is reopened for reading. - responses : sequence of object - The run's responses. - - Returns - ------- - tuple of PeakFieldReport - One per population with an optmat rate; none when the run carries no such - rate or recorded no peak rate, which is logged. - - Warns - ----- - MaterialRangeWarning - Once per run, for a population that reached its rate's maximum field, above - which the rate is held. + - Returns: one per population with an optmat rate; none when the run carries no such + rate or recorded no peak rate, which is logged. """ labels = problem.responses.labels rates = [ @@ -12385,18 +10246,6 @@ def _check_rate_inputs( (:func:`_capillary_input_peak`); on a free-space route the peak is the physical intensity's maximum and the polarisation the pulse's Jones vector, which a spectrum given as an array does not declare, so it is taken as linear there. - - Parameters - ---------- - simulation : Simulation - The run, its input already built. - initial : Pulse, SpatialPulse or numpy.ndarray - The input as given. - - Warns - ----- - MaterialRangeWarning - As :meth:`~solprop.nonlinear.optmat_rates.OptmatRate.check_input` does. """ rates = [ one.rate @@ -12430,18 +10279,6 @@ def _bind_spatial_rates( constant energy per event to the input's peak field, as the capillary route does (SEP 3, Q14); binding also checks the input against each rate. A run whose rates are all bound is only checked. - - Parameters - ---------- - simulation : Simulation - A spatial run, its input already built; its responses are replaced. - initial : Pulse, SpatialPulse or numpy.ndarray - The input as given. - - Warns - ----- - MaterialRangeWarning - As :func:`_bound_rates` and :func:`_check_rate_inputs` do. """ if _unbound_rates(simulation.responses): simulation.responses = tuple( @@ -12460,19 +10297,9 @@ def _spatial_input_peak( ) -> tuple[float, float]: """Return a free-space or planar run's peak input field and its circular share. - Parameters - ---------- - simulation : Simulation - A free-space or planar run, its input already built. - initial : Pulse, SpatialPulse or numpy.ndarray - The input as given. - - Returns - ------- - tuple of float - The peak physical field amplitude in V/m, and - the circular share ``|S3|/S0`` of the pulse's Jones vector; a spectrum given - as an array declares none and is taken as linear. + - Returns: the peak physical field amplitude in V/m, and the circular share + ``|S3|/S0`` of the pulse's Jones vector; a spectrum given as an array declares + none and is taken as linear. """ space = cast("RadialGrid | FreeGrid | PlanarGrid", simulation.spatial_grid) # Ionisation is driven by the physical field, never by a signed axial flux. @@ -12494,17 +10321,8 @@ def _spatial_input_peak( def _linear_moment(cycle: CycleIntegralData, amplitude: float) -> float: """Return an envelope run's cycle-averaged rate at a linear amplitude. - Parameters - ---------- - cycle : solprop.translate.envelope_plasma.CycleIntegralData - The run's cycle integrals of one population. - amplitude : float - Envelope amplitude in V/m. - - Returns - ------- - float - The rate in s^-1 its statistic records there. + - ``amplitude``: envelope amplitude in V/m. + - Returns: the rate in s^-1 its statistic records there. """ return float(cycle_moments(amplitude, 0.0, cycle)[0]) @@ -12512,15 +10330,8 @@ def _linear_moment(cycle: CycleIntegralData, amplitude: float) -> float: def _unbound_rates(responses: Sequence[object]) -> bool: """Return whether a plasma response carries an optmat rate without a reference. - Parameters - ---------- - responses : sequence of object - The run's responses. - - Returns - ------- - bool - True when a rate's constant energy per event still waits for the input's peak. + - Returns: True when a rate's constant energy per event still waits for the input's + peak. """ return any( isinstance(one, PlasmaResponse | EnvelopePlasmaResponse) @@ -12537,27 +10348,11 @@ def _bound_rates( ) -> list[object]: """Bind a run's optmat rates to the input's peak field, and check the input. - Parameters - ---------- - responses : sequence of object - The run's responses. - peak : tuple of float - The input's peak field in V/m and its circular share there. - field_range : tuple of float or None - The field amplitudes in V/m the run tabulates its rates over. - - Returns - ------- - list - The responses, each unbound optmat rate taking its constant energy at the - peak (:func:`solprop.nonlinear.optmat_rates.bind_reference_field`). - - Warns - ----- - MaterialRangeWarning - Once per run, when the peak exceeds a rate's maximum field, or the input is - not linearly polarised at a Keldysh parameter above one - (:meth:`solprop.nonlinear.optmat_rates.OptmatRate.check_input`). + - ``peak``: the input's peak field in V/m and its circular share there. + - ``field_range``: the field amplitudes in V/m the run tabulates its rates over. + - Warns once per run when the peak exceeds a rate's maximum field, or the input is + not linearly polarised at a Keldysh parameter above one + (:meth:`solprop.nonlinear.optmat_rates.OptmatRate.check_input`). """ field, circular = peak bound = list(bind_reference_field(responses, field, field_range)) @@ -12574,18 +10369,6 @@ def _rebind_mode(mode: Mode, mixture: Mixture) -> Mode: The mode's own gas, pressure and temperature (a temperature path included) are replaced by the mixture's; only its geometry is kept. - - Parameters - ---------- - mode : Mode - Supplied gas-filled geometry. - mixture : Mixture - New ordered material populations. - - Returns - ------- - Mode - The rebound geometry. """ rebind: Callable[[Mixture], Mode] | None = getattr(mode, "with_mixture", None) if not callable(rebind): @@ -12607,22 +10390,9 @@ def _hollow_stats( ) -> StatsSpec: """Keep capillary statistics where the supplied geometry defines them. - Parameters - ---------- - with_plasma : bool - Whether plasma statistics are available. - mixture : Mixture - Ordered material populations. - profiles : mapping of str to ZProfile - Available physical paths. - rule : solprop.modes.quadrature.TransverseQuadrature or None - The run's fixed transverse rule, whose embedded coarse rule the - quadrature alarm needs; ``None`` for a mode-averaged run. - - Returns - ------- - StatsSpec - Statistics with unsupported geometry paths omitted. + - ``rule``: the run's fixed transverse rule, whose embedded coarse rule the + quadrature alarm needs; ``None`` for a mode-averaged run. + - Returns: statistics with unsupported geometry paths omitted. """ base = _capillary_stats( with_plasma=with_plasma, mixture=mixture, averaged=rule is None @@ -12646,20 +10416,7 @@ def _hollow_stats( def _refuse_dispersive_policy(materials: MaterialSelection | None, route: str) -> None: - """Refuse a dispersive electronic policy on a route whose response is constant. - - Parameters - ---------- - materials : MaterialSelection or None - The run's material selection. - route : str - What carries the route's constant electronic coefficient, for the message. - - Raises - ------ - DriverError - If the selection names a dispersive electronic policy (SEP 6). - """ + """Refuse a dispersive electronic policy on a route whose response is constant.""" if materials is None or materials.electronic not in DISPERSIVE_POLICIES: return msg = ( @@ -12678,18 +10435,6 @@ def _check_electronic_policy( A caller's own responses are taken as they are, but a constant :class:`~solprop.nonlinear.responses.KerrResponse` beside a selection that names a dispersive policy would record one law and propagate another. - - Parameters - ---------- - materials : MaterialSelection or None - The run's material selection. - responses : sequence of object - The run's declared responses. - - Raises - ------ - DriverError - If the selection is dispersive and a constant Kerr response is declared. """ if materials is None or materials.electronic not in DISPERSIVE_POLICIES: return @@ -12710,21 +10455,11 @@ def _check_selection_carrier( ) -> None: """Refuse a carrier that is not the run's: one carrier sets the Kerr coefficient. - Parameters - ---------- - materials : MaterialSelection or None - The run's material selection. - reference : float - The grid's reference wavelength in m, the run's carrier. - wavelength : float or None, optional - The driver's `wavelength` in m, at which the responses are built. + The electronic coefficient, the Raman response and the rate are all evaluated at + the carrier. - Raises - ------ - DriverError - If the selection's ``carrier_wavelength`` or `wavelength` is not the grid's - reference wavelength: the electronic coefficient, the Raman response and the - rate are all evaluated at the carrier. + - ``reference``: the grid's reference wavelength in m, the run's carrier. + - ``wavelength``: the driver's `wavelength` in m, at which the responses are built. """ if materials is None: return @@ -12751,15 +10486,7 @@ def _check_selection_carrier( def _default_cladding(cladding: GlassIndex | complex | None) -> GlassIndex | complex: """Return the cladding a numbered capillary set takes. - Parameters - ---------- - cladding : GlassIndex, complex or None - The cladding given; ``None`` is the package default. - - Returns - ------- - GlassIndex or complex - A given cladding as it is; the default as optmat's recommended silica. + - ``cladding``: the cladding given; ``None`` is the package default. """ return fused_silica() if cladding is None else cladding @@ -12774,18 +10501,6 @@ def _selected_medium( temperature, so that it is recorded; a supplied susceptibility is kept beside optmat's equation of state, as for a capillary fill. Any other medium is taken as it is. - - Parameters - ---------- - medium : solprop.spatial.SpatialMedium - The medium given. - materials : MaterialSelection - The run's material selection. - - Returns - ------- - solprop.spatial.SpatialMedium - The medium to propagate in. """ if not isinstance(medium.material, Gas): return medium @@ -12810,19 +10525,9 @@ def _selected_guide(guide: ModalGuide, materials: MaterialSelection) -> ModalGui ``tensor=True``). A response declared with any other parameters, and a provider of another kind, are taken as they are. - Parameters - ---------- - guide : solprop.modes.guide.ModalGuide - The guide given. - materials : MaterialSelection - The run's material selection. - - Returns - ------- - solprop.modes.guide.ModalGuide - The guide to propagate on: the one given when nothing resolves, otherwise - the same provider, channels and sampling with the regions' delayed - responses resolved. + - Returns: the guide to propagate on: the one given when nothing resolves, otherwise + the same provider, channels and sampling with the regions' delayed responses + resolved. """ default = SilicaTensorResponse() if not any(region.raman == default for region in guide.regions): @@ -12835,18 +10540,7 @@ def _selected_guide(guide: ModalGuide, materials: MaterialSelection) -> ModalGui ) def selected(region: MaterialRegion) -> MaterialRegion: - """Return a region with its default delayed response resolved. - - Parameters - ---------- - region : solprop.modes.material.MaterialRegion - The region given. - - Returns - ------- - solprop.modes.material.MaterialRegion - The region, its default response replaced by the bundle's. - """ + """Return a region with its default delayed response resolved.""" return dataclasses.replace( region, responses=tuple( @@ -12880,18 +10574,8 @@ def _selected_planar(space: PlanarGrid, materials: MaterialSelection) -> PlanarG recommended silica. A supplied susceptibility and a declared cladding are the caller's. - Parameters - ---------- - space : solprop.modes.slab.PlanarGrid - The planar grid given. - materials : MaterialSelection - The run's material selection. - - Returns - ------- - solprop.modes.slab.PlanarGrid - The grid given when nothing resolves; otherwise the same grid rebuilt on the - resolved slab modes. + - Returns: the grid given when nothing resolves; otherwise the same grid rebuilt on + the resolved slab modes. """ modes = cast("tuple[SlabMode, ...]", tuple(space.modes)) first = modes[0] @@ -12927,25 +10611,7 @@ def _selected_planar(space: PlanarGrid, materials: MaterialSelection) -> PlanarG def _planar_medium(space: PlanarGrid) -> SpatialMedium: - """Return the fill of a planar grid's modes as a constant bulk medium. - - Parameters - ---------- - space : solprop.modes.slab.PlanarGrid - The planar grid, whose modes share one fill. - - Returns - ------- - solprop.spatial.SpatialMedium - The gas at the modes' pressure, temperature and susceptibility, or vacuum. - - Raises - ------ - DriverError - If the fill is a mixture or varies along z: the hybrid transform carries - constant media only, as Cartesian free space does, and mixtures are not yet - admitted. - """ + """Return the fill of a planar grid's modes as a constant bulk medium.""" mode = cast("SlabMode", space.modes[0]) if mode.mixture is not None: msg = ( @@ -12980,30 +10646,13 @@ def _boundary( ) -> tuple[Absorber | None, AbsorberScheme | None]: """Resolve a builder's absorber keywords (SEP 21 section 5). - Parameters - ---------- - absorber : Absorber, False or None - The collar given; ``False`` switches both soft collars off and keeps the - hard band mask; ``None`` takes `default`. - scheme : {'nonlinear', 'split_step'} or None - The temporal scheme given; ``None`` is the default scheme. - grid : TimeGrid - The run's grid. - default : callable or None, optional - The route's collar when none is given; ``None`` is - :func:`default_collar`. - - Returns - ------- - tuple - The absorber (``None`` for none) and the scheme, ``None`` where the settings - are to take the route's default. - - Raises - ------ - DriverError - If a scheme is given beside ``absorber=False``, which has no temporal - collar for it to apply. + - ``absorber``: the collar given; ``False`` switches both soft collars off and keeps + the hard band mask; ``None`` takes `default`. + - ``scheme``: the temporal scheme given; ``None`` is the default scheme. + - ``default``: the route's collar when none is given; ``None`` is + :func:`default_collar`. + - Returns: the absorber (``None`` for none) and the scheme, ``None`` where the + settings are to take the route's default. """ if absorber is False: if scheme is not None: @@ -13061,18 +10710,6 @@ def _spatial_default( The temporal and spectral collars become the routes' default (SEP 21, D6); the transverse boundary is the settings' ``spatial_absorber`` (SEP 40). - - Parameters - ---------- - settings : SimulationSettings - The run's settings. - grid : TimeGrid - The run's temporal grid. - - Returns - ------- - SimulationSettings - The settings, with an absorber. """ if settings.absorber is not None: return settings @@ -13085,22 +10722,8 @@ def _transverse_specification( ) -> SpatialAbsorber | None: """Return the transverse boundary a run's translation resolves (SEP 40). - Parameters - ---------- - setting : solprop.boundaries.SpatialAbsorber, False or None - :attr:`SimulationSettings.spatial_absorber`. - spatial_grid : object - The run's transverse grid, ``None`` for a modal run. - - Returns - ------- - solprop.boundaries.SpatialAbsorber or None - The specification; ``None`` for no ramp and no collar. - - Raises - ------ - DriverError - If a modal run is given a specification. + - ``spatial_grid``: the run's transverse grid, ``None`` for a modal run. + - Returns: the specification; ``None`` for no ramp and no collar. """ if spatial_grid is None: if isinstance(setting, SpatialAbsorber): diff --git a/src/solprop/driver/summary.py b/src/solprop/driver/summary.py index 7a7c2fafa..f72f2bef1 100644 --- a/src/solprop/driver/summary.py +++ b/src/solprop/driver/summary.py @@ -80,10 +80,7 @@ def __repr__(self) -> str: def _describe(self) -> tuple[str, Sequence[tuple[str, object]]]: """Return the title and the rows of the block. - Returns - ------- - tuple - The title, and the rows as they are. + - Returns: the title, and the rows as they are. """ return self.title, self.rows @@ -227,16 +224,10 @@ def _code(metadata: RunMetadata) -> str: whether it was modified; the branch and the installed version follow, where a long branch name may be cut. - Parameters - ---------- - metadata : solprop.output.schema.RunMetadata - The run's record, whose ``versions`` and ``git`` were read when it was built. - - Returns - ------- - str - For example ``"0.1.0"`` or ``"commit 5e8f6884a1b2, modified (main, installed - as 0.1.dev3+g1a2b3c4)"``. + - ``metadata``: the run's record, whose ``versions`` and ``git`` were read when it + was built. + - Returns: for example ``"0.1.0"`` or ``"commit 5e8f6884a1b2, modified (main, + installed as 0.1.dev3+g1a2b3c4)"``. """ installed = str(metadata.versions.get("solprop", "unknown")) git = metadata.git @@ -251,16 +242,9 @@ def _code(metadata: RunMetadata) -> str: def _machine(record: Mapping[str, object]) -> str: """Describe the machine: architecture, CPU, CPU counts, memory and system. - Parameters - ---------- - record : collections.abc.Mapping - The machine, as the execution record's ``host`` holds it. - - Returns - ------- - str - For example ``"arm64, Apple M4 Max, 16 CPUs (16 usable), 64 GiB, Darwin - 25.0.0"``; a part that could not be read is left out. + - ``record``: the machine, as the execution record's ``host`` holds it. + - Returns: for example ``"arm64, Apple M4 Max, 16 CPUs (16 usable), 64 GiB, Darwin + 25.0.0"``; a part that could not be read is left out. """ logical, usable = record.get("logical_cpus"), record.get("usable_cpus") memory = record.get("memory_bytes") @@ -342,15 +326,8 @@ def resources(spent: Mapping[str, float | int | None]) -> str: def _duration(seconds: float) -> str: """Write a duration in seconds below a minute, else as ``H:MM:SS``. - Parameters - ---------- - seconds : float - The duration, s. - - Returns - ------- - str - For example ``"0.132 s"`` or ``"0:03:12"``. + - ``seconds``: the duration, s. + - Returns: for example ``"0.132 s"`` or ``"0:03:12"``. """ return f"{seconds:.3g} s" if seconds < 60.0 else format_duration(seconds) @@ -358,17 +335,7 @@ def _duration(seconds: float) -> str: def _threads(backend: str, execution: Mapping[str, object]) -> str: """Describe the thread pools the backend took. - Parameters - ---------- - backend : str - The backend's name. - execution : collections.abc.Mapping - The backend's ``metadata()``. - - Returns - ------- - str - The pools and the process budget that bounds them. + - ``execution``: the backend's ``metadata()``. """ budget = execution.get("thread_budget") source = execution.get("thread_budget_source") @@ -385,15 +352,7 @@ def _threads(backend: str, execution: Mapping[str, object]) -> str: def _precision(problem: PropagationProblem) -> str: """Describe the floating-point precision of the run. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The translated problem. - - Returns - ------- - str - ``"double"`` or ``"single"``. + - Returns: ``"double"`` or ``"single"``. """ # Translation admits no mixed precision, so the state's name is the run's. return problem.precision.state @@ -402,15 +361,7 @@ def _precision(problem: PropagationProblem) -> str: def _grid(metadata: RunMetadata) -> str: """Describe the time grid: kind, points, window and wavelength band. - Parameters - ---------- - metadata : solprop.output.schema.RunMetadata - The run's record. - - Returns - ------- - str - For example ``"field, 4096 points, window 683.1 fs, 150 nm to 4 um"``. + - Returns: for example ``"field, 4096 points, window 683.1 fs, 150 nm to 4 um"``. """ grid = metadata.grid window = grid.n_t * grid.dt @@ -428,20 +379,7 @@ def _grid(metadata: RunMetadata) -> str: def _representation(problem: PropagationProblem, metadata: RunMetadata) -> str: - """Describe the transverse representation and the polarisation. - - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The translated problem. - metadata : solprop.output.schema.RunMetadata - The run's record. - - Returns - ------- - str - For example ``"mode-averaged (1 mode), scalar"``. - """ + """Describe the transverse representation and the polarisation.""" n_modes, n_pol = problem.shape[0], problem.shape[1] polarisation = "scalar" if n_pol == 1 else "vector (x and y)" kind = metadata.simulation_kind @@ -474,19 +412,7 @@ def _representation(problem: PropagationProblem, metadata: RunMetadata) -> str: def _medium(configuration: Mapping[str, object]) -> str: - """Describe the medium and its fill. - - Parameters - ---------- - configuration : collections.abc.Mapping - The run's recorded physics objects. - - Returns - ------- - str - The mixture or the bulk medium with any pressure profile, the GNLSE's - nonlinear coefficient, or ``"not recorded"``. - """ + """Describe the medium and its fill.""" gnlse = configuration.get("gnlse") if gnlse is not None: gamma = getattr(gnlse, "gamma", None) @@ -505,16 +431,8 @@ def _medium(configuration: Mapping[str, object]) -> str: def _pressure(configuration: Mapping[str, object]) -> str | None: """Describe the fill pressure, constant or along z. - Parameters - ---------- - configuration : collections.abc.Mapping - The run's recorded physics objects. - - Returns - ------- - str or None - The constant pressure in bar or the profile's own description; ``None`` for - a run with no pressure profile. + - Returns: the constant pressure in bar or the profile's own description; ``None`` + for a run with no pressure profile. """ profiles = configuration.get("profiles") pressure = profiles.get("pressure") if isinstance(profiles, Mapping) else None @@ -528,15 +446,9 @@ def _pressure(configuration: Mapping[str, object]) -> str | None: def _mixture(record: Mapping[str, object]) -> str: """Describe a gas mixture from the record a run keeps of it. - Parameters - ---------- - record : collections.abc.Mapping - The mixture's record: its ``labels``, mole ``fractions`` and ``temperature_K``. - - Returns - ------- - str - For example ``"He, 293.15 K"`` or ``"Ar 0.8 + He 0.2, 293.15 K"``. + - ``record``: the mixture's record: its ``labels``, mole ``fractions`` and + ``temperature_K``. + - Returns: for example ``"He, 293.15 K"`` or ``"Ar 0.8 + He 0.2, 293.15 K"``. """ labels = record.get("labels") fractions = record.get("fractions") @@ -556,18 +468,9 @@ def _mixture(record: Mapping[str, object]) -> str: def _responses(problem: PropagationProblem, configuration: Mapping[str, object]) -> str: """Describe the nonlinear responses, one line each, with Raman always named. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The translated problem, which says what was compiled when the configuration - does not hold the response objects. - configuration : collections.abc.Mapping - The run's recorded physics objects. - - Returns - ------- - str - One line per response; ``"Raman off"`` when none is Raman. + - ``problem``: the translated problem, which says what was compiled when the + configuration does not hold the response objects. + - Returns: one line per response; ``"Raman off"`` when none is Raman. """ recorded = configuration.get("responses") responses = list(recorded) if isinstance(recorded, Sequence) else [] @@ -591,23 +494,7 @@ def _compiled( A problem run directly, with a record built without its configuration, still says what it computes, from the compiled terms alone. - Parameters - ---------- - kerr : bool - Whether a Kerr term was compiled. - raman_terms : int or None - The Raman terms, or ``None`` for none. - plasma : bool - Whether a plasma term was compiled. - dispersive : bool - Whether a dispersive Kerr term was compiled. - tdse : bool - Whether the run carries a TDSE response. - - Returns - ------- - list of str - One name per compiled response. + - ``raman_terms``: the Raman terms, or ``None`` for none. """ named = ( (kerr, "Kerr"), @@ -620,18 +507,7 @@ def _compiled( def _response(response: object) -> str: - """Describe one response object. - - Parameters - ---------- - response : object - A response as the simulation carries it. - - Returns - ------- - str - A short phrase naming the model and its species. - """ + """Describe one response object.""" name = type(response).__name__ species = getattr(response, "species", None) who = f" ({species})" if species else "" @@ -666,15 +542,7 @@ def _response(response: object) -> str: def _rate(rate: object) -> str: """Describe an ionisation rate. - Parameters - ---------- - rate : object - The rate object of a plasma response. - - Returns - ------- - str - ``"optmat "`` for an optmat rate, else the rate's class. + - Returns: ``"optmat "`` for an optmat rate, else the rate's class. """ if type(rate).__name__ == "OptmatRate": return f"optmat {getattr(rate, 'model', '')}" @@ -687,16 +555,8 @@ def _noise(metadata: RunMetadata) -> str: A model the run took by default says so, and a default that resolved to no noise gives its reason (SEP 37), so that a noiseless run is never silently noiseless. - Parameters - ---------- - metadata : solprop.output.schema.RunMetadata - The run's record. - - Returns - ------- - str - For example ``"input model (default), seed 3"``, ``"off"``, or - ``"off (default: single precision carries no noise: ...)"``. + - Returns: for example ``"input model (default), seed 3"``, ``"off"``, or ``"off + (default: single precision carries no noise: ...)"``. """ noise = metadata.noise resolution = metadata.noise_resolution @@ -711,18 +571,7 @@ def _noise(metadata: RunMetadata) -> str: def _integrator(request: RunRequest) -> str: - """Describe the march and its formulation. - - Parameters - ---------- - request : solprop.translate.run_request.RunRequest - The run's request. - - Returns - ------- - str - For example ``"Multistep, variable order up to 5, exponential"``. - """ + """Describe the march and its formulation.""" stepper = request.stepper integrator = stepper.integrator if integrator is None: @@ -745,20 +594,9 @@ def _integrator(request: RunRequest) -> str: def _defaulted(text: str, metadata: RunMetadata, choice: str) -> str: """Mark a row whose value is the route's default rather than the caller's. - Parameters - ---------- - text : str - The row's description. - metadata : solprop.output.schema.RunMetadata - The run's record, whose ``defaults`` says which choices were defaults. - choice : str - The choice the row describes: ``"integrator"`` or ``"rtol"``. - - Returns - ------- - str - `text`, followed by ``"(capillary default)"`` or similar where the route's - default applied. + - ``choice``: the choice the row describes: ``"integrator"`` or ``"rtol"``. + - Returns: `text`, followed by ``"(capillary default)"`` or similar where the + route's default applied. """ defaults = metadata.defaults if defaults is None or defaults.route is None or choice not in defaults.defaulted: @@ -769,17 +607,8 @@ def _defaulted(text: str, metadata: RunMetadata, choice: str) -> str: def _tolerances(request: RunRequest, metadata: RunMetadata) -> str: """Describe the step control: tolerances and step bound, or a replayed schedule. - Parameters - ---------- - request : solprop.translate.run_request.RunRequest - The run's request. - metadata : solprop.output.schema.RunMetadata - The run's record, which says whether the tolerance is the route's default. - - Returns - ------- - str - For example ``"rtol 1e-06, atol 1e-10 (capillary default), dz_max 50 mm"``. + - Returns: for example ``"rtol 1e-06, atol 1e-10 (capillary default), dz_max 50 + mm"``. """ stepper = request.stepper controller = stepper.controller @@ -795,17 +624,7 @@ def _tolerances(request: RunRequest, metadata: RunMetadata) -> str: def _absorber(problem: PropagationProblem, check: BoundaryCheck | None) -> str: """Describe the temporal absorber and the watch on the window's edges. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The translated problem. - check : solprop.monitor.BoundaryCheck or None - The edge thresholds, or ``None`` when the edges are not watched. - - Returns - ------- - str - ``"none"`` or the scheme it is applied by, then the edge watch. + - ``check``: the edge thresholds, or ``None`` when the edges are not watched. """ text = ( f"temporal, {problem.responses.absorber_scheme} scheme" @@ -831,21 +650,7 @@ def _transverse( SEP 40 section 5.3: ``unwatched``, ``coarse`` and ``off`` are said before the first step, so that a run that cannot reach a clean verdict says so. - Parameters - ---------- - problem : solprop.translate.problem.PropagationProblem - The translated problem. - request : solprop.translate.run_request.RunRequest - The run's request: its schedule and planes. - check : solprop.monitor.BoundaryCheck or None - The edge thresholds, or ``None`` when nothing is watched. - backend : str - The backend's name. - - Returns - ------- - str - The boundary, then what the monitor will report. + - ``check``: the edge thresholds, or ``None`` when nothing is watched. """ design = transverse_of(problem.route) if design is None: @@ -875,17 +680,7 @@ def _transverse( def _planes(request: RunRequest, planes: np.ndarray) -> str: """Describe the save planes. - Parameters - ---------- - request : solprop.translate.run_request.RunRequest - The run's request. - planes : numpy.ndarray - The save planes, m. - - Returns - ------- - str - The count and the extent, and any per-step saving. + - ``planes``: the save planes, m. """ text = ( f"{planes.size} after the entrance, {engineering(float(planes[0]), 'm')} to " @@ -902,15 +697,7 @@ def _planes(request: RunRequest, planes: np.ndarray) -> str: def _output(output: Output) -> str: """Describe where the planes go. - Parameters - ---------- - output : solprop.output.schema.Output - The handler. - - Returns - ------- - str - The handler's class, and its file when it has one. + - Returns: the handler's class, and its file when it has one. """ path = getattr(output, "path", None) name = type(output).__name__ @@ -920,19 +707,8 @@ def _output(output: Output) -> str: def _start(start_plane: int, n_planes: int, z_start: float) -> str: """Describe whether the run is new or resumed. - Parameters - ---------- - start_plane : int - Index of the first save plane beyond the loaded state. - n_planes : int - The save planes of the run. - z_start : float - Where the run starts, m. - - Returns - ------- - str - ``"new run"`` or where the resume continues from. + - ``z_start``: where the run starts, m. + - Returns: ``"new run"`` or where the resume continues from. """ if start_plane == 0: return "new run" diff --git a/src/solprop/driver/tdse.py b/src/solprop/driver/tdse.py index 1f9783079..b90722d39 100644 --- a/src/solprop/driver/tdse.py +++ b/src/solprop/driver/tdse.py @@ -147,13 +147,7 @@ def pinned_revision() -> str: def _load_provider() -> _Provider: - """Load the optional package only when an atomic operation is requested. - - Returns - ------- - _Provider - Loaded public provider API; no native assembly has occurred. - """ + """Load the optional package only when an atomic operation is requested.""" if sys.version_info[:2] != (3, 14): raise BackendError("the pinned TDSE provider requires Python 3.14") try: @@ -171,17 +165,8 @@ def _build_record( ) -> dict[str, object]: """Refuse stale native extensions and execution-space mismatches before setup. - Parameters - ---------- - provider : _Provider - Loaded optional provider API. - execution : TDSEExecution or None - Required device family, or None when only resolving a preset. - - Returns - ------- - dict - Serializable native build and installed distribution identities. + - ``execution``: required device family, or None when only resolving a preset. + - Returns: serializable native build and installed distribution identities. """ build = provider.build_info() revision = build.source_revision @@ -213,23 +198,7 @@ def _build_record( def _hacc_surface(provider: _Provider) -> _Native: - """Return the provider's haCC namespace, refusing a build without it. - - Parameters - ---------- - provider : _Provider - Loaded optional provider API. - - Returns - ------- - object - The ``pyroraptor.hacc`` module. - - Raises - ------ - BackendError - If the installed provider has no complete haCC consumer surface. - """ + """Return the provider's haCC namespace, refusing a build without it.""" hacc = getattr(provider, "hacc", None) missing = [name for name in _HACC_SURFACE if not hasattr(hacc, name)] if missing: @@ -306,31 +275,11 @@ def _hacc_fields( # noqa: PLR0917 - called positionally ) -> dict[str, object]: """Convert provider haCC objects into the numeric fields of a ``HaccBasis``. - Parameters - ---------- - hacc : object - The provider's haCC namespace. - atom : object - ``hacc.Helium``; ``hacc.Argon`` is refused by name. - basis : object - ``hacc.Basis``: radial axis, irECS and angular limit, SI. - gauge : object - ``hacc.MixedGauge`` or ``hacc.VelocityGauge``. - cut_energy : float - Spectral cut in J. - max_step_au : float - RK4 step ceiling in atomic units of time. - - Returns - ------- - dict - Every ``HaccBasis`` field but ``source`` and ``preset``. - - Raises - ------ - MaterialError - If the atom is argon or another non-helium record, the ion count is not 1 or - 2, or a member cannot be represented. + - ``basis``: ``hacc.Basis``: radial axis, irECS and angular limit, SI. + - ``gauge``: ``hacc.MixedGauge`` or ``hacc.VelocityGauge``. + - ``cut_energy``: spectral cut in J. + - ``max_step_au``: RK4 step ceiling in atomic units of time. + - Returns: every ``HaccBasis`` field but ``source`` and ``preset``. """ if isinstance(atom, hacc.Argon): raise MaterialError( @@ -373,15 +322,7 @@ def _hacc_fields( # noqa: PLR0917 - called positionally def _numeric_fields(basis: HaccBasis) -> dict[str, object]: """Return a ``HaccBasis``'s fields without its provenance and preset identity. - Parameters - ---------- - basis : HaccBasis - Validated record. - - Returns - ------- - dict - The model and numerical fields compared against the provider. + - Returns: the model and numerical fields compared against the provider. """ record = asdict(basis) del record["source"], record["preset"] @@ -393,19 +334,7 @@ def _hacc_objects( ) -> tuple[_Native, _Native, _Native]: """Build the provider's atom, basis and gauge from a ``HaccBasis``. - Parameters - ---------- - provider : _Provider - Loaded provider, for its radial basis and complex scaling. - hacc : object - Its haCC namespace. - basis : HaccBasis - Validated SI record. - - Returns - ------- - tuple - ``hacc.Helium``, ``hacc.Basis`` and the gauge. + - Returns: ``hacc.Helium``, ``hacc.Basis`` and the gauge. """ angular = ( hacc.AngularLimit(particle=basis.angular_order) @@ -696,26 +625,15 @@ class _Chunk: def rows(self) -> int: """Return the number of rows in this contiguous chunk. - Returns - ------- - int - Positive difference between the exclusive stop and inclusive start. + - Returns: positive difference between the exclusive stop and inclusive start. """ return self.stop - self.start def _atomic_samples(descriptor: TDSEDescriptor) -> int: - """Return the samples the provider integrates over: the window's or the lattice's. + """Return the samples the provider integrates over. - Parameters - ---------- - descriptor : TDSEDescriptor - Admitted descriptor. - - Returns - ------- - int - Retained atomic samples. + The atomic window's when there is one, else the lattice's. """ window = descriptor.atomic_window return descriptor.samples if window is None else window.samples @@ -728,24 +646,12 @@ def _estimated_memory( ) -> dict[str, int]: """Estimate conservative data storage before allocating the native setup. - Parameters - ---------- - descriptor : TDSEDescriptor - Admitted output contract and SI sampling lattice. - chunks : tuple of _Chunk - Nonempty stable partition of all radial rows. - native_outputs : {'full', 'total_current'} or None, optional - The provider's output selection when a full archive widens the - descriptor's; None takes ``descriptor.outputs``. - - Returns - ------- - dict - Named conservative atomic storage estimates in bytes; vendor setup - workspaces and allocator overhead are not hard bounded. The haCC provider - offers no estimate before preparation (SEP 20, C7), so only its host - histories are estimated; its model and plans are admitted from their - measured ledgers. + - ``native_outputs``: the provider's output selection when a full archive widens the + descriptor's; None takes ``descriptor.outputs``. + - Returns: named conservative atomic storage estimates in bytes; vendor setup + workspaces and allocator overhead are not hard bounded. The haCC provider offers + no estimate before preparation (SEP 20, C7), so only its host histories are + estimated; its model and plans are admitted from their measured ledgers. """ samples = _atomic_samples(descriptor) outputs = descriptor.outputs if native_outputs is None else native_outputs @@ -776,12 +682,8 @@ def _estimated_memory( def _admit_memory(ledger: Mapping[str, int], limit: int | None) -> None: """Apply the declared atomic byte limit. - Parameters - ---------- - ledger : mapping of str to int - Named persistent allocations or estimates in bytes. - limit : int or None - Positive byte ceiling, or None for an unrestricted allocation. + - ``ledger``: named persistent allocations or estimates in bytes. + - ``limit``: positive byte ceiling, or None for an unrestricted allocation. """ total = sum(ledger.values()) if limit is not None and total > limit: @@ -791,18 +693,7 @@ def _admit_memory(ledger: Mapping[str, int], limit: int | None) -> None: def _json_default(value: object) -> object: - """Convert a public native record or array into JSON-compatible containers. - - Parameters - ---------- - value : object - Dataclass, host array or NumPy scalar requiring conversion. - - Returns - ------- - object - Dictionary, list or Python scalar with no native resource handle. - """ + """Convert a public native record or array into JSON-compatible containers.""" if is_dataclass(value) and not isinstance(value, type): return asdict(value) if isinstance(value, np.ndarray): @@ -815,32 +706,13 @@ def _json_default(value: object) -> object: def _encoded(value: object) -> str: """Encode finite metadata in a deterministic JSON representation. - Parameters - ---------- - value : object - Nested public records, arrays and finite primitive values. - - Returns - ------- - str - Sorted JSON text; nonfinite values raise ValueError. + - ``value``: nested public records, arrays and finite primitive values. """ return json.dumps(value, default=_json_default, sort_keys=True, allow_nan=False) def _failure_safe(value: object) -> object: - """Preserve nonfinite failed monitors as explicit strings in valid JSON. - - Parameters - ---------- - value : object - Nested failure diagnostics, including possibly nonfinite scalars. - - Returns - ------- - object - Independent JSON-compatible containers with tagged nonfinite strings. - """ + """Preserve nonfinite failed monitors as explicit strings in valid JSON.""" encoded = json.dumps(value, default=_json_default, sort_keys=True) return json.loads(encoded, parse_constant=lambda token: f"nonfinite:{token}") @@ -850,17 +722,9 @@ def _input_diagnostics( ) -> object: """Measure field endpoints and area without changing the supplied waveform. - Parameters - ---------- - fields : numpy.ndarray - Finite SI fields in V/m, shape (n_nodes, n_to). - spacing : float - Positive sample spacing in s. - - Returns - ------- - dict - Per-node peak and endpoint fields in V/m and pulse area in V s/m. + - ``fields``: finite SI fields in V/m, shape (n_nodes, n_to). + - ``spacing``: positive sample spacing in s. + - Returns: Per-node peak and endpoint fields in V/m and pulse area in V s/m. """ return { "peak_field_v_per_m": np.max(np.abs(fields), axis=1), @@ -880,18 +744,10 @@ def _drive_diagnostics( Multiplying by the gate need not preserve a zero-area drive; the area is recorded, never removed. - Parameters - ---------- - drive : numpy.ndarray - The exact gated field passed to the provider, V/m. - spacing : float - Sample spacing in s. - - Returns - ------- - dict - Per-node area in V s/m, which is minus the vector potential at ``t_e`` - when ``A(t_s) = 0``, and the endpoint fields in V/m. + - ``drive``: the exact gated field passed to the provider, V/m. + - ``spacing``: sample spacing in s. + - Returns: Per-node area in V s/m, which is minus the vector potential at ``t_e`` + when ``A(t_s) = 0``, and the endpoint fields in V/m. """ area = np.trapezoid(drive, dx=spacing, axis=1) return { @@ -903,20 +759,7 @@ def _drive_diagnostics( def _row_chunks(execution: TDSEExecution, n_nodes: int) -> tuple[_Chunk, ...]: - """Partition radial rows into stable contiguous batches. - - Parameters - ---------- - execution : TDSEExecution - Positive maximum batch size and device-independent resource limits. - n_nodes : int - Positive total number of radial rows. - - Returns - ------- - tuple of _Chunk - Ordered chunks covering each row exactly once. - """ + """Partition radial rows into stable contiguous batches.""" if type(n_nodes) is not int or n_nodes < 1: raise BackendError("TDSE binding requires a positive integer n_nodes") return tuple( @@ -928,19 +771,9 @@ def _row_chunks(execution: TDSEExecution, n_nodes: int) -> tuple[_Chunk, ...]: def _classify(error: BaseException, provider: _Provider) -> str: """Classify a provider-side exception for the failure record (SEP 20, 5.6). - Parameters - ---------- - error : BaseException - The exception raised by, or after, a provider call. - provider : _Provider - Loaded provider, for its ``DrivenError``. - - Returns - ------- - str - ``native`` for a provider ``DrivenError``, ``interface`` for a - ``ValueError`` or ``TypeError`` from the provider, ``contract`` for a - solprop validation of the returned record, ``unexpected`` otherwise. + - Returns: ``native`` for a provider ``DrivenError``, ``interface`` for a + ``ValueError`` or ``TypeError`` from the provider, ``contract`` for a solprop + validation of the returned record, ``unexpected`` otherwise. """ if isinstance(error, provider.DrivenError): return "native" @@ -954,15 +787,8 @@ def _classify(error: BaseException, provider: _Provider) -> str: def _peak_intensity(fields: Float[np.ndarray, "n_nodes n_samples"]) -> list[float]: """Return each row's peak cycle-averaged intensity ``eps0 c E_max^2 / 2``. - Parameters - ---------- - fields : numpy.ndarray - Real fields in V/m. - - Returns - ------- - list of float - Peak intensities in W/m^2, one per row. + - ``fields``: real fields in V/m. + - Returns: peak intensities in W/m^2, one per row. """ peak = np.max(np.abs(fields), axis=1) return [float(value) for value in 0.5 * EPS0 * C * peak**2] @@ -1080,19 +906,8 @@ class _HaccModel: plan built on it (SEP 20, C1). A preset is checked against the installed provider's identity and fields; a custom basis is admitted as unqualified. - Parameters - ---------- - provider : _Provider - Loaded, pin-checked provider. - basis : HaccBasis - Validated record. - execution : TDSEExecution - Device and memory limit. - stack : ExitStack - Owner of the model's lifetime. - archive : {'required', 'full'}, optional - ``"full"`` builds every plan with the provider's full record for a full - archive; the source still reads the same total and order-one currents. + - ``archive``: ``"full"`` builds every plan with the provider's full record for a + full archive; the source still reads the same total and order-one currents. """ def __init__( @@ -1147,18 +962,7 @@ def __init__( self.outputs = "full" def _verified_preset(self, execution: TDSEExecution) -> _Native: - """Refuse a preset the installed provider does not reproduce. - - Parameters - ---------- - execution : TDSEExecution - Requested device family. - - Returns - ------- - object - The provider's ``HeliumPreset``. - """ + """Refuse a preset the installed provider does not reproduce.""" preset = self.hacc.helium_preset(self.basis.ions, self.basis.variant) if preset.identity != self.basis.preset: raise BackendError( @@ -1187,14 +991,7 @@ def _verified_preset(self, execution: TDSEExecution) -> _Native: @property def closure(self) -> bool: - """Return whether the plans carry the escaped-electron closure. - - Returns - ------- - bool - True for a preset and for a custom mixed-gauge basis whose plans the - provider qualified; False before a custom basis's first plan. - """ + """Return whether the plans carry the escaped-electron closure.""" return self._closure is True def plan(self, *, rows: int, spacing: float, samples: int) -> _Native: @@ -1206,19 +1003,7 @@ def plan(self, *, rows: int, spacing: float, samples: int) -> _Native: a full archive every plan is full, and a custom mixed-gauge basis takes its closure from the first plan's ``total_current_qualified``. - Parameters - ---------- - rows : int - Atoms per evaluation. - spacing : float - Response spacing in s. - samples : int - Retained atomic samples. - - Returns - ------- - object - An unentered ``hacc.ResponsePlan``. + - ``spacing``: response spacing in s. """ arguments = { "rows": rows, @@ -1253,14 +1038,7 @@ def check_info( ) -> None: """Require a plan or result's ``info`` to describe exactly this model. - Parameters - ---------- - info : object - ``hacc.ResponsePlanInfo``. - rows, samples : int - Expected extents. - spacing : float - Expected spacing in s. + - ``spacing``: expected spacing in s. """ if ( info.rows != rows @@ -1287,11 +1065,8 @@ def check_info( def record(self) -> dict[str, object]: """Return the model's provenance for run metadata. - Returns - ------- - dict - Label, preset identity or custom flag, qualification, closure, the - preset's accuracy and domain, and the complement policy. + - Returns: label, preset identity or custom flag, qualification, closure, the + preset's accuracy and domain, and the complement policy. """ preset = self.preset return { @@ -1326,18 +1101,8 @@ def record(self) -> dict[str, object]: class _ProviderBinding: """Keep the optional runtime private to the driver and expose only the seam. - Parameters - ---------- - descriptor : TDSEDescriptor - Admitted scientific configuration and fixed time lattice. - execution : TDSEExecution - Device family, persistent memory budget and attempted-call limit. - n_nodes : int - Positive total number of radial rows. - defer_plans : bool, optional - For haCC, build the propagation plans only in :meth:`build_plans`. - archive : TDSEArchiveWriter or None, optional - Open archive session receiving every evaluation. + - ``defer_plans``: for haCC, build the propagation plans only in + :meth:`build_plans`. """ def __init__( @@ -1415,11 +1180,8 @@ def __init__( def native_outputs(self) -> str | None: """Return the provider's output selection for this binding's plans. - Returns - ------- - {'full', 'total_current'} or None - The descriptor's selection, or ``"full"`` for a full archive; None for a - custom haCC basis before its first plan decides it. + - Returns: the descriptor's selection, or ``"full"`` for a full archive; None + for a custom haCC basis before its first plan decides it. """ if self._hacc is not None: return self._hacc.outputs @@ -1441,13 +1203,7 @@ def _prepare(self) -> None: ] def _prepare_sae(self, basis: TDSEBasis) -> None: - """Allocate shared SAE resources and one plan per distinct batch size. - - Parameters - ---------- - basis : TDSEBasis - Resolved SI atomic basis admitted against the estimate. - """ + """Allocate shared SAE resources and one plan per distinct batch size.""" provider = self._provider model = provider.atomic_model(basis.potential) if model.version != basis.potential_version: @@ -1522,13 +1278,7 @@ def _prepare_sae(self, basis: TDSEBasis) -> None: } def _prepare_hacc(self, basis: HaccBasis) -> None: - """Prepare the haCC model, admit its ledger and build the chunk plans. - - Parameters - ---------- - basis : HaccBasis - Validated preset or custom record. - """ + """Prepare the haCC model, admit its ledger and build the chunk plans.""" model = _HaccModel( self._provider, basis, @@ -1591,11 +1341,8 @@ def _build_hacc_plans(self) -> None: def plans_built(self) -> bool: """Return whether the propagation plans exist. - Returns - ------- - bool - False only for a haCC binding prepared with ``defer_plans`` before - :meth:`build_plans`. + - Returns: False only for a haCC binding prepared with ``defer_plans`` before + :meth:`build_plans`. """ return self._hacc is None or bool(self._plans) @@ -1604,13 +1351,6 @@ def build_plans(self) -> None: Call once, after any consistency diagnostic, whose temporary plan is then already closed. A failure closes every resource, as at preparation. - - Raises - ------ - BackendError - If the binding is closed, has no deferred plans, or a plan fails. - DriverError - If the plans do not fit beside the model under the memory limit. """ self._ensure_open() if self.plans_built: @@ -1629,13 +1369,7 @@ def build_plans(self) -> None: raise BackendError(f"TDSE preparation failed: {error} [{kind}]") from error def _preset_domain_at_start(self, model: _HaccModel) -> None: - """Flag a preset run's duration or carrier beyond its qualification. - - Parameters - ---------- - model : _HaccModel - Prepared preset model. - """ + """Flag a preset run's duration or carrier beyond its qualification.""" preset = model.preset configuration = json.loads(self._descriptor.configuration_json) lattice = configuration["lattice"] @@ -1660,17 +1394,7 @@ def _preset_domain_at_start(self, model: _HaccModel) -> None: ) def _flag_domain(self, reason: str, record: object, message: str) -> None: - """Record a domain reason and warn once per reason per run. - - Parameters - ---------- - reason : {'duration', 'carrier', 'intensity'} - Why the run is outside the preset's domain. - record : object - JSON-compatible facts. - message : str - Human-readable warning text. - """ + """Record a domain reason and warn once per reason per run.""" if reason in self._domain_reasons: return self._domain_reasons[reason] = record @@ -1686,13 +1410,7 @@ def _flag_domain(self, reason: str, record: object, message: str) -> None: @property def ledger(self) -> AtomicCallLedger: - """Return attempted-call accounting, including failed evaluations. - - Returns - ------- - AtomicCallLedger - Immutable snapshot of charged, reserved and remaining call counts. - """ + """Return attempted-call accounting, including failed evaluations.""" return self._budget.snapshot() def reserve(self, logical_calls: int) -> None: @@ -1700,11 +1418,6 @@ def reserve(self, logical_calls: int) -> None: A successful reservation opens the next optical trial, whose evaluations the archive labels with its identifier (SEP 20 section 5.8). - - Parameters - ---------- - logical_calls : int - Positive number of complete all-node evaluations required by the trial. """ self._ensure_open() self._ensure_plans() @@ -1714,21 +1427,7 @@ def reserve(self, logical_calls: int) -> None: self._trial_evaluations = [] def record_trial(self, *, accepted: bool) -> None: - """Record the stepper's decision on the latest trial in the archive. - - Parameters - ---------- - accepted : bool - Whether the trial's step was accepted. - - Raises - ------ - BackendError - If no trial was reserved. - TDSEArchiveError - If the archive cannot record it; the run then stops before its next - provider call. - """ + """Record the stepper's decision on the latest trial in the archive.""" if self._trial is None: raise BackendError("a TDSE trial outcome needs a reserved trial") if self._archive is not None: @@ -1741,13 +1440,7 @@ def release_reservation(self) -> None: self._budget.release_reservation() def restore_ledger(self, ledger: AtomicCallLedger) -> None: - """Restore prior attempts before performing work in a resumed binding. - - Parameters - ---------- - ledger : AtomicCallLedger - Previous checkpoint accounting, preserving charges with the new limit. - """ + """Restore prior attempts before performing work in a resumed binding.""" self._ensure_open() self._budget.restore(ledger) @@ -1770,17 +1463,7 @@ def _drive( ) -> Float[np.ndarray, "n_rows n_samples"]: """Return the exact float64 field passed to the provider for one chunk. - Parameters - ---------- - fields : numpy.ndarray - Full-lattice nodal fields in V/m. - chunk : _Chunk - Rows of this call. - - Returns - ------- - numpy.ndarray - Contiguous rows, sliced to the window and multiplied by its gate. + - ``fields``: Full-lattice nodal fields in V/m. """ rows = fields[chunk.start : chunk.stop] window = self._window @@ -1793,18 +1476,7 @@ def _drive( def _pad( self, values: Float[np.ndarray, "n_rows n_samples"] ) -> Float[np.ndarray, "n_rows n_to"]: - """Place a window's currents on the full lattice with zeros outside. - - Parameters - ---------- - values : numpy.ndarray - Provider rows on the retained samples. - - Returns - ------- - numpy.ndarray - Rows on the full lattice; unchanged without a window. - """ + """Place a window's currents on the full lattice with zeros outside.""" window = self._window if window is None: return values @@ -1813,18 +1485,7 @@ def _pad( return full def _pad_valid(self, valid: np.ndarray) -> np.ndarray: - """Place a window's validity on the full lattice; undriven samples are valid. - - Parameters - ---------- - valid : numpy.ndarray - Boolean provider validity on the retained samples. - - Returns - ------- - numpy.ndarray - Boolean validity on the full lattice. - """ + """Place a window's validity on the full lattice; undriven samples are valid.""" window = self._window if window is None: return valid @@ -1853,21 +1514,14 @@ def evaluate( # noqa: PLR0912, PLR0915 - one evaluation's complete sequence validation; a write failure stops the evaluation before the next provider call (SEP 20 section 5.8). - Parameters - ---------- - fields : numpy.ndarray - Finite float64 fields in V/m, shape (n_nodes, n_to). - z : float - Finite optical position in m for provenance and failure reports. - purpose : {'startup', 'predictor', 'corrected', 'diagnostic'} - Reason for the source evaluation; only exact diagnostics may reuse data. - density : float or None, optional - Finite nonnegative number density at `z` in m^-3, an archive label. - - Returns - ------- - TDSEResult - Owning, read-only, unscaled microscopic SI currents and shadow dipoles. + - ``fields``: finite float64 fields in V/m, shape (n_nodes, n_to). + - ``z``: finite optical position in m for provenance and failure reports. + - ``purpose``: reason for the source evaluation; only exact diagnostics may + reuse data. + - ``density``: finite nonnegative number density at `z` in m^-3, an archive + label. + - Returns: owning, read-only, unscaled microscopic SI currents and shadow + dipoles. """ self._ensure_open() self._ensure_plans() @@ -2009,19 +1663,9 @@ def _archive_begin( ) -> int | None: """Write an evaluation's labels and complete input before any provider call. - Parameters - ---------- - fields : numpy.ndarray - The full-lattice nodal fields, V/m. - drives : list of numpy.ndarray - The exact provider input of each chunk, V/m. - labels : mapping - Role, trial, position, density and input identity. - - Returns - ------- - int or None - The archive's evaluation identifier, or None without an archive. + - ``fields``: the full-lattice nodal fields, V/m. + - ``drives``: the exact provider input of each chunk, V/m. + - Returns: the archive's evaluation identifier, or None without an archive. """ archive = self._archive if archive is None: @@ -2063,10 +1707,7 @@ def _archive_begin( def _binding_record(self) -> dict[str, object]: """Return the build, execution, plan and chunk records an archive shares. - Returns - ------- - dict - Provider plan/build and execution configuration. + - Returns: provider plan/build and execution configuration. """ return { "build": self._build, @@ -2084,19 +1725,8 @@ def _archive_result( The binding's own validation runs next either way: a contract failure of the same result is then the primary error, and the archive failure a note. - Parameters - ---------- - evaluation : int or None - The archive's evaluation, or None without an archive. - index : int - The chunk index. - raw : object - The provider's record, as returned. - - Returns - ------- - TDSEArchiveError or None - The write failure, if any. + - ``evaluation``: the archive's evaluation, or None without an archive. + - Returns: the write failure, if any. """ archive = self._archive if archive is None or evaluation is None: @@ -2125,18 +1755,8 @@ def _archive_failure( ) -> None: """Record an evaluation's exception, keeping the caller's error primary. - Parameters - ---------- - evaluation : int or None - The archive's evaluation, or None without an archive. - index : int or None - The chunk that raised, or None. - error : BaseException - The exception. - classification : str - Its failure class. - note : BaseException or None, optional - The error the caller raises, which gains a note if recording fails. + - ``evaluation``: the archive's evaluation, or None without an archive. + - ``note``: the error the caller raises, which gains a note if recording fails. """ archive = self._archive if archive is None or evaluation is None: @@ -2164,21 +1784,9 @@ def _call_domain( The peaks before and after the drive taper are both recorded, and the flag uses the larger, so the gate cannot conceal a field above the bound. - Parameters - ---------- - fields : numpy.ndarray - Full-lattice nodal fields in V/m. - drives : list of numpy.ndarray - The gated provider inputs of each chunk. - z : float - Optical position in m. - purpose : str - Evaluation label. - - Returns - ------- - dict or None - The call's domain record for a preset, None otherwise. + - ``fields``: Full-lattice nodal fields in V/m. + - ``z``: optical position in m. + - Returns: the call's domain record for a preset, None otherwise. """ model = self._hacc if model is None or model.preset is None: @@ -2206,35 +1814,14 @@ def _call_domain( def _bound(self, raw: _Native) -> _Native: """Return a result's neglected-current bound, wherever its record keeps it. - Parameters - ---------- - raw : object - Provider result. - - Returns - ------- - object - The bound array, or None for a haCC model without the closure. + - Returns: the bound array, or None for a haCC model without the closure. """ if self._hacc is None and isinstance(raw, self._provider.GridResponse): return raw.plasma.neglected_current_bound return raw.neglected_current_bound def _failed_native_record(self, raw: _Native, chunk: _Chunk) -> object: - """Retain diagnostic facts even when the native result is malformed. - - Parameters - ---------- - raw : object - Public provider result returned by the failed evaluation. - chunk : _Chunk - Contiguous row range to identify in the failure record. - - Returns - ------- - object - Failure-safe diagnostic containers, or the serialization failure reason. - """ + """Retain diagnostic facts even when the native result is malformed.""" try: return _failure_safe( { @@ -2250,17 +1837,7 @@ def _failed_native_record(self, raw: _Native, chunk: _Chunk) -> object: return {"unreadable_native_record": type(raw).__name__, "error": str(error)} def _validate_diagnostics(self, raw: _Native, rows: int, samples: int) -> None: - """Reject malformed diagnostics before reducing or labelling row arrays. - - Parameters - ---------- - raw : object - Public provider result with bounds and row monitors. - rows : int - Expected row count. - samples : int - Expected atomic sample count. - """ + """Reject malformed diagnostics before reducing or labelling row arrays.""" if self._hacc is not None and ( not isinstance(raw.total_current, np.ndarray) or raw.total_current.shape != (rows, samples) @@ -2297,20 +1874,11 @@ def _validate_diagnostics(self, raw: _Native, rows: int, samples: int) -> None: def _native_record(self, raw: _Native, chunk: _Chunk) -> object: """Summarise validated native diagnostics with their original row positions. - Parameters - ---------- - raw : object - Public result whose diagnostic shapes and finiteness were validated. - chunk : _Chunk - Contiguous row range of this result. - - Returns - ------- - dict - Per-row monitors, outcomes, validity and maximum current bounds. A haCC - record adds the bound's maximum relative to the row's peak total current, - ``None`` with ``zero_reference`` for a zero-current row; the bound is a - diagnostic and never compared with a threshold. + - ``raw``: public result whose diagnostic shapes and finiteness were validated. + - Returns: Per-row monitors, outcomes, validity and maximum current bounds. A + haCC record adds the bound's maximum relative to the row's peak total current, + ``None`` with ``zero_reference`` for a zero-current row; the bound is a + diagnostic and never compared with a threshold. """ bound = self._bound(raw) record: dict[str, object] = { @@ -2344,17 +1912,8 @@ def _hacc_currents( ) -> tuple[np.ndarray, np.ndarray]: """Validate a haCC result and return its total and first-order currents. - Parameters - ---------- - raw : object - ``hacc.TotalCurrentResponse`` or ``hacc.GridResponse``. - rows, samples : int - Expected extents. - - Returns - ------- - tuple of numpy.ndarray - ``j_tot`` and ``j_1`` on the retained samples, C m/s per atom. + - ``raw``: ``hacc.TotalCurrentResponse`` or ``hacc.GridResponse``. + - Returns: ``j_tot`` and ``j_1`` on the retained samples, C m/s per atom. """ model = self._hacc assert model is not None @@ -2395,18 +1954,9 @@ def _sae_currents( ]: """Validate an SAE result and return its currents and shadow products. - Parameters - ---------- - raw : object - SAE ``GridResponse`` or ``TotalCurrentResponse``. - chunk : _Chunk - Expected rows. - - Returns - ------- - tuple - Total and first-order currents, the optional third-order current and - dipole, and the order labels, on the retained samples. + - ``raw``: SAE ``GridResponse`` or ``TotalCurrentResponse``. + - Returns: total and first-order currents, the optional third-order current and + dipole, and the order labels, on the retained samples. """ descriptor = self._descriptor expected_orders = (1,) if descriptor.shadow_order == 1 else (1, 3) @@ -2463,23 +2013,7 @@ def _sae_currents( ) def _normalize(self, raw: _Native, chunk: _Chunk, identity: str) -> TDSEResult: - """Validate native labels and copy one chunk into the host seam result. - - Parameters - ---------- - raw : object - Full or compact public provider result. - chunk : _Chunk - Expected row range with fixed sample count. - identity : str - Hash of the complete all-node input waveform. - - Returns - ------- - TDSEResult - Validated owning SI result without density or calibration factors, - on the full lattice. - """ + """Validate native labels and copy one chunk into the host seam result.""" descriptor = self._descriptor if self._hacc is not None: total, first = self._hacc_currents(raw, chunk.rows, self._samples) @@ -2514,20 +2048,7 @@ def _normalize(self, raw: _Native, chunk: _Chunk, identity: str) -> TDSEResult: ) def _combine(self, results: list[TDSEResult], identity: str) -> TDSEResult: - """Concatenate normalized chunks in the original radial row order. - - Parameters - ---------- - results : list of TDSEResult - Nonempty ordered chunk results with identical scientific contracts. - identity : str - Hash of the complete all-node input waveform. - - Returns - ------- - TDSEResult - Owning all-node result with merged native diagnostics. - """ + """Concatenate normalized chunks in the original radial row order.""" third = None dipole = None if self._descriptor.shadow_order == 3: @@ -2574,26 +2095,10 @@ def diagnostic_currents( propagation plans against the memory limit, charged as one ``diagnostic`` call to this binding's ledger and closed again. - Parameters - ---------- - field : numpy.ndarray - Finite float64 field in V/m, shape ``(1, n_samples)``, at ``m * - spacing``. - spacing : float - Positive protocol spacing in s. - - Returns - ------- - tuple of numpy.ndarray - ``j_tot`` and ``j_1`` in C m/s per atom on the protocol samples. - - Raises - ------ - BackendError - If the binding is not haCC, the field is invalid, or the call fails - (the attempt stays charged). - DriverError - If the temporary plan does not fit beside the propagation plans. + - ``field``: finite float64 field in V/m, shape ``(1, n_samples)``, at ``m * + spacing``. + - ``spacing``: positive protocol spacing in s. + - Returns: ``j_tot`` and ``j_1`` in C m/s per atom on the protocol samples. """ self._ensure_open() model = self._hacc @@ -2615,27 +2120,13 @@ def diagnostic_currents( return _diagnostic(context, field, float(spacing)) def record_consistency(self, record: Mapping[str, object]) -> None: - """Attach the run's consistency provenance to the binding's metadata. - - Parameters - ---------- - record : mapping - JSON-compatible consistency record. - """ + """Attach the run's consistency provenance to the binding's metadata.""" self._consistency = dict(record) # -- metadata ------------------------------------------------------------------ def metadata(self) -> Mapping[str, object]: - """Return independent JSON-compatible build, setup and call diagnostics. - - Returns - ------- - mapping of str to object - Scientific identity, actual plans, resource counts and failure records, - with the model, atomic window, haCC provenance, domain flags and - consistency record. - """ + """Return independent JSON-compatible build, setup and call diagnostics.""" record: dict[str, object] = { "configuration_identity": self._descriptor.identity, "configuration": json.loads(self._descriptor.configuration_json), @@ -2672,10 +2163,7 @@ def metadata(self) -> Mapping[str, object]: def memory_ledger(self) -> Mapping[str, int]: """Return native storage counted once and the admitted host-result reserve. - Returns - ------- - mapping of str to int - Named atomic allocations in bytes, excluding separately owned optics. + - Returns: named atomic allocations in bytes, excluding separately owned optics. """ return dict(self._memory) @@ -2731,19 +2219,9 @@ def _diagnostic( ) -> tuple[Float[np.ndarray, " n_samples"], Float[np.ndarray, " n_samples"]]: """Run one charged one-row haCC diagnostic on a temporary plan. - Parameters - ---------- - context : _DiagnosticContext - Model, ledger, failure log, provider and memory admission. - field : numpy.ndarray - Finite float64 field in V/m, shape ``(1, n_samples)``. - spacing : float - Protocol spacing in s. - - Returns - ------- - tuple of numpy.ndarray - Owning ``j_tot`` and ``j_1`` in C m/s. + - ``field``: finite float64 field in V/m, shape ``(1, n_samples)``. + - ``spacing``: protocol spacing in s. + - Returns: owning ``j_tot`` and ``j_1`` in C m/s. """ model, budget, provider = context.model, context.budget, context.provider if ( @@ -2833,19 +2311,9 @@ def _diagnostic_archive_begin( ) -> int | None: """Write a consistency diagnostic's labels and input before its provider call. - Parameters - ---------- - context : _DiagnosticContext - The call's context, with its archive. - field : numpy.ndarray - The exact protocol field passed, V/m. - spacing : float - Protocol spacing in s. - - Returns - ------- - int or None - The archive's evaluation identifier, or None without an archive. + - ``field``: the exact protocol field passed, V/m. + - ``spacing``: protocol spacing in s. + - Returns: the archive's evaluation identifier, or None without an archive. """ archive = context.archive if archive is None: @@ -2892,19 +2360,7 @@ def _diagnostic_archive_result( ) -> TDSEArchiveError | None: """Store a consistency diagnostic's returned record before its validation. - Parameters - ---------- - context : _DiagnosticContext - The call's context. - evaluation : int or None - The archive's evaluation, or None. - raw : object - The provider's record, as returned. - - Returns - ------- - TDSEArchiveError or None - The write failure, if any. + - Returns: the write failure, if any. """ archive = context.archive if archive is None or evaluation is None: @@ -2927,18 +2383,7 @@ def _diagnostic_archive_failure( ) -> None: """Record a failed consistency diagnostic, keeping the provider error primary. - Parameters - ---------- - context : _DiagnosticContext - The call's context. - evaluation : int or None - The archive's evaluation, or None. - error : BaseException - The exception. - classification : str - Its failure class. - primary : BaseException - The error raised, which gains a note if recording fails. + - ``primary``: the error raised, which gains a note if recording fails. """ archive = context.archive if archive is None or evaluation is None: @@ -2955,14 +2400,7 @@ def _diagnostic_archive_failure( class _DiagnosticView: """Validate a one-row diagnostic result with the binding's haCC rules. - Parameters - ---------- - model : _HaccModel - Prepared model. - provider : _Provider - Loaded provider. - spacing : float - Protocol spacing in s. + - ``spacing``: protocol spacing in s. """ def __init__(self, model: _HaccModel, provider: _Provider, spacing: float) -> None: @@ -2971,15 +2409,7 @@ def __init__(self, model: _HaccModel, provider: _Provider, spacing: float) -> No self._spacing = spacing def validate(self, raw: _Native, samples: int) -> None: - """Check the bound, monitors, outcomes and validity of one row. - - Parameters - ---------- - raw : object - Provider result. - samples : int - Protocol samples. - """ + """Check the bound, monitors, outcomes and validity of one row.""" bound = raw.neglected_current_bound if bound is None: if self._model.closure: @@ -2998,17 +2428,7 @@ def validate(self, raw: _Native, samples: int) -> None: def currents(self, raw: _Native, samples: int) -> tuple[np.ndarray, np.ndarray]: """Return the validated total and first-order currents. - Parameters - ---------- - raw : object - Provider result. - samples : int - Protocol samples. - - Returns - ------- - tuple of numpy.ndarray - ``(1, samples)`` currents in C m/s. + - Returns: ``(1, samples)`` currents in C m/s. """ model = self._model model.check_info(raw.info, rows=1, samples=samples, spacing=self._spacing) @@ -3081,13 +2501,10 @@ def __init__( raise BackendError(f"TDSE preparation failed: {error} [{kind}]") from error @property - def ledger(self) -> AtomicCallLedger: - """Return the check's attempted-call accounting. + def ledger(self) -> AtomicCallLedger: # numpydoc ignore=RT01 + """The check's attempted-call accounting. - Returns - ------- - AtomicCallLedger - Diagnostic charges, including failures. + Diagnostic charges, including failures. """ return self._budget.snapshot() diff --git a/src/solprop/driver/tdse_consistency.py b/src/solprop/driver/tdse_consistency.py index b6f0c3c13..cc4433b1c 100644 --- a/src/solprop/driver/tdse_consistency.py +++ b/src/solprop/driver/tdse_consistency.py @@ -182,33 +182,14 @@ class LinearReduction: def _invalid(reason: str) -> LinearReduction: - """Return an invalid reduction with its reason. - - Parameters - ---------- - reason : str - The failed validity condition. - - Returns - ------- - LinearReduction - Invalid record with no values. - """ + """Return an invalid reduction with its reason.""" return LinearReduction(False, reason) def _edge_ratio(values: Float[np.ndarray, " n_samples"]) -> float: """Return the largest endpoint magnitude relative to the peak. - Parameters - ---------- - values : numpy.ndarray - A finite record with a nonzero peak. - - Returns - ------- - float - ``max |x|`` over the first and last edge fraction, over ``max |x|``. + - ``values``: a finite record with a nonzero peak. """ edge = max(1, round(_EDGE_FRACTION * values.size)) ends = np.concatenate([values[:edge], values[-edge:]]) @@ -221,17 +202,10 @@ def _record_reason( ) -> str | None: """Return why the time records cannot be reduced, or None. - Parameters - ---------- - drive : numpy.ndarray - Field in V/m. - first_order : numpy.ndarray - First-order current in C m/s. - - Returns - ------- - str or None - The failed condition: shape or finiteness, a zero record, endpoint leakage. + - ``drive``: field in V/m. + - ``first_order``: First-order current in C m/s. + - Returns: the failed condition: shape or finiteness, a zero record, endpoint + leakage. """ if ( drive.shape != first_order.shape @@ -329,11 +303,8 @@ def _helium_reference() -> tuple[ ]: """Return optmat helium's per-atom susceptibility, its support and identity. - Returns - ------- - tuple - The susceptibility function of angular frequency, the supported angular - frequency interval in rad/s, and the material identity record. + - Returns: the susceptibility function of angular frequency, the supported angular + frequency interval in rad/s, and the material identity record. """ helium = species("He") susceptibility = helium.susceptibility @@ -357,15 +328,8 @@ def reference( ) -> Complex[np.ndarray, " n_band"]: """Evaluate optmat's per-atom susceptibility in m^3. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s. - - Returns - ------- - numpy.ndarray - Complex susceptibility in the optics convention. + - ``omega``: angular frequencies in rad/s. + - Returns: complex susceptibility in the optics convention. """ return np.asarray(susceptibility.susceptibility(omega), dtype=np.complex128) @@ -391,18 +355,7 @@ def cache_directory() -> Path: def _key(inputs: Mapping[str, object]) -> str: - """Digest the cache key's canonical inputs. - - Parameters - ---------- - inputs : mapping - Basis identity, provider revision, material identity, wavelength, protocol. - - Returns - ------- - str - SHA-256 hex digest. - """ + """Digest the cache key's canonical inputs.""" encoded = json.dumps(inputs, sort_keys=True, separators=(",", ":"), allow_nan=False) return hashlib.sha256(encoded.encode()).hexdigest() @@ -410,15 +363,7 @@ def _key(inputs: Mapping[str, object]) -> str: def _reduction_from(record: object) -> LinearReduction | None: """Rebuild a cached reduction, or None if malformed, non-finite or invalid. - Parameters - ---------- - record : object - Parsed ``reduction`` record of a cache entry. - - Returns - ------- - LinearReduction or None - A valid reduction with matching finite arrays, else None. + - Returns: a valid reduction with matching finite arrays, else None. """ # Explicit checks, not assertions: a cached entry is untrusted input, and # ``python -O`` would strip an assertion and admit a malformed entry. @@ -443,15 +388,7 @@ def _reduction_from(record: object) -> LinearReduction | None: def _finite_floats(values: tuple[object, ...]) -> bool: """Return whether every value is a finite float. - Parameters - ---------- - values : tuple - Candidate values. - - Returns - ------- - bool - True when each is a ``float`` and finite. + - Returns: True when each is a ``float`` and finite. """ return all(isinstance(value, float) and math.isfinite(value) for value in values) @@ -459,17 +396,9 @@ def _finite_floats(values: tuple[object, ...]) -> bool: def _cached_reduction_valid(reduction: LinearReduction) -> bool: """Check that a cached reduction is a complete, finite, self-consistent valid one. - Parameters - ---------- - reduction : LinearReduction - A reduction rebuilt from a cache entry. - - Returns - ------- - bool - True only when it is marked valid without a reason, its three arrays are - nonempty, equally long and finite, and its summary (deviation, worst - wavelength and worst kappa) is finite and agrees with the arrays. + - Returns: True only when it is marked valid without a reason, its three arrays are + nonempty, equally long and finite, and its summary (deviation, worst wavelength + and worst kappa) is finite and agrees with the arrays. """ size = len(reduction.wavelength) worst_kappa = reduction.worst_kappa @@ -508,18 +437,9 @@ def _read_cache( ) -> tuple[LinearReduction, dict[str, object]] | None: """Return a cache entry whose inputs, values and validity check out. - Parameters - ---------- - path : pathlib.Path - Entry file. - inputs : mapping - The key's inputs, which the entry must repeat exactly. - - Returns - ------- - tuple or None - The validated reduction and its computation record, or None when absent or - unusable (logged, never a pass). + - ``inputs``: the key's inputs, which the entry must repeat exactly. + - Returns: the validated reduction and its computation record, or None when absent + or unusable (logged, never a pass). """ if not path.exists(): return None @@ -542,15 +462,7 @@ def _read_cache( def _write_cache(path: Path, entry: Mapping[str, object]) -> None: - """Write a cache entry atomically: a temporary file, then a rename. - - Parameters - ---------- - path : pathlib.Path - Final entry path. - entry : mapping - JSON-compatible entry. - """ + """Write a cache entry atomically: a temporary file, then a rename.""" path.parent.mkdir(parents=True, exist_ok=True) handle, temporary = tempfile.mkstemp( dir=path.parent, prefix=f".{path.stem}.", suffix=".tmp" @@ -678,13 +590,10 @@ def record_consistency(self, record: Mapping[str, object]) -> None: ... @property - def plans_built(self) -> bool: - """Return whether the propagation plans exist. + def plans_built(self) -> bool: # numpydoc ignore=RT01 + """Whether the propagation plans exist. - Returns - ------- - bool - False while a deferred binding awaits :meth:`build_plans`. + False while a deferred binding awaits :meth:`build_plans`. """ ... @@ -694,20 +603,7 @@ def build_plans(self) -> None: def _decide(deviation: float, policy: TDSEConsistency) -> Outcome: - """Apply the bounds to a valid ``|kappa_1 - 1|`` (rules 2 to 4 of 5.4). - - Parameters - ---------- - deviation : float - Largest ``|kappa_1 - 1|``. - policy : TDSEConsistency - The bounds. - - Returns - ------- - {'passed', 'warned', 'refused'} - Passed at or below ``warn``, warned up to and including ``refuse``. - """ + """Apply the bounds to a valid ``|kappa_1 - 1|`` (rules 2 to 4 of 5.4).""" if deviation <= policy.warn: return "passed" if deviation <= policy.refuse: @@ -830,23 +726,7 @@ def evaluate_consistency( def _deviation(value: float | None) -> float: - """Return a value known to be a float. - - Parameters - ---------- - value : float or None - A valid reduction's deviation. - - Returns - ------- - float - The same value. - - Raises - ------ - BackendError - If the value is missing, which a valid reduction never allows. - """ + """Return a value known to be a float.""" if value is None: raise BackendError("a valid kappa_1 reduction has no deviation") return value @@ -1052,19 +932,7 @@ def _open_check_archive( ) -> TDSEArchiveWriter | None: """Open a new archive for a standalone check, before any atomic work. - Parameters - ---------- - archive : TDSEArchive or None - The request. - basis : HaccBasis - The checked basis. - execution : TDSEExecution - The check's resources. - - Returns - ------- - TDSEArchiveWriter or None - The session, or None without a request. + - Returns: the session, or None without a request. """ if archive is None: return None @@ -1084,18 +952,7 @@ def _open_check_archive( def _fields_of(result: ConsistencyResult) -> dict[str, object]: - """Return a result's fields without converting nested records. - - Parameters - ---------- - result : ConsistencyResult - A result. - - Returns - ------- - dict - Field name to value. - """ + """Return a result's fields without converting nested records.""" return {name: getattr(result, name) for name in result.__dataclass_fields__} diff --git a/src/solprop/fourier.py b/src/solprop/fourier.py index b763266eb..26af58ff0 100644 --- a/src/solprop/fourier.py +++ b/src/solprop/fourier.py @@ -76,16 +76,6 @@ def _check_kind(kind: str) -> None: The parameter is a plain string rather than :data:`GridKind` on purpose: this is the run-time guard for callers that ignore the annotation, and the test session's ``beartype`` hook would otherwise reject the bad value before this check is reached. - - Parameters - ---------- - kind : str - Layout name to check. - - Raises - ------ - GridError - If ``kind`` is not one of the two layout names. """ if kind not in ("field", "envelope"): msg = f"kind must be 'field' or 'envelope', got {kind!r}" @@ -95,22 +85,7 @@ def _check_kind(kind: str) -> None: def _n_t_from_n_omega(n_omega: int, kind: GridKind) -> int: """Return the number of time samples a spectrum of ``n_omega`` bins came from. - Parameters - ---------- - n_omega : int - Number of spectral bins. - kind : {'field', 'envelope'} - Spectral layout. - - Returns - ------- - int - The corresponding number of time samples, ``n_t``. - - Raises - ------ - GridError - If the bin count is too small to be a valid grid. + - ``kind``: spectral layout. """ _check_kind(kind) n_t = 2 * (n_omega - 1) if kind == "field" else n_omega @@ -160,20 +135,7 @@ def n_omega_for(n_t: int, kind: GridKind) -> int: def _check_time_dtype(x: np.ndarray, kind: GridKind) -> None: - """Check that time samples have the dtype their layout requires. - - Parameters - ---------- - x : numpy.ndarray - Time samples to check. - kind : {'field', 'envelope'} - Layout the samples are claimed to belong to. - - Raises - ------ - GridError - If a field array is complex or an envelope array is real. - """ + """Check that time samples have the dtype their layout requires.""" _check_kind(kind) if kind == "field" and np.iscomplexobj(x): msg = ( @@ -192,22 +154,8 @@ def _check_time_dtype(x: np.ndarray, kind: GridKind) -> None: def _check_spectrum(x_omega: np.ndarray, kind: GridKind) -> int: """Check a spectrum's trailing axis against its layout and return ``n_t``. - Parameters - ---------- - x_omega : numpy.ndarray - Spectrum whose last axis is the frequency axis. - kind : {'field', 'envelope'} - Spectral layout. - - Returns - ------- - int - The number of time samples the spectrum corresponds to. - - Raises - ------ - GridError - If the trailing axis is not a valid length for the layout. + - ``x_omega``: spectrum whose last axis is the frequency axis. + - ``kind``: spectral layout. """ return _n_t_from_n_omega(np.shape(x_omega)[-1], kind) @@ -457,18 +405,7 @@ def to_internal(x: Num[np.ndarray, "*shape"]) -> Num[np.ndarray, "*shape"]: def _origin_shift(n_omega: int) -> Float[np.ndarray, " n_omega"]: - """Return the half-window phase-origin factor as an array of alternating signs. - - Parameters - ---------- - n_omega : int - Number of spectral bins. - - Returns - ------- - numpy.ndarray - The values ``(-1)**k`` for storage index ``k``, shape ``(n_omega,)``. - """ + """Return the half-window phase-origin factor as an array of alternating signs.""" shift = np.ones(n_omega) shift[1::2] = -1.0 return shift @@ -592,21 +529,7 @@ def spectrum_to_internal( def _check_sizes(n_t: int, n_to: int) -> None: - """Check a coarse/fine size pair, naming the offending quantity. - - Parameters - ---------- - n_t : int - Coarse number of time samples. - n_to : int - Fine number of time samples. - - Raises - ------ - GridError - If either size is not an even integer of at least 4, if ``n_to`` is smaller than - ``n_t``, or if it is not an integer multiple of ``n_t``. - """ + """Check a coarse/fine size pair, naming the offending quantity.""" for name, value in (("n_t", n_t), ("n_to", n_to)): if value < MIN_N_T or value % 2: msg = f"{name} must be an even integer of at least {MIN_N_T}, got {value}" diff --git a/src/solprop/gasflow.py b/src/solprop/gasflow.py index d246ad2c7..dca1f583f 100644 --- a/src/solprop/gasflow.py +++ b/src/solprop/gasflow.py @@ -237,23 +237,9 @@ class _FlowGas: def of(cls, gas: Composition | Mixture, temperature: float) -> "_FlowGas": """Evaluate the properties of a gas or a shared mixture fill. - Parameters - ---------- - gas : Gas, str, Species, Mapping or Mixture - One built-in gas, gases to mole fractions, or a mixture with a shared fill. - temperature : float - Temperature in K. + A mixture with independent populations is refused: it is not one flow. - Returns - ------- - _FlowGas - The properties. - - Raises - ------ - MaterialError - If the gas has no transport data at this temperature, or a mixture has - independent populations, which are not one flow. + - ``temperature``: temperature in K. """ composition = _as_composition(gas) return cls( @@ -266,23 +252,7 @@ def of(cls, gas: Composition | Mixture, temperature: float) -> "_FlowGas": def _as_composition(gas: Composition | Mixture) -> Composition: - """Return the composition a flow calculation takes, from a gas or a mixture. - - Parameters - ---------- - gas : Gas, str, Species, Mapping or Mixture - The gas. - - Returns - ------- - Gas, str, Species or Mapping - A single gas, or species to mole fractions. - - Raises - ------ - MaterialError - If a mixture has independent populations. - """ + """Return the composition a flow calculation takes, from a gas or a mixture.""" if not isinstance(gas, Mixture): return gas if gas.shared is None or gas.fractions is None: @@ -334,25 +304,8 @@ def of( ) -> "_TemperatureFlow": """Fit the viscosity law over the temperatures a path takes on a fill. - Parameters - ---------- - gas : Gas, str, Species, Mapping or Mixture - One built-in gas, gases to mole fractions, or a mixture with a shared fill. - temperature : ZProfile - The temperature path in K. - positions : numpy.ndarray - The fill's stage boundaries in m. - - Returns - ------- - _TemperatureFlow - The properties. - - Raises - ------ - MaterialError - If the gas has no transport data over the temperature range, or its - viscosity does not converge to a series by the highest degree tried. + - ``temperature``: the temperature path in K. + - ``positions``: the fill's stage boundaries in m. """ composition = _as_composition(gas) start, end = float(positions[0]), float(positions[-1]) @@ -379,13 +332,7 @@ def of( @property def weight(self) -> SeriesOfPath: - """Return the flow weight ``eta(T) T`` along the fibre, in Pa s K. - - Returns - ------- - solprop.profiles.SeriesOfPath - The weight as a series of the temperature path. - """ + """Return the flow weight ``eta(T) T`` along the fibre, in Pa s K.""" law = self.viscosity_law return SeriesOfPath( self.temperature, law * Chebyshev.identity(domain=law.domain) @@ -394,15 +341,7 @@ def weight(self) -> SeriesOfPath: def at(self, z: float) -> _FlowGas: """Return the properties at one position, evaluated directly. - Parameters - ---------- - z : float - Position in m. - - Returns - ------- - _FlowGas - The properties at the temperature there. + - ``z``: position in m. """ local = float(self.temperature(np.array([z]))[0]) return _FlowGas.of(self.composition, local) @@ -410,15 +349,8 @@ def at(self, z: float) -> _FlowGas: def viscosity(self, z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: """Return the viscosity at each position, from the series. - Parameters - ---------- - z : numpy.ndarray - Positions in m. - - Returns - ------- - numpy.ndarray - ``eta(T(z))`` in Pa s. + - ``z``: positions in m. + - Returns: ``eta(T(z))`` in Pa s. """ return np.asarray(self.viscosity_law(self.temperature(z)), dtype=float) @@ -427,15 +359,9 @@ def mean_free_path_at_one_bar( ) -> Float[np.ndarray, " n_z"]: """Return the mean free path at one bar and the local temperature. - Parameters - ---------- - z : numpy.ndarray - Positions in m. - - Returns - ------- - numpy.ndarray - ``lambda`` in m, :func:`solprop.physdata.transport.mean_free_path` at 1 bar. + - ``z``: positions in m. + - Returns: ``lambda`` in m, :func:`solprop.physdata.transport.mean_free_path` at + 1 bar. """ local = np.asarray(self.temperature(z), dtype=float) speed = np.sqrt(math.pi * _GAS_CONSTANT * local / (2.0 * self.molar_mass)) @@ -455,39 +381,15 @@ def _viscosity_law( measured error, not a coefficient tail, since CoolProp's helium viscosity is not smooth enough for its tail to fall. - Parameters - ---------- - composition : Gas, str, Species or Mapping - The gas. - low, high : float - The temperature range in K, ``low < high``. - degrees : tuple of int, optional - The degrees tried in turn. - - Returns - ------- - numpy.polynomial.Chebyshev - ``eta(T)`` in Pa s on ``[low, high]``. - - Raises - ------ - MaterialError - If the gas has no transport data there, or no degree tried meets the - tolerance. + - ``low``, ``high``: the temperature range in K, ``low < high``. + - Returns: ``eta(T)`` in Pa s on ``[low, high]``. """ def values(temperatures: Float[np.ndarray, " n_t"]) -> Float[np.ndarray, " n_t"]: """Return the viscosity at each temperature. - Parameters - ---------- - temperatures : numpy.ndarray - Temperatures in K. - - Returns - ------- - numpy.ndarray - ``eta`` in Pa s. + - ``temperatures``: temperatures in K. + - Returns: ``eta`` in Pa s. """ return np.array([viscosity(composition, float(t)) for t in temperatures]) @@ -505,25 +407,7 @@ def values(temperatures: Float[np.ndarray, " n_t"]) -> Float[np.ndarray, " n_t"] def _positive(value: float, what: str) -> float: - """Return a finite positive number, or refuse it by name. - - Parameters - ---------- - value : float - The number. - what : str - Its name and unit, for the error. - - Returns - ------- - float - The number as a float. - - Raises - ------ - TranslationError - If it is not finite and positive. - """ + """Return a finite positive number, or refuse it by name.""" number = float(value) if not (math.isfinite(number) and number > 0.0): raise TranslationError(f"{what} must be finite and positive, got {value}") @@ -535,20 +419,6 @@ def _checked_choice(value: object, name: str, choices: tuple[str, ...]) -> None: The annotations already say which strings are allowed; this is the check a caller without a type checker meets. - - Parameters - ---------- - value : object - What was passed. - name : str - The keyword, for the error. - choices : tuple of str - The allowed values. - - Raises - ------ - TranslationError - If `value` is not one of `choices`. """ if value not in choices: raise TranslationError(f"{name} must be one of {choices}, got {value!r}") @@ -557,17 +427,9 @@ def _checked_choice(value: object, name: str, choices: tuple[str, ...]) -> None: def _choked_from(high_pa: float, factor: float) -> float: """Solve ``k (p_h^2 - p_e^2) = p_e`` for the exit pressure without cancellation. - Parameters - ---------- - high_pa : float - The stage's other end pressure in Pa, >= 0. - factor : float - ``k = 1 / (16 eta c a_e^2 R)`` in Pa^-1, > 0. - - Returns - ------- - float - The choked exit pressure in Pa. + - ``high_pa``: the stage's other end pressure in Pa, >= 0. + - ``factor``: ``k = 1 / (16 eta c a_e^2 R)`` in Pa^-1, > 0. + - Returns: the choked exit pressure in Pa. """ # The positive root 2 k p^2 / (1 + sqrt(1 + 4 k^2 p^2)) rather than # (-1 + sqrt(1 + 4 k^2 p^2)) / (2 k): the latter cancels to nothing when k p << 1, @@ -581,21 +443,9 @@ def _choke( ) -> float: """Return the choked exit pressure in Pa of a stage. - Parameters - ---------- - flow : _FlowGas - The gas properties. - high_pa : float - The other end of the stage in Pa. - exit_radius : float - The core radius at the exit in m. - resistance : float - The stage's ``int a^-4 dz`` in m^-3. - - Returns - ------- - float - The choked exit pressure in Pa. + - ``high_pa``: the other end of the stage in Pa. + - ``exit_radius``: the core radius at the exit in m. + - ``resistance``: the stage's ``int a^-4 dz`` in m^-3. """ factor = 1.0 / ( 16.0 * flow.viscosity * flow.sound_speed * exit_radius**2 * resistance @@ -606,17 +456,9 @@ def _choke( def _resistance(radius: float | ZProfile, length: float) -> tuple[ZProfile, float]: """Return a radius path and its hydraulic resistance over ``[0, length]``. - Parameters - ---------- - radius : float or ZProfile - Core radius in m. - length : float - Length in m, > 0. - - Returns - ------- - tuple - The radius as a path, and ``int_0^length a^-4 dz`` in m^-3. + - ``radius``: core radius in m. + - ``length``: length in m, > 0. + - Returns: the radius as a path, and ``int_0^length a^-4 dz`` in m^-3. """ path = as_profile(radius) edges = np.array([0.0, _positive(length, "length in m")]) @@ -624,25 +466,7 @@ def _resistance(radius: float | ZProfile, length: float) -> tuple[ZProfile, floa def _checked_pressure(value: float, what: str) -> float: - """Return a finite non-negative pressure in bar, or refuse it by name. - - Parameters - ---------- - value : float - The pressure in bar. - what : str - Its name, for the error. - - Returns - ------- - float - The pressure as a float. - - Raises - ------ - TranslationError - If it is negative or not finite. - """ + """Return a finite non-negative pressure in bar, or refuse it by name.""" number = float(value) if not (math.isfinite(number) and number >= 0.0): raise TranslationError(f"{what} must be finite and not negative, got {value}") @@ -708,19 +532,9 @@ def _poiseuille( ) -> float: """Return the laminar throughput between two pressures in Pa. - Parameters - ---------- - flow : _FlowGas - The gas properties. - first_pa, second_pa : float - The two end pressures in Pa, in either order. - resistance : float - ``int a^-4 dz`` in m^-3. - - Returns - ------- - float - ``pi |p_1^2 - p_2^2| / (16 eta R)`` in Pa m^3 s^-1. + - ``first_pa``, ``second_pa``: the two end pressures in Pa, in either order. + - ``resistance``: ``int a^-4 dz`` in m^-3. + - Returns: ``pi |p_1^2 - p_2^2| / (16 eta R)`` in Pa m^3 s^-1. """ # (p1 - p2)(p1 + p2): the difference of the squares without their cancellation. difference = abs(first_pa - second_pa) * (first_pa + second_pa) @@ -836,17 +650,7 @@ def mass_flow( def _mass_per_pv(flow: _FlowGas, pressure_bar: float) -> float: """Return ``rho / p`` in kg per Pa m^3 at a pressure, real-gas where it matters. - Parameters - ---------- - flow : _FlowGas - The gas properties. - pressure_bar : float - Pressure in bar, >= 0. - - Returns - ------- - float - Mass density over pressure, ``M / (Z R T)``. + - ``pressure_bar``: pressure in bar, >= 0. """ if pressure_bar == 0.0: return flow.molar_mass / (_GAS_CONSTANT * flow.temperature) @@ -915,26 +719,9 @@ def _reynolds( ) -> Float[np.ndarray, " n_z"]: """Return ``2 mdot / (pi a eta)`` at each position. - Parameters - ---------- - flow : _FlowGas - The gas properties. - rate : float - Mass flow in kg s^-1. - radius : ZProfile - Core radius in m. - z : numpy.ndarray - Positions in m. - - Returns - ------- - numpy.ndarray - The Reynolds number at each position. - - Raises - ------ - TranslationError - If the radius is not positive at a position. + - ``rate``: mass flow in kg s^-1. + - ``radius``: core radius in m. + - ``z``: positions in m. """ values = np.asarray(radius(z), dtype=float) if not np.all(np.isfinite(values)) or np.any(values <= 0.0): @@ -1049,19 +836,10 @@ def _pump_excess( ) -> float: """Return a pump's throughput less the gas's at a chamber pressure. - Parameters - ---------- - pressure_bar : float - Chamber pressure in bar. - pump_speed : callable - Pumping speed in m^3 s^-1 as a function of the pressure in bar. - rate : float - The gas throughput in Pa m^3 s^-1. - - Returns - ------- - float - ``p S(p) - Q`` in Pa m^3 s^-1. + - ``pressure_bar``: chamber pressure in bar. + - ``pump_speed``: pumping speed in m^3 s^-1 as a function of the pressure in bar. + - ``rate``: the gas throughput in Pa m^3 s^-1. + - Returns: ``p S(p) - Q`` in Pa m^3 s^-1. """ speed = _positive(pump_speed(pressure_bar), "pump speed in m^3/s") return pressure_bar * BAR * speed - rate @@ -1180,37 +958,28 @@ class StageFlow(Displayed): transitional_fraction: float @property - def exit_knudsen(self) -> float: - """Return the Knudsen number at the stage's lower-pressure end. + def exit_knudsen(self) -> float: # numpydoc ignore=RT01 + """Knudsen number at the stage's lower-pressure end. - Returns - ------- - float - ``knudsen_end`` when the flow runs towards +z, else ``knudsen_start``. + ``knudsen_end`` when the flow runs towards +z, else ``knudsen_start``. """ return ( self.knudsen_end if self.end_bar <= self.start_bar else self.knudsen_start ) @property - def throughput_mbar_litres_per_second(self) -> float: - """Return the throughput in mbar l/s. + def throughput_mbar_litres_per_second(self) -> float: # numpydoc ignore=RT01 + """Throughput in mbar l/s. - Returns - ------- - float - :func:`mbar_litres_per_second` of :attr:`throughput`. + :func:`mbar_litres_per_second` of :attr:`throughput`. """ return mbar_litres_per_second(self.throughput) @property - def throughput_slpm(self) -> float: - """Return the throughput in standard litres per minute. + def throughput_slpm(self) -> float: # numpydoc ignore=RT01 + """Throughput in standard litres per minute. - Returns - ------- - float - :func:`standard_litres_per_minute` of :attr:`throughput`. + :func:`standard_litres_per_minute` of :attr:`throughput`. """ return standard_litres_per_minute(self.throughput, self.temperature) @@ -1262,36 +1031,18 @@ class FlowReport(Displayed): end_is_choked: bool @property - def peak_reynolds(self) -> float: - """Return the largest Reynolds number of the fill. - - Returns - ------- - float - The maximum over the stages. - """ + def peak_reynolds(self) -> float: # numpydoc ignore=RT01 + """Largest Reynolds number of the fill.""" return max(stage.peak_reynolds for stage in self.stages) @property - def peak_reynolds_position(self) -> float: - """Return where the largest Reynolds number is. - - Returns - ------- - float - The position in m. - """ + def peak_reynolds_position(self) -> float: # numpydoc ignore=RT01 + """Where the largest Reynolds number is, m.""" return max(self.stages, key=lambda s: s.peak_reynolds).peak_reynolds_position @property - def peak_knudsen(self) -> float: - """Return the largest Knudsen number of the fill. - - Returns - ------- - float - The maximum over the stages. - """ + def peak_knudsen(self) -> float: # numpydoc ignore=RT01 + """Largest Knudsen number of the fill.""" return max(stage.peak_knudsen for stage in self.stages) def __repr__(self) -> str: @@ -1323,10 +1074,7 @@ def __repr__(self) -> str: def _describe(self) -> tuple[str, list[tuple[str, object]]]: """Return the title and rows of the printed block. - Returns - ------- - tuple - The title and ``(key, value)`` rows. + - Returns: the title and ``(key, value)`` rows. """ rows: list[tuple[str, object]] = [ ("viscosity", f"{self.viscosity * 1e6:.4g} uPa s"), @@ -1359,17 +1107,7 @@ def _stage_samples( ) -> Float[np.ndarray, " n_samples"]: """Return the positions at which a stage's flow regime is sampled. - Parameters - ---------- - fill : PressureGradient - The fill. - stage : int - The stage index. - - Returns - ------- - numpy.ndarray - The stage's breakpoints and evenly spaced interior positions, in m. + - Returns: the stage's breakpoints and evenly spaced interior positions, in m. """ start, end = float(fill.positions[stage]), float(fill.positions[stage + 1]) kinks = np.asarray(fill.breakpoints, dtype=float) @@ -1393,26 +1131,11 @@ def _stage_flow( # noqa: PLR0917 - called positionally ) -> StageFlow: """Return the flow through one stage. - Parameters - ---------- - flow : _FlowGas - The gas properties; for a temperature path, those at the fill's start. - fill : PressureGradient - The fill. - radius : ZProfile - Core radius in m. - stage : int - The stage index. - resistance : float - The stage's ``int a^-4 dz`` in m^-3; for a temperature path its - ``int eta T a^-4 dz`` in Pa s K m^-3. - along : _TemperatureFlow or None, optional - The properties along a temperature path; ``None`` for a constant temperature. - - Returns - ------- - StageFlow - The stage's flow. + - ``radius``: core radius in m. + - ``resistance``: the stage's ``int a^-4 dz`` in m^-3; for a temperature path its + ``int eta T a^-4 dz`` in Pa s K m^-3. + - ``along``: the properties along a temperature path; ``None`` for a constant + temperature. """ first, second = (float(p) for p in fill.pressures[stage : stage + 2]) samples = _stage_samples(fill, stage) @@ -1431,15 +1154,8 @@ def _stage_flow( # noqa: PLR0917 - called positionally def free_path_of(z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: """Return the mean free path at one bar, the same everywhere. - Parameters - ---------- - z : numpy.ndarray - Positions in m. - - Returns - ------- - numpy.ndarray - ``lambda(1 bar)`` in m at each position. + - ``z``: positions in m. + - Returns: ``lambda(1 bar)`` in m at each position. """ return np.full(np.shape(z), at_one_bar) @@ -1499,51 +1215,25 @@ def _transitional( density ``int p dz`` is integrated by adaptive quadrature across the fill's breakpoints, where ``p`` kinks. - Parameters - ---------- - fill : PressureGradient - The fill. - radius : ZProfile - Core radius in m. - samples : numpy.ndarray - The stage's sampled positions in m, its breakpoints included. - free_path_of : callable - The mean free path at one bar in m at given positions: the same everywhere - at a constant temperature, the local one along a temperature path. - - Returns - ------- - tuple of float - The transitional length in m and its share of the column density. + - ``radius``: core radius in m. + - ``samples``: the stage's sampled positions in m, its breakpoints included. + - ``free_path_of``: the mean free path at one bar in m at given positions: the same + everywhere at a constant temperature, the local one along a temperature path. """ def threshold(z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: """Return the transitional threshold of ``p a`` at each position. - Parameters - ---------- - z : numpy.ndarray - Positions in m. - - Returns - ------- - numpy.ndarray - ``lambda(1 bar) / (2 Kn_t)`` in bar m. + - ``z``: positions in m. + - Returns: ``lambda(1 bar) / (2 Kn_t)`` in bar m. """ return free_path_of(z) / (2.0 * KNUDSEN_TRANSITION_LIMIT) def excess(z: float) -> float: """Return ``p a`` less its transitional threshold at one position. - Parameters - ---------- - z : float - Position in m. - - Returns - ------- - float - ``p(z) a(z) - lambda(1 bar) / 0.2`` in bar m. + - ``z``: position in m. + - Returns: ``p(z) a(z) - lambda(1 bar) / 0.2`` in bar m. """ position = np.array([z]) return float(fill(position)[0] * radius(position)[0] - threshold(position)[0]) @@ -1563,15 +1253,8 @@ def excess(z: float) -> float: def column(low: float, high: float) -> float: """Return the column density between two positions. - Parameters - ---------- - low, high : float - The interval in m. - - Returns - ------- - float - ``int p dz`` in bar m. + - ``low``, ``high``: the interval in m. + - Returns: ``int p dz`` in bar m. """ inside = kinks[(kinks > low) & (kinks < high)] value, _ = quad( @@ -1603,26 +1286,16 @@ def _outer_chokes( ) -> tuple[float | None, float | None]: """Return the choked pressure in bar of each outer end that is a stage's low end. - Parameters - ---------- - flows : tuple of _FlowGas - The gas properties at the first and the last end: the same object at a - constant temperature, each end's own along a temperature path. - pressures_bar : numpy.ndarray - Stage pressures in bar. - exit_radii : tuple of float - The core radius in m at the first and the last position. - resistances : tuple of float - The ``int a^-4 dz`` in m^-3 of the first and the last stage; along a - temperature path ``int (eta T) / (eta_e T_e) a^-4 dz`` with the end's own - ``eta_e T_e``, which turns the choke into ``T_e / (16 c_e a_e^2 int eta T - a^-4 dz)``. - - Returns - ------- - tuple - The first and last end's choked pressure in bar, or ``None`` where that end - is not lower than the other end of its stage. + - ``flows``: the gas properties at the first and the last end: the same object at a + constant temperature, each end's own along a temperature path. + - ``pressures_bar``: stage pressures in bar. + - ``exit_radii``: the core radius in m at the first and the last position. + - ``resistances``: the ``int a^-4 dz`` in m^-3 of the first and the last stage; + along a temperature path ``int (eta T) / (eta_e T_e) a^-4 dz`` with the end's own + ``eta_e T_e``, which turns the choke into ``T_e / (16 c_e a_e^2 int eta T a^-4 + dz)``. + - Returns: the first and last end's choked pressure in bar, or ``None`` where that + end is not lower than the other end of its stage. """ chokes: list[float | None] = [] for end, other, flow, exit_radius, resistance in ( @@ -1650,30 +1323,13 @@ def _end_flows( ]: """Return what the chokes and the report of a fill need, for either temperature. - Parameters - ---------- - gas : Gas, str, Species, Mapping or Mixture - The gas. - temperature : float or ZProfile - Temperature in K, validated: a number, or a varying path. - radius : ZProfile - Core radius in m. - positions : numpy.ndarray - Stage boundaries in m. - - Returns - ------- - tuple - The gas at the first and last end, the equivalent resistances of the first - and last stage (:func:`_outer_chokes`), every stage's resistance (``int - a^-4 dz`` in m^-3, or ``int eta T a^-4 dz`` in Pa s K m^-3 along a - temperature path), and the properties along the path (``None`` at a - constant temperature). - - Raises - ------ - MaterialError - If the gas has no transport data at the temperatures. + - ``temperature``: temperature in K, validated: a number, or a varying path. + - ``radius``: core radius in m. + - ``positions``: stage boundaries in m. + - Returns: the gas at the first and last end, the equivalent resistances of the + first and last stage (:func:`_outer_chokes`), every stage's resistance (``int a^-4 + dz`` in m^-3, or ``int eta T a^-4 dz`` in Pa s K m^-3 along a temperature path), + and the properties along the path (``None`` at a constant temperature). """ if not isinstance(temperature, ZProfile): flow = _FlowGas.of(gas, temperature) @@ -1701,17 +1357,10 @@ def _end_flows( def _at_choke(pressure: float, choked: float | None) -> bool: """Return whether an end pressure is at or below its choked value. - Parameters - ---------- - pressure : float - The end's pressure in bar. - choked : float or None - Its choked pressure in bar, or ``None`` where the end is not a low end. - - Returns - ------- - bool - ``True`` when the exit throughput is the sonic limit. + - ``pressure``: the end's pressure in bar. + - ``choked``: its choked pressure in bar, or ``None`` where the end is not a low + end. + - Returns: ``True`` when the exit throughput is the sonic limit. """ return choked is not None and pressure <= choked * (1.0 + _CHOKE_MATCH) @@ -1974,24 +1623,13 @@ def _apply_chokes( ) -> Float[np.ndarray, " n_positions"]: """Raise each outer low end to its choked pressure, logging what changed. - Parameters - ---------- - flows : tuple of _FlowGas - The gas properties at the first and the last end. - resistances : tuple of float - The equivalent resistances of the first and last stage - (:func:`_outer_chokes`). - edges : numpy.ndarray - Stage boundaries in m. - declared : numpy.ndarray - Declared pressures in bar. - radius : ZProfile - Core radius in m. - - Returns - ------- - numpy.ndarray - The pressures in bar with every choked end applied. + - ``flows``: the gas properties at the first and the last end. + - ``resistances``: the equivalent resistances of the first and last stage + (:func:`_outer_chokes`). + - ``edges``: stage boundaries in m. + - ``declared``: declared pressures in bar. + - ``radius``: core radius in m. + - Returns: the pressures in bar with every choked end applied. """ ends = np.asarray(radius(edges[[0, -1]]), dtype=float) applied = declared.copy() @@ -2027,21 +1665,7 @@ def _apply_chokes( def _check_regime(report: FlowReport) -> None: - """Warn or log where a fill leaves the laminar continuum regime. - - Parameters - ---------- - report : FlowReport - The fill's flow. - - Warns - ----- - GasFlowWarning - For a peak Reynolds number above :data:`REYNOLDS_LAMINAR_LIMIT`, or a stage - whose mean-pressure Knudsen number exceeds :data:`KNUDSEN_SLIP_LIMIT` or whose - transitional share of the column density exceeds - :data:`TRANSITIONAL_COLUMN_LIMIT`. - """ + """Warn or log where a fill leaves the laminar continuum regime.""" if report.peak_reynolds > REYNOLDS_LAMINAR_LIMIT: warnings.warn( f"the steady-flow fill has a peak Reynolds number of " diff --git a/src/solprop/grid.py b/src/solprop/grid.py index f9b77f2c0..a40bc8abb 100644 --- a/src/solprop/grid.py +++ b/src/solprop/grid.py @@ -147,19 +147,8 @@ def _taper_edge( ) -> Float[np.ndarray, " n"]: """Evaluate one Planck taper edge, strictly between the two breakpoints. - Parameters - ---------- - x : numpy.ndarray - Points strictly between ``outer`` and ``inner``, shape ``(n,)``. - outer : float - Breakpoint at which the edge reaches 0. - inner : float - Breakpoint at which the edge reaches 1. - - Returns - ------- - numpy.ndarray - Edge values in ``(0, 1)``, shape ``(n,)``. + - ``x``: points strictly between ``outer`` and ``inner``, shape ``(n,)``. + - Returns: edge values in ``(0, 1)``, shape ``(n,)``. """ width = inner - outer exponent = width / (x - outer) + width / (x - inner) @@ -334,17 +323,7 @@ def _derivative_edges( ) -> tuple[tuple[float, float, Bool[np.ndarray, " n"]], ...]: """Validate the breakpoints and select the points strictly inside each edge. - Parameters - ---------- - values : numpy.ndarray - Points, shape ``(n,)``. - zero_low, one_low, one_high, zero_high : float - Breakpoints of :func:`planck_taper`. - - Returns - ------- - tuple - ``(outer, inner, mask)`` for the rising and the falling edge. + - Returns: ``(outer, inner, mask)`` for the rising and the falling edge. """ if not zero_low < one_low <= one_high < zero_high: msg = ( @@ -369,22 +348,9 @@ def _taper_edge_derivative( ) -> tuple[Float[np.ndarray, " n"], Float[np.ndarray, " n"]]: """Evaluate one edge's derivative strictly between its two breakpoints. - Parameters - ---------- - x : numpy.ndarray - Points strictly between ``outer`` and ``inner``, shape ``(n,)``. - outer : float - Breakpoint at which the edge reaches 0. - inner : float - Breakpoint at which the edge reaches 1. - - Returns - ------- - slope : numpy.ndarray - ``dg/dx``, shape ``(n,)``; the asymptotic zero in the cut-off region. - exponent : numpy.ndarray - ``zeta`` where the slope is evaluated, NaN in the cut-off region, where no - reciprocal is formed. + - ``x``: points strictly between ``outer`` and ``inner``, shape ``(n,)``. + - Returns ``exponent``: ``zeta`` where the slope is evaluated, NaN in the cut-off + region, where no reciprocal is formed. """ width = inner - outer # Both distances are formed from x directly. r = 1 - s would cancel next to the @@ -465,69 +431,45 @@ def __post_init__(self) -> None: self.validate() @property - def outer_radius(self) -> float: - """Return the Dirichlet boundary: the aperture with its collar's room. + def outer_radius(self) -> float: # numpydoc ignore=RT01 + """Dirichlet boundary: the aperture with its collar's room, m. - Returns - ------- - float - ``radius * (1 + collar_fraction)``, m. + ``radius * (1 + collar_fraction)``. """ return self.radius * (1.0 + self.collar_fraction) @property - def r(self) -> Float[np.ndarray, " n_r"]: - """Return the positive interior radial samples. + def r(self) -> Float[np.ndarray, " n_r"]: # numpydoc ignore=RT01 + """Positive interior radial samples, m. - Returns - ------- - numpy.ndarray - Radii in m, shape ``(n_r,)``; zero and the outer boundary are excluded. + Shape ``(n_r,)``; zero and the outer boundary are excluded. """ return self.hankel.r @property - def k_r(self) -> Float[np.ndarray, " n_r"]: - """Return the transverse spectral samples. - - Returns - ------- - numpy.ndarray - Positive angular frequencies in rad/m, shape ``(n_r,)``. - """ + def k_r(self) -> Float[np.ndarray, " n_r"]: # numpydoc ignore=RT01 + """Positive transverse spectral samples, rad/m, shape ``(n_r,)``.""" return self.hankel.k_r @property - def radial_weights(self) -> Float[np.ndarray, " n_r"]: - """Return the quadrature weights of the transverse area integral. + def radial_weights(self) -> Float[np.ndarray, " n_r"]: # numpydoc ignore=RT01 + """Quadrature weights of the transverse area integral, m^2. - Returns - ------- - numpy.ndarray - Weights of ``2*pi*r*dr``, in m^2, shape ``(n_r,)``. + Weights of ``2*pi*r*dr``, shape ``(n_r,)``. """ return self.hankel.radial_weights @property - def spectral_weights(self) -> Float[np.ndarray, " n_r"]: - """Return the quadrature weights of the transverse spectral integral. + def spectral_weights(self) -> Float[np.ndarray, " n_r"]: # numpydoc ignore=RT01 + """Quadrature weights of the transverse spectral integral, m^-2. - Returns - ------- - numpy.ndarray - Weights of ``2*pi*k_r*dk_r``, in m^-2, shape ``(n_r,)``. + Weights of ``2*pi*k_r*dk_r``, shape ``(n_r,)``. """ return self.hankel.spectral_weights @property - def n_columns(self) -> int: - """Return the number of transverse spectral columns. - - Returns - ------- - int - The radial sample count. - """ + def n_columns(self) -> int: # numpydoc ignore=RT01 + """Number of transverse spectral columns.""" return self.n_r def validate(self) -> None: @@ -579,10 +521,7 @@ def __repr__(self) -> str: def _describe(self) -> tuple[str, list[tuple[str, object]]]: """Return the title and rows of the printed block. - Returns - ------- - tuple - The title and ``(key, value)`` rows. + - Returns: the title and ``(key, value)`` rows. """ return "RadialGrid: a radial grid on the zeros of J0", [ ("aperture radius", engineering(self.radius, "m")), @@ -599,20 +538,7 @@ def _describe(self) -> tuple[str, list[tuple[str, object]]]: def _check_fraction(owner: str, fraction: float) -> None: - """Refuse a collar fraction that is not finite and at least zero. - - Parameters - ---------- - owner : str - The grid class, named in the message. - fraction : float - The room outside the requested size, as a fraction of it. - - Raises - ------ - GridError - If the fraction is negative or not finite. - """ + """Refuse a collar fraction that is not finite and at least zero.""" if ( isinstance(fraction, bool) or not isinstance(fraction, int | float) @@ -630,27 +556,12 @@ def _periodic_axis( ]: """Build one centred periodic Cartesian axis, its FFT frequencies and its collar. - Parameters - ---------- - owner : str - The grid class, named in the error messages. - radius : float - Requested physical half-width in m, finite and positive. - count : int - Number of samples, at least 1 (validated by the caller). - collar_fraction : float - Room outside the requested half-width, as a fraction of it, at least 0. - - Returns - ------- - tuple - The centred axis in m, the angular frequencies in rad/m in unshifted FFT - order, the Planck collar, each of shape ``(count,)``, and the spacing in m. - - Raises - ------ - GridError - If the extent, the spacing or the wave-number squares are not representable. + - ``radius``: requested physical half-width in m, finite and positive. + - ``count``: number of samples, at least 1 (validated by the caller). + - ``collar_fraction``: room outside the requested half-width, as a fraction of it, + at least 0. + - Returns: the centred axis in m, the angular frequencies in rad/m in unshifted FFT + order, the Planck collar, each of shape ``(count,)``, and the spacing in m. """ outer = radius * (1 + collar_fraction) spacing = 2 * (outer / count) @@ -761,22 +672,11 @@ def _adopt( ) -> None: """Derive the flattened columns from the two axes and freeze them. - Parameters - ---------- - x, y : numpy.ndarray - Centred physical axes in m. - frequencies : tuple of numpy.ndarray - ``(k_x, k_y)`` in rad/m, in unshifted FFT order. - windows : tuple of numpy.ndarray - The x and y Planck collars. - area : float - The measure of one column: ``dx*dy`` in m^2, or ``dx`` times the unit - y measure for a line. - - Raises - ------ - GridError - If the measure or a derived array is not representable. + - ``x``, ``y``: centred physical axes in m. + - ``frequencies``: ``(k_x, k_y)`` in rad/m, in unshifted FFT order. + - ``windows``: the x and y Planck collars. + - ``area``: the measure of one column: ``dx*dy`` in m^2, or ``dx`` times the + unit y measure for a line. """ k_x, k_y = frequencies window_x, window_y = windows @@ -804,13 +704,10 @@ def _adopt( self.validate() @property - def n_columns(self) -> int: - """Return the number of flattened transverse spectral columns. + def n_columns(self) -> int: # numpydoc ignore=RT01 + """Number of flattened transverse spectral columns. - Returns - ------- - int - ``n_x*n_y``. + ``n_x*n_y``. """ return self.n_x * self.n_y @@ -888,10 +785,7 @@ def __repr__(self) -> str: def _describe(self) -> tuple[str, list[tuple[str, object]]]: """Return the title and rows of the printed block. - Returns - ------- - tuple - The title and ``(key, value)`` rows. + - Returns: the title and ``(key, value)`` rows. """ return "FreeGrid: a Cartesian transverse grid", [ ( @@ -1001,14 +895,11 @@ def __post_init__(self) -> None: ) @property - def transverse_measure(self) -> Literal["per_unit_width"]: - """Return what the grid's transverse integrals measure. + def transverse_measure(self) -> Literal["per_unit_width"]: # numpydoc ignore=RT01 + """What the grid's transverse integrals measure. - Returns - ------- - str - ``"per_unit_width"``: energies are J/m and powers W/m, per metre of y - (:data:`solprop.modes.protocol.TransverseMeasure`). + ``"per_unit_width"``: energies are J/m and powers W/m, per metre of y + (:data:`solprop.modes.protocol.TransverseMeasure`). """ return "per_unit_width" @@ -1027,10 +918,7 @@ def __repr__(self) -> str: def _describe(self) -> tuple[str, list[tuple[str, object]]]: """Return the title and rows of the printed block. - Returns - ------- - tuple - The title and ``(key, value)`` rows. + - Returns: the title and ``(key, value)`` rows. """ return "LineGrid: an x line, uniform in y (per metre of y)", [ ( @@ -1078,24 +966,18 @@ class Collar: zero_high: float @property - def width_low(self) -> float: - """Return the width of the rising edge, in the collar's own units. + def width_low(self) -> float: # numpydoc ignore=RT01 + """Width of the rising edge, in the collar's own units. - Returns - ------- - float - The distance from ``zero_low`` to ``one_low``. + The distance from ``zero_low`` to ``one_low``. """ return self.one_low - self.zero_low @property - def width_high(self) -> float: - """Return the width of the falling edge, in the collar's own units. + def width_high(self) -> float: # numpydoc ignore=RT01 + """Width of the falling edge, in the collar's own units. - Returns - ------- - float - The distance from ``one_high`` to ``zero_high``. + The distance from ``one_high`` to ``zero_high``. """ return self.zero_high - self.one_high @@ -1180,18 +1062,7 @@ def fft_cost(n: int) -> float: def _smooth_sizes(low: int, high: int) -> list[int]: - """Return every even 2-3-5-smooth integer in ``[low, high]``. - - Parameters - ---------- - low, high : int - Inclusive bounds. - - Returns - ------- - list of int - The sizes, increasing. - """ + """Return every even 2-3-5-smooth integer in ``[low, high]``.""" found = [] size = low while size <= high: @@ -1207,24 +1078,7 @@ def _smooth_sizes(low: int, high: int) -> list[int]: def _needed_window(trange: float, collar_fraction: float, placement: str) -> float: """Return the window a grid must realise at least, s. - Parameters - ---------- - trange : float - Requested window, s. - collar_fraction : float - Width of each temporal collar as a fraction of the realised window. - placement : {'outside', 'inside'} - Where the collar lies against the request. - - Returns - ------- - float - ``trange / (1 - 2 collar_fraction)`` outside, ``trange`` inside. - - Raises - ------ - GridError - If the placement is neither. + - ``trange``: requested window, s. """ if placement == "outside": return trange / (1.0 - 2.0 * collar_fraction) @@ -1237,16 +1091,8 @@ def _needed_window(trange: float, collar_fraction: float, placement: str) -> flo def _next_smooth_even(n: int) -> int: """Return the smallest even 2-3-5-smooth integer that is at least ``n``. - Parameters - ---------- - n : int - Lower bound on the result. - - Returns - ------- - int - The smallest even integer of the form ``2^a 3^b 5^c`` with ``a >= 1`` that is at - least ``n``. + - Returns: the smallest even integer of the form ``2^a 3^b 5^c`` with ``a >= 1`` + that is at least ``n``. """ # scipy.fft.next_fast_len also admits factors 7 and 11 (next_fast_len(4100) is # 4116 = 2^2 * 3 * 7^3), so it cannot be used where 2-3-5-smooth is required. @@ -1268,24 +1114,7 @@ def _next_smooth_even(n: int) -> int: def _fft_size(needed: float, fft_size: str) -> int: """Round a required sample count up to a transform size. - Parameters - ---------- - needed : float - Number of samples the request implies. - fft_size : {'auto', 'pow2', 'smooth'} - Round up to the next power of two, or to the next even 2-3-5-smooth size - (``'auto'`` rounds as ``'smooth'`` here; its choice is made in - :func:`_choose_sizes`). - - Returns - ------- - int - The rounded transform size, even and at least 4. - - Raises - ------ - GridError - If ``fft_size`` is not one of :data:`FFT_SIZES`. + - Returns: the rounded transform size, even and at least 4. """ return max(fourier.MIN_N_T, _next_size(needed, fft_size)) @@ -1293,25 +1122,8 @@ def _fft_size(needed: float, fft_size: str) -> int: def _next_size(needed: float, fft_size: str) -> int: """Round a count up to the next size of the chosen family, with no lower bound. - Parameters - ---------- - needed : float - Lower bound on the result. - fft_size : {'auto', 'pow2', 'smooth'} - Round up to the next power of two, or to the next even 2-3-5-smooth size - (``'auto'`` rounds as ``'smooth'`` here; its choice is made in - :func:`_choose_sizes`). - - Returns - ------- - int - The rounded size. Used for the oversampling multiplier, where a floor of four - would inflate the fine grid fourfold. - - Raises - ------ - GridError - If ``fft_size`` is not one of :data:`FFT_SIZES`. + - Returns: the rounded size. Used for the oversampling multiplier, where a floor of + four would inflate the fine grid fourfold. """ if fft_size not in FFT_SIZES: msg = f"fft_size must be one of {FFT_SIZES}, got {fft_size!r}" @@ -1331,18 +1143,8 @@ def _coarse_size(needed: int, n_to: int) -> int: exactly; when it is merely 2-3-5-smooth, that rounding could exceed ``n_to`` and duplicate spectral bins, which this cannot. - Parameters - ---------- - needed : int - Lower bound on the coarse size. - n_to : int - Fine transform size, even. - - Returns - ------- - int - The chosen coarse size, clamped to ``n_to`` when nothing smaller is big - enough. + - Returns: the chosen coarse size, clamped to ``n_to`` when nothing smaller is big + enough. """ if needed >= n_to: return n_to @@ -1370,23 +1172,11 @@ def _choose_sizes( divisors for that to leave any oversampling at all, and only then is the fine grid widened to a multiple of the coarse size the request implies. - Parameters - ---------- - half_bins : callable - Function of the bin spacing in rad/s and the fine size, returning the number of - positive-frequency bins the coarse grid must hold. - trange : float - Requested time window, s. - dt_fine : float - Fine sample spacing, s. - fft_size : {'auto', 'pow2', 'smooth'} - Rounding rule for both sizes; ``'auto'`` takes the cheapest pair by - :func:`fft_cost`. - - Returns - ------- - tuple of int - The coarse and fine numbers of time samples, ``(n_t, n_to)``. + - ``half_bins``: function of the bin spacing in rad/s and the fine size, returning + the number of positive-frequency bins the coarse grid must hold. + - ``trange``: requested time window, s. + - ``dt_fine``: fine sample spacing, s. + - Returns: the coarse and fine numbers of time samples, ``(n_t, n_to)``. """ if fft_size == "auto": # The cheapest pair by the transform-cost model, among the smooth fine sizes up @@ -1491,70 +1281,40 @@ def __post_init__(self) -> None: # --- sizes --------------------------------------------------------------------- @property - def n_t(self) -> int: - """Return the number of coarse time samples. - - Returns - ------- - int - The length of ``t``. - """ + def n_t(self) -> int: # numpydoc ignore=RT01 + """Number of coarse time samples.""" return self.t.size @property - def n_to(self) -> int: - """Return the number of fine time samples. - - Returns - ------- - int - The length of ``t_fine``. - """ + def n_to(self) -> int: # numpydoc ignore=RT01 + """Number of fine time samples.""" return self.t_fine.size @property - def n_omega(self) -> int: - """Return the number of coarse spectral bins. - - Returns - ------- - int - The length of ``omega``. - """ + def n_omega(self) -> int: # numpydoc ignore=RT01 + """Number of coarse spectral bins.""" return self.omega.size @property - def n_omega_fine(self) -> int: - """Return the number of fine spectral bins. - - Returns - ------- - int - The length of ``omega_fine``. - """ + def n_omega_fine(self) -> int: # numpydoc ignore=RT01 + """Number of fine spectral bins.""" return self.omega_fine.size # --- frequencies --------------------------------------------------------------- @property - def omega0(self) -> float: - """Return the reference angular frequency, rad/s. + def omega0(self) -> float: # numpydoc ignore=RT01 + """Reference angular frequency, rad/s. - Returns - ------- - float - ``2 pi c / reference_wavelength``. + ``2 pi c / reference_wavelength``. """ return 2.0 * np.pi * C / self.reference_wavelength @property - def d_omega(self) -> float: - """Return the spectral bin spacing, rad/s. + def d_omega(self) -> float: # numpydoc ignore=RT01 + """Spectral bin spacing, rad/s. - Returns - ------- - float - ``2 pi / (n_to dt_fine)``, the same on the coarse and the fine grid. + ``2 pi / (n_to dt_fine)``, the same on the coarse and the fine grid. """ # The axes are built from the fine window, and dt = dt_fine (n_to // n_t) is # rounded, so 2 pi / (n_t dt) can differ from this in the last place on a grid @@ -1562,137 +1322,104 @@ def d_omega(self) -> float: return 2.0 * np.pi / (self.n_to * self.dt_fine) @property - def omega_min(self) -> float: - """Return the lowest in-band angular frequency, rad/s. + def omega_min(self) -> float: # numpydoc ignore=RT01 + """Lowest in-band angular frequency, rad/s. - Returns - ------- - float - The frequency of the longest requested wavelength. + The frequency of the longest requested wavelength. """ return self.omega_collar.one_low @property - def omega_max(self) -> float: - """Return the highest in-band angular frequency, rad/s. + def omega_max(self) -> float: # numpydoc ignore=RT01 + """Highest in-band angular frequency, rad/s. - Returns - ------- - float - The frequency of the shortest requested wavelength. + The frequency of the shortest requested wavelength. """ return self.omega_collar.one_high @property - def omega_max_window(self) -> float: - """Return the upper edge of the apodised frequency window, rad/s. + def omega_max_window(self) -> float: # numpydoc ignore=RT01 + """Upper edge of the apodised frequency window, rad/s. - Returns - ------- - float - ``1.1 omega_max``, where the spectral collar reaches zero. + ``1.1 omega_max``, where the spectral collar reaches zero. """ return self.omega_collar.zero_high # --- masks and windows ----------------------------------------------------------- @property - def active_mask(self) -> Bool[np.ndarray, " n_omega"]: - """Return the bins where the physics lives, inside the apodised window. + def active_mask(self) -> Bool[np.ndarray, " n_omega"]: # numpydoc ignore=RT01 + """The bins where the physics lives, inside the apodised window. The dispersion relation, the noise and the error norm are evaluated here and nowhere else. - Returns - ------- - numpy.ndarray - Boolean mask over the coarse frequency axis, shape ``(n_omega,)``. + Boolean mask over the coarse frequency axis, shape ``(n_omega,)``. """ return (self.omega > 0.5 * self.omega_min) & ( self.omega < self.omega_max_window ) @property - def inband_mask(self) -> Bool[np.ndarray, " n_omega"]: - """Return the bins inside the requested wavelength limits. + def inband_mask(self) -> Bool[np.ndarray, " n_omega"]: # numpydoc ignore=RT01 + """The bins inside the requested wavelength limits. - Returns - ------- - numpy.ndarray - Boolean mask over the coarse frequency axis, shape ``(n_omega,)``, true - where ``omega_min <= omega <= omega_max``. + Boolean mask over the coarse frequency axis, shape ``(n_omega,)``, true + where ``omega_min <= omega <= omega_max``. """ return (self.omega >= self.omega_min) & (self.omega <= self.omega_max) @property - def omega_window(self) -> Float[np.ndarray, " n_omega"]: - """Return the spectral collar profile on the coarse frequency axis. + def omega_window(self) -> Float[np.ndarray, " n_omega"]: # numpydoc ignore=RT01 + """Spectral collar profile on the coarse frequency axis. - Returns - ------- - numpy.ndarray - Window values in ``[0, 1]``, shape ``(n_omega,)``, equal to 1 in band. + Window values in ``[0, 1]``, shape ``(n_omega,)``, equal to 1 in band. """ return self.omega_collar.window(self.omega) @property - def t_window(self) -> Float[np.ndarray, " n_t"]: - """Return the temporal collar profile on the coarse time axis. + def t_window(self) -> Float[np.ndarray, " n_t"]: # numpydoc ignore=RT01 + """Temporal collar profile on the coarse time axis. - Returns - ------- - numpy.ndarray - Window values in ``[0, 1]``, shape ``(n_t,)``, equal to 1 inside the window. + Window values in ``[0, 1]``, shape ``(n_t,)``, equal to 1 inside the window. """ return self.t_collar.window(self.t) @property - def t_window_fine(self) -> Float[np.ndarray, " n_to"]: - """Return the temporal collar profile on the fine time axis. + def t_window_fine(self) -> Float[np.ndarray, " n_to"]: # numpydoc ignore=RT01 + """Temporal collar profile on the fine time axis. Both temporal windows come from the one collar geometry, so the coarse and fine apodisations agree exactly wherever their samples coincide, rather than each window being built from its own axis's extremes, which differ at the upper edge. - Returns - ------- - numpy.ndarray - Window values in ``[0, 1]``, shape ``(n_to,)``, 1 inside the window. + Window values in ``[0, 1]``, shape ``(n_to,)``, 1 inside the window. """ return self.t_collar.window(self.t_fine) # --- oversampling ----------------------------------------------------------------- @property - def crop_index(self) -> np.ndarray: - """Return the positions of the coarse spectral bins inside the fine spectrum. + def crop_index(self) -> np.ndarray: # numpydoc ignore=RT01 + """The positions of the coarse spectral bins inside the fine spectrum. - Returns - ------- - numpy.ndarray - Index array of ``numpy.intp``, shape ``(n_omega,)``. + Index array of ``numpy.intp``, shape ``(n_omega,)``. """ return fourier.crop_index(self.n_t, self.n_to, kind=self.kind) @property - def pad_scale(self) -> float: - """Return the factor applied when padding the coarse spectrum out. + def pad_scale(self) -> float: # numpydoc ignore=RT01 + """Factor applied when padding the coarse spectrum out. - Returns - ------- - float - ``n_to / n_t``. + ``n_to / n_t``. """ return fourier.pad_scale(self.n_t, self.n_to) @property - def crop_scale(self) -> float: - """Return the factor applied when cropping the fine spectrum back. + def crop_scale(self) -> float: # numpydoc ignore=RT01 + """Factor applied when cropping the fine spectrum back. - Returns - ------- - float - ``n_t / n_to``. + ``n_t / n_to``. """ return fourier.crop_scale(self.n_t, self.n_to) @@ -1759,22 +1486,14 @@ def validate(self) -> None: self._validate_spectrum() def _validate_spectrum(self) -> None: - """Check the frequency-axis invariants that depend on the grid's layout. - - Raises - ------ - NotImplementedError - Always; each subclass supplies the invariants for its own layout. - """ + """Check the frequency-axis invariants that depend on the grid's layout.""" raise NotImplementedError def _band(self) -> str: """Return the spectral band as wavelengths, for printing. - Returns - ------- - str - ``"150-4000 nm"``, with ``inf`` where the band reaches zero frequency. + - Returns: ``"150-4000 nm"``, with ``inf`` where the band reaches zero + frequency. """ shortest = 2.0e9 * np.pi * C / self.omega_max longest = 2.0e9 * np.pi * C / self.omega_min if self.omega_min > 0.0 else np.inf @@ -1802,10 +1521,7 @@ def __repr__(self) -> str: def _describe(self) -> tuple[str, list[tuple[str, object]]]: """Return the title and rows of the printed block. - Returns - ------- - tuple - The title and ``(key, value)`` rows. + - Returns: the title and ``(key, value)`` rows. """ kind = "a real field" if self.kind == "field" else "an envelope" collar = self.t_collar @@ -1838,20 +1554,8 @@ def _frequency_limits(wavelength_limits: Sequence[float]) -> tuple[float, float] run-time guard on the pair's length, and the test session's ``beartype`` hook would otherwise reject a wrong-length tuple before this check is reached. - Parameters - ---------- - wavelength_limits : sequence of float - Shortest and longest wavelength of the frequency window, m. - - Returns - ------- - tuple of float - The lowest and highest angular frequency, rad/s. - - Raises - ------ - GridError - If the pair is not two strictly increasing positive wavelengths. + - ``wavelength_limits``: shortest and longest wavelength of the frequency window, m. + - Returns: the lowest and highest angular frequency, rad/s. """ if len(wavelength_limits) != 2: msg = ( @@ -1878,25 +1582,10 @@ def _check_common( # noqa: PLR0917 - called positionally ) -> None: """Validate the constructor arguments both grids share, naming each failure. - Parameters - ---------- - length : float - Propagation distance, m. - reference_wavelength : float - Reference wavelength, m. - trange : float - Requested time window, s. - dt : float or None - Requested sample spacing, s. - ffac : float - Fine-grid oversampling factor. - collar_fraction : float - Minimum temporal collar width as a fraction of the window. - - Raises - ------ - GridError - If any argument is outside its valid range. + - ``length``: propagation distance, m. + - ``reference_wavelength``: reference wavelength, m. + - ``trange``: requested time window, s. + - ``dt``: requested sample spacing, s. """ if length < 0.0: msg = f"length must be a non-negative distance in metres, got {length!r}" @@ -1938,19 +1627,8 @@ def _temporal_collar( Requiring at least ``collar_fraction`` of the full window at each end removes that case (conventions.md §3). - Parameters - ---------- - trange : float - Requested time window, s. - trange_even : float - Realised time window, s, at least ``trange``. - collar_fraction : float - Minimum width of each collar as a fraction of the full window. - - Returns - ------- - Collar - The temporal collar geometry, symmetric about zero. + - ``trange``: requested time window, s. + - ``trange_even``: realised time window, s, at least ``trange``. """ half = 0.5 * trange_even inner = min(0.5 * trange, half * (1.0 - 2.0 * collar_fraction)) @@ -1960,18 +1638,8 @@ def _temporal_collar( def _log_grid(kind: str, n_t: int, n_to: int, dt: float, trange_even: float) -> None: """Record the realised grid at debug level. - Parameters - ---------- - kind : str - Which grid was built. - n_t : int - Coarse number of time samples. - n_to : int - Fine number of time samples. - dt : float - Coarse sample spacing, s. - trange_even : float - Realised time window, s. + - ``dt``: coarse sample spacing, s. + - ``trange_even``: realised time window, s. """ if logger.isEnabledFor(logging.DEBUG): logger.debug( @@ -2093,27 +1761,18 @@ def from_limits( required = 2.0 * np.pi / (ffac * omega_max) dt_fine = required if dt is None else min(required, dt) - def half_bins(d_omega: float, n_to: int) -> int: + def _half_bins(d_omega: float, n_to: int) -> int: """Return the coarse half-size needed to hold the apodised window. - Parameters - ---------- - d_omega : float - Bin spacing, rad/s. - n_to : int - Fine transform size. - - Returns - ------- - int - Number of positive-frequency bins the coarse grid must hold. + - ``d_omega``: bin spacing, rad/s. + - Returns: number of positive-frequency bins the coarse grid must hold. """ # Keep every fine bin out to the first one past the apodised window edge, # so the collar is fully represented on the coarse grid. return min(math.floor(omega_max_window / d_omega) + 2, n_to // 2 + 1) needed = _needed_window(trange, collar_fraction, collar_placement) - n_t, n_to = _choose_sizes(half_bins, needed, dt_fine, fft_size) + n_t, n_to = _choose_sizes(_half_bins, needed, dt_fine, fft_size) trange_even = n_to * dt_fine d_omega = 2.0 * np.pi / trange_even # dt comes from the integer ratio rather than from pi/omega[-1], so that the @@ -2140,13 +1799,7 @@ def half_bins(d_omega: float, n_to: int) -> int: return grid def _validate_spectrum(self) -> None: - """Check that the frequency axis is the one-sided rfft axis of the time axis. - - Raises - ------ - GridError - If ``dt`` is not ``pi/omega[-1]``, or ``omega`` is not ``k d_omega``. - """ + """Check that the frequency axis is the one-sided rfft axis of the time axis.""" expected_dt = np.pi / self.omega[-1] if not math.isclose(self.dt, expected_dt, rel_tol=INVARIANT_RTOL): msg = f"dt is {self.dt!r}, expected pi/omega[-1] = {expected_dt!r}" @@ -2277,20 +1930,12 @@ def from_limits( dt_fine = required if dt is None else min(required, dt) oversampling = thg or dt_fine < required - def half_bins(d_omega: float, n_to: int) -> int: + def _half_bins(d_omega: float, n_to: int) -> int: """Return the coarse half-size needed to hold the apodised window. - Parameters - ---------- - d_omega : float - Bin spacing, rad/s. - n_to : int - Fine transform size. - - Returns - ------- - int - Number of relative-frequency bins the coarse grid must hold each side. + - ``d_omega``: bin spacing, rad/s. + - Returns: number of relative-frequency bins the coarse grid must hold each + side. """ if not oversampling: return n_to // 2 @@ -2298,7 +1943,7 @@ def half_bins(d_omega: float, n_to: int) -> int: return min(max(math.ceil(reach / d_omega), 1), n_to // 2) needed = _needed_window(trange, collar_fraction, collar_placement) - n_t, n_to = _choose_sizes(half_bins, needed, dt_fine, fft_size) + n_t, n_to = _choose_sizes(_half_bins, needed, dt_fine, fft_size) trange_even = n_to * dt_fine d_omega = 2.0 * np.pi / trange_even dt_coarse = dt_fine * (n_to // n_t) @@ -2331,12 +1976,6 @@ def _validate_spectrum(self) -> None: The field grid's ``omega_fine[-1]/omega[-1]`` has no meaning in FFT order, where the last bin is the one just below the carrier on both grids; the ratio that carries the information is the one of the minima. - - Raises - ------ - GridError - If ``dt`` is not ``2 pi / (n_omega d_omega)``, or ``omega`` is not the - FFT-order axis about ``omega0``. """ relative = self.omega - self.omega0 expected_dt = -np.pi / relative.min() @@ -2359,18 +1998,7 @@ def _validate_spectrum(self) -> None: def _fft_order(n: int) -> Float[np.ndarray, " n"]: - """Return the integer bin indices of an ``n``-point DFT, in FFT order. - - Parameters - ---------- - n : int - Transform length, even. - - Returns - ------- - numpy.ndarray - The values ``0, 1, ..., n/2 - 1, -n/2, ..., -1`` as floats, shape ``(n,)``. - """ + """Return the integer bin indices of an ``n``-point DFT, in FFT order.""" index = np.arange(n, dtype=float) index[n // 2 :] -= n return index @@ -2424,20 +2052,7 @@ class GridSuggestion: def _rounded(value: float, *, up: bool) -> float: - """Round a positive value to two significant figures, outwards. - - Parameters - ---------- - value : float - The value. - up : bool - Round up rather than down. - - Returns - ------- - float - The value with two significant figures. - """ + """Round a positive value to two significant figures, outwards.""" scale = 10.0 ** (math.floor(math.log10(value)) - 1) steps = value / scale steps = math.ceil(steps - 1e-9) if up else math.floor(steps + 1e-9) @@ -2447,23 +2062,10 @@ def _rounded(value: float, *, up: bool) -> float: def _extent(pulse: object) -> tuple[float, float, float, float]: """Return a pulse's carrier and bandwidth, its duration and its delay. - Parameters - ---------- - pulse : object - A shaped pulse, a sum of pulses or a measured spectrum. - - Returns - ------- - tuple of float - The centre frequency and the intensity full width at half maximum in - frequency, Hz; the intensity full width at half maximum in time and the - largest delay, s. - - Raises - ------ - GridError - For a pulse whose extent cannot be read, such as a continuous wave or a - custom shape. + - ``pulse``: a shaped pulse, a sum of pulses or a measured spectrum. + - Returns: the centre frequency and the intensity full width at half maximum in + frequency, Hz; the intensity full width at half maximum in time and the largest + delay, s. """ from solprop.pulses import ( # noqa: PLC0415 - pulses import this module SECH_FWHM_TO_WIDTH, diff --git a/src/solprop/hankel.py b/src/solprop/hankel.py index dad647eec..911960740 100644 --- a/src/solprop/hankel.py +++ b/src/solprop/hankel.py @@ -171,17 +171,8 @@ def inverse(self, values: ArrayLike) -> Inexact[np.ndarray, "... n_r"]: def _apply(self, values: ArrayLike, scale: float) -> Inexact[np.ndarray, "... n_r"]: """Apply the dimensionless transform between its two diagonal scalings. - Parameters - ---------- - values : array_like - Finite real or complex samples with n_r on the final axis. - scale : float - Forward or inverse common scaling, in m^2 or m^-2. - - Returns - ------- - numpy.ndarray - Transformed samples, retaining the leading dimensions. + - ``values``: finite real or complex samples with n_r on the final axis. + - ``scale``: forward or inverse common scaling, in m^2 or m^-2. """ array = np.asarray(values) if array.ndim == 0 or array.shape[-1] != self.n_r: diff --git a/src/solprop/linear.py b/src/solprop/linear.py index c1ec5dc1e..47f3cf3d8 100644 --- a/src/solprop/linear.py +++ b/src/solprop/linear.py @@ -464,23 +464,10 @@ def _check_absorber( ) -> Float[np.ndarray, " n_omega"]: """Validate a spectral absorber rate against the grid it belongs to. - Parameters - ---------- - absorber_rate : numpy.ndarray - The rate the caller supplied. - grid : solprop.grid.TimeGrid - The grid it must match. - - Returns - ------- - numpy.ndarray - The rate as float64, shape ``(n_omega,)``. + A negative rate anywhere is refused: an absorber removes energy, and a negative + rate is gain wearing a boundary's clothes. - Raises - ------ - GridError - If it is the wrong length, not finite, or negative anywhere. An absorber - removes energy; a negative rate is gain wearing a boundary's clothes. + - ``grid``: the grid it must match. """ values = np.asarray(absorber_rate, dtype=float) if values.shape != (grid.n_omega,): diff --git a/src/solprop/log.py b/src/solprop/log.py index ec5f5ef57..5da292fa3 100644 --- a/src/solprop/log.py +++ b/src/solprop/log.py @@ -298,47 +298,26 @@ class PlaneSummary(Protocol): """ @property - def n_steps(self) -> int: - """Return the steps taken to reach the plane, accepted and rejected together. - - Returns - ------- - int - The attempted steps. - """ + def n_steps(self) -> int: # numpydoc ignore=RT01 + """The steps taken to reach the plane, accepted and rejected together.""" ... @property - def n_rejected(self) -> int: - """Return the rejected steps among them. - - Returns - ------- - int - The rejected steps. - """ + def n_rejected(self) -> int: # numpydoc ignore=RT01 + """Rejected steps among them.""" ... @property - def max_norm(self) -> float: - """Return the largest scaled error norm over the accepted steps. + def max_norm(self) -> float: # numpydoc ignore=RT01 + """Largest scaled error norm over the accepted steps. - Returns - ------- - float - The norm; 1 is the tolerance the run asked for. + The norm; 1 is the tolerance the run asked for. """ ... @property - def wall_time(self) -> float: - """Return the seconds the plane took. - - Returns - ------- - float - Wall time, s. - """ + def wall_time(self) -> float: # numpydoc ignore=RT01 + """Wall time the plane took, s.""" ... @@ -868,66 +847,51 @@ def __init__( self._elapsed_final: float | None = None @property - def n_steps(self) -> int: - """Return the attempted steps of the run so far. + def n_steps(self) -> int: # numpydoc ignore=RT01 + """Attempted steps of the run so far. - Returns - ------- - int - The larger of the two channels that count: the per-step calls when a driver - is attached, and the plane summaries otherwise. Both count every attempted - step, so when both are live they agree. + The larger of the two channels that count: the per-step calls when a driver + is attached, and the plane summaries otherwise. Both count every attempted + step, so when both are live they agree. """ return max(self._n_attempts, self._plane_steps) @property - def n_rejected(self) -> int: - """Return the rejected steps of the run so far. + def n_rejected(self) -> int: # numpydoc ignore=RT01 + """Rejected steps of the run so far. - Returns - ------- - int - Counted from whichever channel is live; see :attr:`n_steps`. + Counted from whichever channel is live; see :attr:`n_steps`. """ return max(self._n_rejected, self._plane_rejected) @property - def tolerance_ratio(self) -> float: - """Return what the run achieved against what it asked for. + def tolerance_ratio(self) -> float: # numpydoc ignore=RT01 + """What the run achieved against what it asked for. - Returns - ------- - float - The largest scaled error norm any accepted step scored - (:attr:`solprop.stepper.controller.ControlRecord.tolerance_ratio`); 0 before - the first plane. + The largest scaled error norm any accepted step scored + (:attr:`solprop.stepper.controller.ControlRecord.tolerance_ratio`); 0 before + the first plane. """ return self._max_norm @property - def eta_error(self) -> float: - """Return how far off the reported ETAs were. + def eta_error(self) -> float: # numpydoc ignore=RT01 + """How far off the reported ETAs were. - Returns - ------- - float - Median absolute relative error of the windowed estimate over every periodic - line that published one; NaN before ``finished()``, or when no line did. + Median absolute relative error of the windowed estimate over every periodic + line that published one; NaN before ``finished()``, or when no line did. """ return self._median_error(0) @property - def eta_error_whole_run(self) -> float: - """Return how far off the whole-run estimator would have been. + def eta_error_whole_run(self) -> float: # numpydoc ignore=RT01 + """How far off the whole-run estimator would have been. Formed on the same lines as :attr:`eta_error` but never reported, so that the two can be compared on every run. - Returns - ------- - float - Median absolute relative error of the whole-run estimate; NaN as for - :attr:`eta_error`. + Median absolute relative error of the whole-run estimate; NaN as for + :attr:`eta_error`. """ return self._median_error(1) @@ -1081,32 +1045,11 @@ def interrupted(self, z: float, error: BaseException) -> None: ) def _say(self, level: int, message: str, *args: object) -> None: - """Log one of the lines a person reads whole: the summary and the closing one. - - Parameters - ---------- - level : int - The record's level. - message : str - The ``%``-format of the record. - *args : object - Its arguments. - """ + """Log a line a person reads whole: the summary or the closing one.""" self._logger.log(level, message, *args) def _median_error(self, index: int) -> float: - """Return the median absolute relative error of one of the two estimators. - - Parameters - ---------- - index : int - ``0`` for the windowed estimate, ``1`` for the whole-run one. - - Returns - ------- - float - The median over every line that published an estimate, or NaN. - """ + """Return the median absolute relative error of one of the two estimators.""" if self._elapsed_final is None: return math.nan errors = [ @@ -1123,15 +1066,7 @@ def _status(self, now: float) -> "ProgressStatus": The one place the estimator's bookkeeping happens, so every reporter built on this one -- the line and the bar -- is measured the same way. - Parameters - ---------- - now : float - The clock reading the status is formed at, s. - - Returns - ------- - ProgressStatus - Where the run is and what it expects. + - ``now``: the clock reading the status is formed at, s. """ elapsed = now - self._t0 if self._t0 is not None else 0.0 self._window.append((now, self._z, self._n_accepted)) @@ -1153,18 +1088,7 @@ def _status(self, now: float) -> "ProgressStatus": @staticmethod def _tail(status: "ProgressStatus") -> str: - """Return the ETA clause of a line or a bar. - - Parameters - ---------- - status : ProgressStatus - What :meth:`_status` returned. - - Returns - ------- - str - ``ETA H:MM:SS (from the last H:MM:SS)`` or ``ETA unknown``. - """ + """Return the ETA clause of a line or a bar.""" if not status.known: return "ETA unknown" return ( @@ -1175,10 +1099,7 @@ def _tail(status: "ProgressStatus") -> str: def _emit(self, now: float) -> None: """Form both ETAs, record them, and log one periodic line. - Parameters - ---------- - now : float - The clock reading this line is being emitted at, s. + - ``now``: the clock reading this line is being emitted at, s. """ status = self._status(now) if not self._logger.isEnabledFor(self._level): @@ -1200,19 +1121,11 @@ def _emit(self, now: float) -> None: def _estimates(self, now: float, elapsed: float) -> tuple[float, float, float]: """Return the windowed ETA, the whole-run ETA and the window's own length. - Parameters - ---------- - now : float - The clock reading the estimates are formed at, s. - elapsed : float - Time since :meth:`started`, s. - - Returns - ------- - tuple of float - The windowed estimate, the whole-run estimate and the length of the window - the first was taken over, all in seconds. An estimate that cannot be formed - from a positive rate is infinite, which formats as ``unknown``. + - ``now``: the clock reading the estimates are formed at, s. + - ``elapsed``: time since :meth:`started`, s. + - Returns: the windowed estimate, the whole-run estimate and the length of the + window the first was taken over, all in seconds. An estimate that cannot be + formed from a positive rate is infinite, which formats as ``unknown``. """ remaining = max(self._z_end - self._z, 0.0) done = self._z - self._z_start @@ -1273,10 +1186,7 @@ class ProgressStatus: def _tqdm_available() -> bool: """Return whether tqdm can be imported, without importing it. - Returns - ------- - bool - Whether a bar can be drawn in this environment. + - Returns: whether a bar can be drawn in this environment. """ return importlib.util.find_spec("tqdm") is not None @@ -1288,16 +1198,6 @@ def _tqdm_class() -> Any: import happens when a bar is first drawn. ``tqdm.auto`` warns when a notebook lacks ``ipywidgets`` and falls back to the text bar, which is the documented fallback, so the warning is not one the suite should turn into an error. - - Returns - ------- - type - The ``tqdm`` class to draw with. - - Raises - ------ - ImportError - If tqdm is not installed in this environment, saying what to do. """ with warnings.catch_warnings(): warnings.simplefilter("ignore") @@ -1314,14 +1214,6 @@ class _BarHandler(logging.Handler): Takes the place of a :class:`logging.StreamHandler` for a bar's lifetime and writes each record with ``tqdm.write``, which clears the bars drawn on that stream, writes the line and redraws them. - - Parameters - ---------- - tqdm : type - The class whose ``write`` to use. - original : logging.StreamHandler - The handler being stood in for, whose stream, level, formatter and filters - this one takes. """ def __init__(self, tqdm: Any, original: logging.StreamHandler) -> None: # type: ignore[type-arg] - a text stream @@ -1333,13 +1225,7 @@ def __init__(self, tqdm: Any, original: logging.StreamHandler) -> None: # type: self._stream = original.stream def emit(self, record: logging.LogRecord) -> None: - """Write one record above the bars on this handler's stream. - - Parameters - ---------- - record : logging.LogRecord - The record to write. - """ + """Write one record above the bars on this handler's stream.""" try: self._tqdm.write(self.format(record), file=self._stream) except Exception: # logging must not end a run: logging.Handler's contract @@ -1463,13 +1349,10 @@ def __init__( self._swapped: list[tuple[logging.Logger, list[logging.Handler]]] = [] @property - def bar(self) -> Any: - """Return the tqdm instance while the bar is up, else ``None``. + def bar(self) -> Any: # numpydoc ignore=RT01 + """The ``tqdm`` bar while it is up, else ``None``. - Returns - ------- - tqdm.tqdm or None - The bar, for a parent placing its own beside it. + The bar, for a parent placing its own beside it. """ return self._bar @@ -1561,10 +1444,7 @@ def interrupted(self, z: float, error: BaseException) -> None: def _emit(self, now: float) -> None: """Advance the estimator and redraw the bar. - Parameters - ---------- - now : float - The clock reading this redraw happens at, s. + - ``now``: the clock reading this redraw happens at, s. """ status = self._status(now) if self._bar is None: @@ -1582,17 +1462,7 @@ def _emit(self, now: float) -> None: self._bar.refresh() def _say(self, level: int, message: str, *args: object) -> None: - """Log a line a person reads whole, and write it out when nothing else would. - - Parameters - ---------- - level : int - The record's level. - message : str - The ``%``-format of the record. - *args : object - Its arguments. - """ + """Log a line a person reads whole, and write it out when nothing else would.""" super()._say(level, message, *args) if self._echo and not self._console_shows(level): stream: Any = sys.stderr if self._file is None else self._file @@ -1600,20 +1470,7 @@ def _say(self, level: int, message: str, *args: object) -> None: stream.flush() def _console_shows(self, level: int) -> bool: - """Return whether a console handler already prints a record of this level. - - Parameters - ---------- - level : int - The record's level. - - Returns - ------- - bool - Whether the logger is enabled for it and a handler on its path, at or - below that level, writes to standard output or error or to the bar's own - stream. - """ + """Return whether a console handler already prints a record of this level.""" if not self._logger.isEnabledFor(level): return False consoles = [sys.stdout, sys.stderr, self._file] @@ -1632,13 +1489,7 @@ def _console_shows(self, level: int) -> bool: return False def _redirect_logging(self, tqdm: Any) -> None: - """Stand a bar-aware handler in for each console handler, for the bar's life. - - Parameters - ---------- - tqdm : type - The class whose ``write`` the stand-ins use. - """ + """Stand a bar-aware handler in for each console handler, for the bar's life.""" consoles = [sys.stdout, sys.stderr] if self._file is not None: consoles.append(self._file) diff --git a/src/solprop/modes/_stepindex_vector.py b/src/solprop/modes/_stepindex_vector.py index 89ab586b0..9c55ab3a1 100644 --- a/src/solprop/modes/_stepindex_vector.py +++ b/src/solprop/modes/_stepindex_vector.py @@ -36,21 +36,9 @@ def _radial( ]: """Evaluate the scaled radial function, slope and axis limit. - Parameters - ---------- - order : int - Non-negative angular Bessel order. - argument : numpy.ndarray - Dimensionless Bessel arguments, shape (n_points,). - edge : float - Dimensionless Bessel argument at the interface. - inside : bool - Whether to use the regular core solution. - - Returns - ------- - tuple of numpy.ndarray - Dimensionless radial function, argument derivative and divided value. + - ``edge``: dimensionless Bessel argument at the interface. + - ``inside``: the regular core solution (``J``) rather than the cladding one. + - Returns: dimensionless radial function, argument derivative and divided value. """ if inside: value = jv(order, argument) / jv(order, edge) @@ -115,19 +103,8 @@ class _FieldParameters: def _field_parameters(mode: StepIndexMode, omega: float, z: float) -> _FieldParameters: """Solve the position-independent parameters of one transverse field. - Parameters - ---------- - mode : StepIndexMode - Existing bound lossless mode. - omega : float - Positive angular frequency in rad/s. - z : float - Axial position in m within the declared path. - - Returns - ------- - _FieldParameters - Immutable scalar parameters for this frequency and axial position. + - ``omega``: positive angular frequency in rad/s. + - ``z``: axial position in m within the declared path. """ if not np.isfinite(omega) or omega <= 0 or not np.isfinite(z): raise ModeError("omega must be positive and z finite") @@ -186,23 +163,11 @@ def _fields( ) -> tuple[Complex[np.ndarray, "n_points 3"], Complex[np.ndarray, "n_points 3"]]: """Recover full Maxwell fields from analytic longitudinal amplitudes. - Parameters - ---------- - mode : StepIndexMode - Existing bound lossless mode. - points : numpy.ndarray - Explicit radial fractions or Cartesian points as required by this helper. - omega : float - Positive angular frequency in rad/s. - z : float - Position in m within the declared path. - exterior : bool or None, optional - Force one side for an exact interface trace; None follows each point. - - Returns - ------- - tuple of numpy.ndarray - Unnormalised E and H, each shape (n_points, 3). + - ``omega``: positive angular frequency in rad/s. + - ``z``: position in m within the declared path. + - ``exterior``: force one side for an exact interface trace; None follows each + point. + - Returns: unnormalised E and H, each shape (n_points, 3). """ if points.ndim != 2 or points.shape[1] != 2 or not np.all(np.isfinite(points)): raise ModeError("points must be finite Cartesian (x, y) pairs") @@ -219,19 +184,10 @@ def _prepared_fields( ) -> tuple[Complex[np.ndarray, "n_points 3"], Complex[np.ndarray, "n_points 3"]]: """Evaluate one solved mode at Cartesian positions without solving it again. - Parameters - ---------- - parameters : _FieldParameters - Immutable solution at the requested frequency and axial position. - points : numpy.ndarray - Cartesian positions in m, shape (n_points, 2). - exterior : bool or None, optional - Force one side for an exact interface trace; None follows each point. - - Returns - ------- - tuple of numpy.ndarray - Unnormalised E and H, each shape (n_points, 3). + - ``points``: Cartesian positions in m, shape (n_points, 2). + - ``exterior``: force one side for an exact interface trace; None follows each + point. + - Returns: unnormalised E and H, each shape (n_points, 3). """ radius, k, beta = parameters.radius, parameters.k, parameters.beta u, w, core, clad = parameters.u, parameters.w, parameters.core, parameters.clad @@ -336,21 +292,11 @@ def _power( ) -> tuple[float, float]: """Integrate signed full-domain or exterior-tail Poynting power. - Parameters - ---------- - mode : StepIndexMode - Existing bound lossless mode. - omega : float - Positive angular frequency in rad/s. - z : float - Position in m within the declared path. - start : float, optional - Lower integration radius divided by the core radius. - - Returns - ------- - tuple of float - Power in W and the adaptive absolute integration error in W. + - ``omega``: positive angular frequency in rad/s. + - ``z``: position in m within the declared path. + - ``start``: lower integration radius divided by the core radius; ``start >= 1`` + integrates an exterior tail alone. + - Returns: power in W and the adaptive absolute integration error in W. """ # The quadrature varies radius alone. Resolve the eigenpair and material # values once for this integral, without caching a mutable profile's inputs. @@ -359,17 +305,9 @@ def _power( angles = np.arange(2 * mode.n + 3) * 2 * np.pi / (2 * mode.n + 3) def integrand(x: float) -> float: - """Evaluate the angularly integrated radial flux. - - Parameters - ---------- - x : float - Radius divided by the core radius. + """Evaluate the angularly integrated radial flux at ``x = r / a``. - Returns - ------- - float - Dimensionless-radius integrand before the squared-radius factor. + The caller multiplies by the squared radius, the Jacobian of ``x``. """ points = radius * x * np.column_stack((np.cos(angles), np.sin(angles))) e, h = _prepared_fields(parameters, points) diff --git a/src/solprop/modes/antiresonant.py b/src/solprop/modes/antiresonant.py index 3184e86e6..a03764a29 100644 --- a/src/solprop/modes/antiresonant.py +++ b/src/solprop/modes/antiresonant.py @@ -147,17 +147,7 @@ class _Conventions: def _positive(value: float, name: str) -> None: """Check a positive finite dimension. - Parameters - ---------- - value : float - Dimension in m. - name : str - Name used in the diagnostic. - - Raises - ------ - ModeError - If the dimension is nonpositive or nonfinite. + - ``value``: dimension in m. """ if not math.isfinite(value) or value <= 0: raise ModeError(f"{name} must be positive and finite, got {value}") @@ -166,20 +156,7 @@ def _positive(value: float, name: str) -> None: def _tube_angle(n_tubes: int) -> float: """Return the sine of half the angular tube spacing. - Parameters - ---------- - n_tubes : int - Number of tubes, at least three. - - Returns - ------- - float - Sine of pi divided by the count. - - Raises - ------ - ModeError - If the count is invalid. + - ``n_tubes``: number of tubes, at least three. """ if isinstance(n_tubes, bool) or not isinstance(n_tubes, int) or n_tubes < 3: raise ModeError("n_tubes must be an integer >= 3") @@ -273,15 +250,7 @@ def tube_radius_from_gap(core_radius: float, n_tubes: int, gap: float) -> float: def _check_gap(gap: float) -> None: """Check that tubes do not overlap. - Parameters - ---------- - gap : float - Tube separation in m. - - Raises - ------ - ModeError - If the gap is negative or nonfinite. + - ``gap``: tube separation in m. """ if not math.isfinite(gap) or gap < 0: raise ModeError(f"tube gap must be nonnegative and finite, got {gap} m") @@ -328,10 +297,7 @@ def __repr__(self) -> str: def _describe(self) -> tuple[str, list[tuple[str, object]]]: """Return the title and rows of the printed block. - Returns - ------- - tuple - The title and ``(key, value)`` rows. + - Returns: the title and ``(key, value)`` rows. """ return "TubeLattice: a single ring of cladding tubes", [ ("wall thickness", engineering(self.wall_thickness, "m")), @@ -485,19 +451,7 @@ def __add__( self, other: "_Spectral | complex | Inexact[np.ndarray, '...']", ) -> "_Spectral": - """Apply the sum rule. - - Parameters - ---------- - other : _Spectral, complex or numpy.ndarray - Other operand with its slope, or a frequency-independent constant, - scalar or broadcastable. - - Returns - ------- - _Spectral - Resulting value and analytic frequency slope. - """ + """Apply the sum rule.""" if isinstance(other, _Spectral): return _Spectral(self.value + other.value, self.slope + other.slope) return _Spectral(self.value + other, self.slope) @@ -505,67 +459,25 @@ def __add__( __radd__ = __add__ def __neg__(self) -> "_Spectral": - """Negate value and slope. - - Returns - ------- - _Spectral - Resulting value and analytic frequency slope. - """ + """Negate value and slope.""" return _Spectral(-self.value, -self.slope) def __sub__( self, other: "_Spectral | complex | Inexact[np.ndarray, '...']", ) -> "_Spectral": - """Subtract a spectral quantity. - - Parameters - ---------- - other : _Spectral, complex or numpy.ndarray - Other operand with its slope, or a frequency-independent constant, - scalar or broadcastable. - - Returns - ------- - _Spectral - Resulting value and analytic frequency slope. - """ + """Subtract a spectral quantity.""" return self + (-other) def __rsub__(self, other: "complex | Inexact[np.ndarray, '...']") -> "_Spectral": - """Subtract from a constant. - - Parameters - ---------- - other : _Spectral, complex or numpy.ndarray - Other operand with its slope, or a frequency-independent constant, - scalar or broadcastable. - - Returns - ------- - _Spectral - Resulting value and analytic frequency slope. - """ + """Subtract from a constant.""" return -self + other def __mul__( self, other: "_Spectral | complex | Inexact[np.ndarray, '...']", ) -> "_Spectral": - """Apply the product rule. - - Parameters - ---------- - other : _Spectral, complex or numpy.ndarray - Other operand with its slope, or a frequency-independent constant, - scalar or broadcastable. - - Returns - ------- - _Spectral - Resulting value and analytic frequency slope. - """ + """Apply the product rule.""" if isinstance(other, _Spectral): return _Spectral( self.value * other.value, @@ -579,19 +491,7 @@ def __truediv__( self, other: "_Spectral | complex | Inexact[np.ndarray, '...']", ) -> "_Spectral": - """Apply the quotient rule. - - Parameters - ---------- - other : _Spectral, complex or numpy.ndarray - Other operand with its slope, or a frequency-independent constant, - scalar or broadcastable. - - Returns - ------- - _Spectral - Resulting value and analytic frequency slope. - """ + """Apply the quotient rule.""" if isinstance(other, _Spectral): value = self.value / other.value return _Spectral(value, (self.slope - value * other.slope) / other.value) @@ -601,62 +501,22 @@ def __rtruediv__( self, other: "complex | Inexact[np.ndarray, '...']", ) -> "_Spectral": - """Divide a constant by a spectral quantity. - - Parameters - ---------- - other : _Spectral, complex or numpy.ndarray - Other operand with its slope, or a frequency-independent constant, - scalar or broadcastable. - - Returns - ------- - _Spectral - Resulting value and analytic frequency slope. - """ + """Divide a constant by a spectral quantity.""" return (self**-1) * other def __pow__(self, power: float) -> "_Spectral": - """Apply the constant-exponent power rule. - - Parameters - ---------- - power : float - Constant exponent. - - Returns - ------- - _Spectral - Resulting value and analytic frequency slope. - """ + """Apply the constant-exponent power rule.""" return _Spectral( self.value**power, power * self.value ** (power - 1) * self.slope ) def __rpow__(self, base: float) -> "_Spectral": - """Differentiate a frequency-dependent exponent. - - Parameters - ---------- - base : float - Positive constant base. - - Returns - ------- - _Spectral - Resulting value and analytic frequency slope. - """ + """Differentiate a frequency-dependent exponent.""" value = base**self.value return _Spectral(value, value * math.log(base) * self.slope) def exp(self) -> "_Spectral": - """Apply the exponential chain rule. - - Returns - ------- - _Spectral - Resulting value and analytic frequency slope. - """ + """Apply the exponential chain rule.""" value = np.exp(self.value) return _Spectral(value, value * self.slope) @@ -712,23 +572,13 @@ def _cutoffs( ) -> tuple[_Spectral, _Spectral]: """Evaluate the HE and EH resonance fits of the 2019 paper, eqs. 4-5. - Parameters - ---------- - mu : numpy.ndarray - Azimuthal tube-mode indices, positive, as a column so the cutoffs come out - with one row per index. - nu : int - Radial tube-mode index, positive. - ratio : float - Dimensionless wall thickness divided by outer tube radius. - tube : _Spectral - Real tube index and its slope in s/rad. - - Returns - ------- - tuple of _Spectral - HE and EH dimensionless cutoff frequencies and their slopes, shape - ``(n_terms, n_omega)``. + - ``mu``: azimuthal tube-mode indices, positive, as a column so the cutoffs come out + with one row per index. + - ``nu``: radial tube-mode index, positive. + - ``ratio``: wall thickness divided by the outer tube radius. + - ``tube``: real tube index and its slope in s/rad. + - Returns: HE and EH dimensionless cutoff frequencies and their slopes, shape + ``(n_terms, n_omega)``. """ if nu == 1: he = ( @@ -761,19 +611,8 @@ def _resonance_sums( ) -> tuple[_Spectral, _Spectral]: """Sum loss resonances and the fundamental-mode dispersion correction. - Parameters - ---------- - frequency : _Spectral - Dimensionless normalised frequency and its slope. - tube : _Spectral - Real tube index and its slope. - lattice : TubeLattice - Wall, tube dimensions and truncation count. - - Returns - ------- - tuple of _Spectral - Weighted loss and dispersion sums with analytic slopes. + - ``frequency``: dimensionless normalised frequency and its slope. + - Returns: weighted loss and dispersion sums with analytic slopes. """ ratio = lattice.wall_thickness / lattice.tube_radius # One row per azimuthal index: the cutoffs and Lorentzians of every mu are @@ -839,14 +678,7 @@ class VincettiMode(MarcatiliMode): lattice: TubeLattice def _check_arguments(self) -> None: - """Validate the parent inputs and the tube geometry. - - Raises - ------ - ModeError - If the reduced model is requested or a declared radius gives - overlapping tubes. - """ + """Validate the parent inputs and the tube geometry.""" super()._check_arguments() if self.model == "reduced": raise ModeError('VincettiMode does not support model="reduced"') @@ -936,22 +768,10 @@ def _spectral_model( ) -> _SpectralFit: """Evaluate the empirical index, radius and dispersion fits together. - Parameters - ---------- - terms : _VincettiTerms - Frequency samples, including both material slopes. - radius : float - Core radius in m. - density : float or numpy.ndarray - Number density in m^-3, scalar or per species. - conventions : _Conventions, optional - Which form of the disputed terms to evaluate; solprop's by default. - - Returns - ------- - _SpectralFit - Refractivity, gas index, effective radius, dispersion correction and - waveguide decrement, each paired with its analytic frequency derivative. + - ``radius``: core radius in m. + - ``density``: number density in m^-3, scalar or per species. + - ``conventions``: which form of the disputed terms to evaluate; solprop's by + default. """ # Zeroing only the Bessel root uses precisely the parent's susceptibility # reduction and stable refractivity arithmetic, including complex species. @@ -1028,24 +848,9 @@ def _model_values( ) -> tuple[Inexact[np.ndarray, " n_omega"], Inexact[np.ndarray, " n_omega"]]: """Return refractivity and slope before the inherited loss policy. - Parameters - ---------- - terms : MarcatiliTerms - Samples from this mode, carrying its tube material. - radius : float - Core radius in m. - density : float or numpy.ndarray - Number density in m^-3, scalar or per species. - - Returns - ------- - tuple of numpy.ndarray - Refractivity and derivative in s/rad, shape ``(n_omega,)``. - - Raises - ------ - ModeError - If samples were taken from another model. + - ``radius``: core radius in m. + - ``density``: number density in m^-3, scalar or per species. + - Returns: refractivity and derivative in s/rad, shape ``(n_omega,)``. """ if not isinstance(terms, _VincettiTerms): raise ModeError("VincettiMode requires its own sampled_terms") @@ -1096,22 +901,13 @@ def effective_area( return np.asarray(area) def _area_rows(self) -> list[tuple[str, object]]: - """Return no area row: this mode's area is not a constant of the geometry. - - Returns - ------- - list of tuple - Empty. - """ + """Return no area row: this mode's area is not a constant of the geometry.""" return [] def _describe(self) -> tuple[str, list[tuple[str, object]]]: """Return the title and rows of the printed block. - Returns - ------- - tuple - The title and ``(key, value)`` rows. + - Returns: the title and ``(key, value)`` rows. """ title, rows = super()._describe() lattice = self.lattice diff --git a/src/solprop/modes/base.py b/src/solprop/modes/base.py index 168db6894..3dacea8e1 100644 --- a/src/solprop/modes/base.py +++ b/src/solprop/modes/base.py @@ -94,20 +94,7 @@ def _as_omega(omega: Float[np.ndarray, " n_omega"]) -> Float[np.ndarray, " n_omega"]: """Validate an angular-frequency argument for a derived quantity. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s, shape ``(n_omega,)``. - - Returns - ------- - numpy.ndarray - The same values as a float array. - - Raises - ------ - ModeError - If any value is not finite. + - ``omega``: angular frequencies in rad/s, shape ``(n_omega,)``. """ values = np.asarray(omega, dtype=float) if not np.all(np.isfinite(values)): @@ -331,23 +318,11 @@ def _second_difference( ) -> Float[np.ndarray, " n_omega"]: """Return the central second difference of `beta1` at one step. - Parameters - ---------- - mode : Mode - The mode. - omega : numpy.ndarray - Angular frequencies in rad/s. - centre : numpy.ndarray - ``beta1`` at `omega`, s/m. - step : numpy.ndarray - The step at each frequency, rad/s. - z : float - Position along the waveguide, m. - - Returns - ------- - numpy.ndarray - The difference, s^3/m. + - ``omega``: angular frequencies in rad/s. + - ``centre``: ``beta1`` at `omega`, s/m. + - ``step``: the step at each frequency, rad/s. + - ``z``: position along the waveguide, m. + - Returns: the difference, s^3/m. """ above = beta1(mode, omega + step, z) below = beta1(mode, omega - step, z) @@ -404,29 +379,22 @@ def zdw( lower = 2.0 * math.pi * C / longest upper = 2.0 * math.pi * C / shortest - def dispersion(value: float) -> float: + def _dispersion(value: float) -> float: """Return beta2 at one angular frequency, for the root finder. - Parameters - ---------- - value : float - Angular frequency in rad/s. - - Returns - ------- - float - ``beta_2`` in s^2/m. + - ``value``: angular frequency in rad/s. + - Returns: ``beta_2`` in s^2/m. """ return float(beta2(mode, np.array([value]), z)[0]) - at_lower, at_upper = dispersion(lower), dispersion(upper) + at_lower, at_upper = _dispersion(lower), _dispersion(upper) if at_lower * at_upper > 0.0: raise NoZeroDispersionError( f"beta2 does not change sign over {shortest * 1e9:.4g}-" f"{longest * 1e9:.4g} nm (it is {at_lower:.4g} and {at_upper:.4g} s^2/m at " "the ends), so there is no zero-dispersion wavelength in that window" ) - root = brentq(dispersion, lower, upper) + root = brentq(_dispersion, lower, upper) return 2.0 * math.pi * C / float(root) diff --git a/src/solprop/modes/capillary.py b/src/solprop/modes/capillary.py index a63a746f9..93c1d13a1 100644 --- a/src/solprop/modes/capillary.py +++ b/src/solprop/modes/capillary.py @@ -243,19 +243,9 @@ def _cladding_term_and_derivative( with ``Re e < 1`` -- a metal, or silica in the extreme ultraviolet -- is handled rather than producing a NaN. - Parameters - ---------- - permittivity : numpy.ndarray - Complex relative permittivity of the cladding, shape ``(n_omega,)``. - dpermittivity : numpy.ndarray - Its analytic frequency derivative in s/rad, same shape. - kind : str - ``"HE"``, ``"TE"`` or ``"TM"``. - - Returns - ------- - tuple of numpy.ndarray - ``v_n(omega)`` and ``dv_n/domega``. + - ``dpermittivity``: its analytic frequency derivative in s/rad, same shape. + - ``kind``: ``"HE"``, ``"TE"`` or ``"TM"``. + - Returns: ``v_n(omega)`` and ``dv_n/domega``. """ offset = np.asarray(permittivity - 1.0, dtype=complex) root = np.sqrt(offset) @@ -385,14 +375,7 @@ class MarcatiliMode(GasFillMixin): _terms_origin: ClassVar["MarcatiliMode | None"] = None def _check_arguments(self) -> None: - """Check the constructor arguments against each other. - - Raises - ------ - ModeError - If the family and the azimuthal index disagree, or the model, the - temperature or the loss specification is unusable. - """ + """Check the constructor arguments against each other.""" if not math.isfinite(self.phi): raise ModeError(f"phi must be finite, got {self.phi!r}") if self.kind not in ("HE", "TE", "TM"): @@ -460,20 +443,14 @@ def _area_rows(self) -> list[tuple[str, object]]: Marcatili's area is a constant times the radius squared, so it costs nothing; a subclass whose area is frequency dependent or tabulated returns no row. - Returns - ------- - list of tuple - ``(key, value)`` rows. + - Returns: ``(key, value)`` rows. """ return [("effective area", f"{self.aeff() * 1e12:.4g} um^2")] def _describe(self) -> tuple[str, list[tuple[str, object]]]: """Return the title and rows of the printed block. - Returns - ------- - tuple - The title and ``(key, value)`` rows. + - Returns: the title and ``(key, value)`` rows. """ radius = float(self.core_radius(np.array([0.0]))[0]) cladding = self.cladding_index @@ -497,82 +474,67 @@ def _describe(self) -> tuple[str, list[tuple[str, object]]]: # ------------------------------------------------------------------ traits @property - def z_constant(self) -> bool: - """Return whether nothing about the mode varies along z. + def z_constant(self) -> bool: # numpydoc ignore=RT01 + """Whether nothing about the mode varies along z. - Returns - ------- - bool - ``True`` when neither the core radius nor the density varies. The - multimode transform cares only about the radius, which - :attr:`radius_is_constant` reports separately. + ``True`` when neither the core radius nor the density varies. The + multimode transform cares only about the radius, which + :attr:`radius_is_constant` reports separately. """ return all(path.is_constant for path in self.parameter_paths.values()) @property - def radius_is_constant(self) -> bool: - """Return whether the transverse profile is the same at every z. + def radius_is_constant(self) -> bool: # numpydoc ignore=RT01 + """Whether the transverse profile is the same at every z. - Returns - ------- - bool - ``True`` when the core radius does not vary, so the synthesis and - projection matrices of the multimode transform may be built once. A - pressure gradient changes the effective index but not the field profile. + ``True`` when the core radius does not vary, so the synthesis and + projection matrices of the multimode transform may be built once. A + pressure gradient changes the effective index but not the field profile. """ return self.core_radius.is_constant @property - def scale_invariant(self) -> bool: - """Return whether the profile scales exactly with the core radius. + def scale_invariant(self) -> bool: # numpydoc ignore=RT01 + """Whether the profile scales exactly with the core radius. - Returns - ------- - bool - Always ``True``: the field depends on ``r`` only through ``r / a`` and the - normalisation goes as ``a^2``, so a taper rescales the transform matrices - by two scalars. + Always ``True``: the field depends on ``r`` only through ``r / a`` and the + normalisation goes as ``a^2``, so a taper rescales the transform matrices + by two scalars. """ return True @property - def azimuthal_order(self) -> int: - """Return the highest azimuthal harmonic in the Cartesian field components. + def azimuthal_order(self) -> int: # numpydoc ignore=RT01 + """Highest azimuthal harmonic in the Cartesian field components. - Returns - ------- - int - ``|n - 1|`` for a hybrid mode and 1 for a transverse one. The signed - ``n - 1`` would be negative for ``HE_{-1,m}`` and would then understate - the theta rule a cubic product needs. + ``|n - 1|`` for a hybrid mode and 1 for a transverse one. The signed + ``n - 1`` would be negative for ``HE_{-1,m}`` and would then understate + the theta rule a cubic product needs. """ return 1 if self.kind in ("TE", "TM") else abs(self.n - 1) @property - def channel_label(self) -> ChannelLabel: - """Return the family, the two indices and which polarisation partner this is. - - Returns - ------- - ChannelLabel - ``parity`` is ``None`` for a transverse family, which has no degenerate - polarisation partner. For a hybrid mode it follows the one definition in - :class:`~solprop.modes.protocol.ChannelLabel`: the ``even`` member is the - ``cos((n - 1) theta)`` pattern about the x axis, which here is the - **unrotated** mode ``phi = 0`` -- at that angle the transverse components - are ``(cos((n - 1) theta), -sin((n - 1) theta))``, the same pattern the - exact step-index ``even`` member carries, and for ``HE_1m`` it is the - x-polarised state. The quarter turn ``phi = pi / (2 n)`` is therefore the - ``odd`` member, y-polarised for ``HE_1m``. Any other rotation is a gauge - rather than a parity: its parity is ``None`` and its display label carries - the angle, so that two rotated copies of one mode are still told apart. - - A **negative** azimuthal index is the ``EH`` family, and the label says - so: the label's own ``azimuthal_order`` is the non-negative total - azimuthal order ``|n|`` and the family is ``"EH"``, which is how an exact - :class:`~solprop.modes.stepindex.StepIndexMode` of the same physical - channel labels itself, so an ``EH`` capillary mode can be put in a - :class:`~solprop.modes.collection.ModeCollection`. + def channel_label(self) -> ChannelLabel: # numpydoc ignore=RT01 + """Family, the two indices and which polarisation partner this is. + + ``parity`` is ``None`` for a transverse family, which has no degenerate + polarisation partner. For a hybrid mode it follows the one definition in + :class:`~solprop.modes.protocol.ChannelLabel`: the ``even`` member is the + ``cos((n - 1) theta)`` pattern about the x axis, which here is the + **unrotated** mode ``phi = 0`` -- at that angle the transverse components + are ``(cos((n - 1) theta), -sin((n - 1) theta))``, the same pattern the + exact step-index ``even`` member carries, and for ``HE_1m`` it is the + x-polarised state. The quarter turn ``phi = pi / (2 n)`` is therefore the + ``odd`` member, y-polarised for ``HE_1m``. Any other rotation is a gauge + rather than a parity: its parity is ``None`` and its display label carries + the angle, so that two rotated copies of one mode are still told apart. + + A **negative** azimuthal index is the ``EH`` family, and the label says + so: the label's own ``azimuthal_order`` is the non-negative total + azimuthal order ``|n|`` and the family is ``"EH"``, which is how an exact + :class:`~solprop.modes.stepindex.StepIndexMode` of the same physical + channel labels itself, so an ``EH`` capillary mode can be put in a + :class:`~solprop.modes.collection.ModeCollection`. Examples -------- @@ -662,19 +624,16 @@ def rotated(self, phi: float) -> "MarcatiliMode": return replace(self, phi=phi) @property - def orthogonal_polarisation(self) -> "MarcatiliMode | None": - """Return the degenerate partner of a hybrid mode, or ``None``. + def orthogonal_polarisation(self) -> "MarcatiliMode | None": # numpydoc ignore=RT01 + """Degenerate partner of a hybrid mode, or ``None``. - Returns - ------- - MarcatiliMode or None - The same mode turned by ``pi / (2 n)``, which turns the pattern a further - quarter turn from ``x`` towards ``y`` and so exchanges the two Cartesian - components: for ``HE_11`` that is the y-polarised partner of the - x-polarised default, and so the ``odd`` member of the pair - (:attr:`channel_label`). ``None`` for a transverse family, whose - members are not degenerate in polarisation -- ``TE_0m`` and ``TM_0m`` - are different modes, not two states of one. + The same mode turned by ``pi / (2 n)``, which turns the pattern a further + quarter turn from ``x`` towards ``y`` and so exchanges the two Cartesian + components: for ``HE_11`` that is the y-polarised partner of the + x-polarised default, and so the ``odd`` member of the pair + (:attr:`channel_label`). ``None`` for a transverse family, whose + members are not degenerate in polarisation -- ``TE_0m`` and ``TM_0m`` + are different modes, not two states of one. Examples -------- @@ -692,16 +651,13 @@ def orthogonal_polarisation(self) -> "MarcatiliMode | None": # ------------------------------------------------ the parameterised protocol @property - def parameter_paths(self) -> Mapping[str, ZProfile]: - """Return the scalar paths that carry the whole z dependence. + def parameter_paths(self) -> Mapping[str, ZProfile]: # numpydoc ignore=RT01 + """Scalar paths that carry the whole z dependence. - Returns - ------- - collections.abc.Mapping - Radius and the material paths, including constant ones. One component - retains ``density``; a shared fill uses total ``pressure`` in bar; - independent populations use ``density: