From 87ec67aa859d75ed65d035eeafa3dcadedc28b28 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:01:37 +0000 Subject: [PATCH 01/50] Add tools/docstring_diet.py to slim private docstrings Co-Authored-By: Claude Opus 5.5 --- tools/docstring_diet.py | 502 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 502 insertions(+) create mode 100644 tools/docstring_diet.py diff --git a/tools/docstring_diet.py b/tools/docstring_diet.py new file mode 100644 index 00000000..4360c14c --- /dev/null +++ b/tools/docstring_diet.py @@ -0,0 +1,502 @@ +# SPDX-FileCopyrightText: 2026 John C. Travers and contributors +# +# SPDX-License-Identifier: GPL-3.0-or-later + +r"""Cut private and nested docstrings down to the house rule (AGENTS.md). + +``python -m tools.docstring_diet PATH... [--write] [--check] [--report] +[--rename-closures]``. A definition is private when its numpydoc name (module stem and +qualname) has a single-underscore component, and nested when it sits in a function. +Either loses its ``Parameters``, ``Returns``, ``Yields``, ``Raises``, ``Examples`` and +``See Also`` sections; the summary, the extended summary and the other sections stay +verbatim, and an entry that states a unit, range, condition, convention or shape becomes +a one-line bullet. ``--check`` proves that each file still compiles and that its AST +without docstrings is unchanged. ``--rename-closures`` renames a nested function that +numpydoc validates (a public name in a public function) to ``_name`` in its function. +""" + +import argparse +import ast +import functools +import re +import sys +import textwrap +import tomllib +from dataclasses import dataclass, field +from pathlib import Path + +from numpydoc.docscrape import NumpyDocString + +__all__ = ["main"] + +_PRIVATE = re.compile(r"(^|\.)_[^_]") +_BULLETED = ("Parameters", "Other Parameters", "Receives", "Returns", "Yields") +_DROP = {*_BULLETED, "Raises", "Warns", "Examples", "See Also"} +_KEEP = {"Notes", "References", "Warnings", "Attributes", "Methods"} +_UNDERLINE = re.compile(r"^\s*-{3,}\s*$") +_QUOTED = re.compile(r"^([rRuU]{0,2})(\"\"\"|''')(.*)\2$", re.DOTALL) +_JAXTYPING = re.compile( + r"\b(Float|Complex|Int|UInt|Bool|Shaped|Num|Real|Inexact|Integer)\[" +) +_SHAPE = re.compile(r",?\s*(of |with |in )?shape\s*(``[^`]*``|\([^)]*\))") +_FACTS = re.compile( + "|".join(( # a unit; a range, condition or sentinel; a convention or layout + r"(?≤≥]|\[\s*-?\w+\s*,|\(\s*-?[\d.]+\s*,\s*[\d.∞]", + r"\b(FFT|DFT|conventions?|sign|normali[sz]\w*|layout|axis|axes|row-major|" + r"contiguous|interface|internal|conjugat\w*|keyed)\b", + )) +) # fmt: skip +_COMPOSITE = re.compile(r"^(tuple|dict|list of tuple|(collections\.abc\.)?Mapping)\b") +_PROPER = {"True", "False", "None", "Kerr", "Raman", "Fourier", "Butcher", "Adams"} +_PROPER |= {"Milne", "Hermite", "Gaussian", "Newton", "Python", "NumPy", "FFTW"} + + +@dataclass +class _Report: + """What a run did to one file.""" + + removed: int = 0 + examples: list[str] = field(default_factory=list) + closures: list[str] = field(default_factory=list) + skipped: list[str] = field(default_factory=list) + flattened: list[str] = field(default_factory=list) + + +def _exclude(path: Path) -> re.Pattern[str]: + """Return numpydoc's exclude pattern, from the nearest ``pyproject.toml``.""" + for parent in path.resolve().parents: + if (toml := parent / "pyproject.toml").is_file(): + with toml.open("rb") as stream: + config = tomllib.load(stream).get("tool", {}) + patterns = config.get("numpydoc_validation", {}).get("exclude", []) + return re.compile("|".join(patterns) or r"(?!)") + return re.compile(r"(?!)") + + +def _module(path: Path) -> tuple[Path, str]: + """Return the directory above ``path``'s top package, and its dotted module name.""" + parts, parent = [path.stem], path.resolve().parent + while (parent / "__init__.py").is_file(): + parts.insert(0, parent.name) + parent = parent.parent + return parent, ".".join(parts[:-1] if parts[-1] == "__init__" else parts) + + +@functools.cache +def _reexported(root: Path) -> frozenset[tuple[str, str]]: + """Return each (module, name) that a public module lists in its ``__all__``. + + A class or function defined in a private module and re-exported is public API: its + docstring is the reference page, whatever numpydoc's file-based name says. + """ + edges: list[tuple[str, str, str, str]] = [] # importer, source, name, alias + for file in sorted(root.rglob("*.py")): + tree, module = ast.parse(file.read_bytes()), _module(file)[1] + listed = { + c.value + for node in tree.body + if isinstance(node, ast.Assign | ast.AugAssign | ast.AnnAssign) + and "__all__" in ast.unparse(node).partition("=")[0] + for c in ast.walk(node) + if isinstance(c, ast.Constant) and isinstance(c.value, str) + } + edges += [ + (module, node.module, a.name, a.asname or a.name) + for node in ast.walk(tree) + if isinstance(node, ast.ImportFrom) and node.module + for a in node.names + if (a.asname or a.name) in listed + ] + found: set[tuple[str, str]] = set() + while True: + more = { + (source, name) + for importer, source, name, alias in edges + if not _PRIVATE.search(importer) or (importer, alias) in found + } + if more <= found: + return frozenset(found) + found |= more + + +def _demands_sections(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool: + """Return whether numpydoc asks this function for Parameters, Returns or Yields. + + Mirrors numpydoc's PR01, RT01 and YD01: any argument, a ``return`` with a value + outside nested functions, or a ``yield`` statement in the body. + """ + args = node.args + if args.posonlyargs or args.args or args.kwonlyargs or args.vararg or args.kwarg: + return True + stack: list[ast.AST] = list(node.body) + while stack: + child = stack.pop() + value = getattr(child, "value", None) + if isinstance(child, ast.Return) and not ( + value is None or (isinstance(value, ast.Constant) and value.value is None) + ): + return True + if isinstance(child, ast.Expr) and isinstance( + child.value, ast.Yield | ast.YieldFrom + ): + return True + if not isinstance(child, ast.FunctionDef): + stack.extend(ast.iter_child_nodes(child)) + return False + + +def _bullet(label: str, desc: list[str], indent: str) -> list[str]: + """Return one entry as a wrapped ``- label: fact.`` bullet.""" + fact = " ".join(" ".join(desc).split()) + first = fact.partition(" ")[0].rstrip(",.:;") + if first.istitle() and first.isalpha() and first not in _PROPER: + fact = fact[0].lower() + fact[1:] + fact = fact.rstrip(",;:") if fact.endswith((",", ";", ":")) else fact + fact = fact if fact.endswith(".") else f"{fact}." + return textwrap.wrap( + f"- {label}: {fact}", + width=88, + initial_indent=indent, + subsequent_indent=f"{indent} ", + break_long_words=False, + break_on_hyphens=False, + ) + + +def _words(text: str) -> set[str]: + """Return the lower-case words of ``text``, without the commonest ones.""" + return set(re.findall(r"\w+", text.lower())) - {"the", "a", "an", "of", "in", "to"} + + +def _bullets( # noqa: PLR0917 - private, called once + doc: NumpyDocString, + node: ast.AST, + indent: str, + report: _Report, + name: str, + said: set[str], +) -> list[str]: + """Return the bullets of the entries whose description states a fact.""" + annotations: dict[str, str] = {} + if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef): + arguments = node.args + every = [*arguments.posonlyargs, *arguments.args, *arguments.kwonlyargs] + every += [a for a in (arguments.vararg, arguments.kwarg) if a is not None] + annotations = {a.arg: ast.unparse(a.annotation) for a in every if a.annotation} + annotations["return"] = ast.unparse(node.returns) if node.returns else "" + lines: list[str] = [] + for section in _BULLETED: + for entry in doc[section]: + names = [n.strip() for n in entry.name.split(",") if n.strip()] + returned = section in {"Returns", "Yields"} + keys = ["return"] if returned else [n.lstrip("*") for n in names] + text = " ".join(entry.desc) + shaped = all(_JAXTYPING.search(annotations.get(k, "")) for k in keys) + if shaped: # the annotation carries the shape + text = _SHAPE.sub("", text) + composite = _COMPOSITE.match(entry.type) and re.search(r"``|;| and ", text) + if not ( + (not shaped and "shape" in text) or composite or _FACTS.search(text) + ): + continue + if any( + not line.strip() or line.lstrip().startswith("..") + for line in entry.desc + ): + report.flattened.append(name) + label = ", ".join(f"``{n}``" for n in names) + if returned: + label = f"{section} {label}" if label and entry.type else section + if "``" in text or not _words(text) <= said: # else the summary says it + lines += _bullet(label, entry.desc, indent) + return lines + + +def _slim( + seg: str, node: ast.AST, column: int, name: str, report: _Report +) -> str | None: + """Return the slimmed source of one docstring literal, or None to leave it alone.""" + quoted = _QUOTED.match(seg) + if quoted is None: + report.skipped.append(f"{name}: not a triple-quoted literal") + return None + prefix, quote, content = quoted.groups() + lines = content.split("\n") + heads = [ + i + for i in range(1, len(lines) - 1) + if _UNDERLINE.match(lines[i + 1]) and lines[i].strip() + ] + titles = [lines[i].strip() for i in heads] + if unknown := [t for t in titles if t not in _DROP | _KEEP]: + report.skipped.append(f"{name}: unknown sections {unknown}") + return None + if not set(titles) & _DROP: + return None + try: + doc = NumpyDocString(textwrap.dedent("\n".join(lines[1:]))) + except Exception as error: + report.skipped.append(f"{name}: {error}") + return None + report.examples += [name] if "Examples" in titles else [] + indent = " " * min( + (len(line) - len(line.lstrip()) for line in lines[1:] if line.strip()), + default=column, + ) + out = _trimmed(lines[: heads[0]]) + said = _words("\n".join(lines[: heads[0]])) + if bullets := _bullets(doc, node, indent, report, name, said): + out += ["", *bullets] + for title, start, end in zip(titles, heads, [*heads[1:], len(lines)], strict=True): + if title in _KEEP: + out += ["", *_trimmed(lines[start:end])] + if len(out) == 1 and column + len(prefix) + 6 + len(out[0]) > 88: + report.skipped.append(f"{name}: summary too long to stand alone (D200)") + return None + text = out[0] if len(out) == 1 else "\n".join(out) + f"\n{indent}" + return f"{prefix}{quote}{text}{quote}" + + +def _trimmed(lines: list[str]) -> list[str]: + """Return ``lines`` without trailing blank lines.""" + while lines and not lines[-1].strip(): + lines = lines[:-1] + return lines + + +class _Diet: + """Plan the docstring and rename edits of one module.""" + + def __init__(self, path: Path, source: str, rename: bool) -> None: + self.path, self.source, self.rename = path, source, rename + self.lines = source.splitlines(keepends=True) + self.starts = [0] + for line in self.lines: + self.starts.append(self.starts[-1] + len(line)) + self.exclude = _exclude(path) + root, module = _module(path) + self.public = {name for m, name in _reexported(root) if m == module} + self.edits: list[tuple[int, int, str]] = [] + self.renamed: list[tuple[tuple[str, ...], str, str]] = [] + self.report = _Report() + + def offset(self, line: int, col: int) -> int: + """Return the character offset of an AST position (UTF-8 column).""" + text = self.lines[line - 1].encode()[:col].decode() + return self.starts[line - 1] + len(text) + + def visit( + self, parent: ast.AST, stack: tuple[str, ...], func: bool, seen: bool + ) -> None: + """Plan the edits of every definition under ``parent``. + + ``seen`` tracks whether numpydoc's visitor reaches the node: it descends only + through the direct bodies of modules, classes and functions. + """ + body = getattr(parent, "body", []) + for child in ast.iter_child_nodes(parent): + if not isinstance( + child, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef + ): + self.visit(child, stack, func, seen=False) + continue + reached = seen and any(child is s for s in body) + qual = (*stack, child.name) + name = ".".join((self.path.stem, *qual)) + validated = func and reached and not self.exclude.search(name) + validated = validated and not _PRIVATE.search(name) + if validated and ( + isinstance(child, ast.ClassDef) or _demands_sections(child) + ): + closure = isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef) + if not ( + self.rename + and closure + and not isinstance(child, ast.ClassDef) + and self.rename_closure(parent, child, stack) + ): + self.report.closures.append(f"{name} (kept)") + self.visit(child, qual, True, seen=True) + continue + qual = (*stack, f"_{child.name}") + self.report.closures.append(f"{name} -> _{child.name}") + public = qual[0] in self.public # re-exported from a private module + private = _PRIVATE.search(".".join(qual) if public else name) + if (func or private) and ast.get_docstring(child, clean=False): + self.slim(child, name) + self.visit( + child, qual, func or not isinstance(child, ast.ClassDef), reached + ) + + def slim(self, node: ast.AST, name: str) -> None: + """Plan the rewrite of one node's docstring.""" + expr = getattr(node, "body")[0] # noqa: B009 - typed as ast.AST + start = self.offset(expr.lineno, expr.col_offset) + end = self.offset(expr.end_lineno, expr.end_col_offset) + column = start - self.starts[expr.lineno - 1] + new = _slim(self.source[start:end], node, column, name, self.report) + if new is not None: + self.edits.append((start, end, new)) + + def rename_closure( + self, + outer: ast.FunctionDef | ast.AsyncFunctionDef, + inner: ast.FunctionDef | ast.AsyncFunctionDef, + stack: tuple[str, ...], + ) -> bool: + """Plan renaming ``inner`` to ``_inner`` throughout ``outer``; False if unsafe. + + Unsafe is any other binding of either name in ``outer``, a ``global`` or + ``nonlocal`` naming it, the name as a string, or a read of ``__name__``. + """ + old, new = inner.name, f"_{inner.name}" + both, names, binders = {old, new}, [], 0 + for node in ast.walk(outer): + if isinstance(node, ast.Name) and node.id in both: + if node.id == new or not isinstance(node.ctx, ast.Load): + return False + names.append(node) + elif isinstance( + node, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef + ): + if node.name == new: + return False + binders += node.name == old + elif ( + (isinstance(node, ast.Global | ast.Nonlocal) and both & set(node.names)) + or (isinstance(node, ast.alias) and (node.asname or node.name) in both) + or (isinstance(node, ast.arg) and node.arg in both) + or (isinstance(node, ast.ExceptHandler) and node.name in both) + or ( + isinstance(node, ast.Attribute) + and node.attr in {"__name__", "__qualname__"} + ) + or (isinstance(node, ast.Constant) and node.value in both) + ): + return False + if binders != 1: + return False + line = self.lines[inner.lineno - 1] + found = re.compile(rf"def\s+({old})\b").search( + line.encode()[inner.col_offset :].decode() + ) + if found is None: + return False + col = len(line.encode()[: inner.col_offset].decode()) + found.start(1) + at = self.starts[inner.lineno - 1] + col + self.edits.append((at, at + len(old), new)) + for node in names: + at = self.offset(node.lineno, node.col_offset) + self.edits.append((at, at + len(old), new)) + self.renamed.append((stack, new, old)) + return True + + def apply(self) -> str: + """Return the source with every planned edit applied, last first.""" + text = self.source + for start, end, new in sorted(self.edits, reverse=True): + text = text[:start] + new + text[end:] + return text + + +def _stripped(tree: ast.Module, renamed: list[tuple[tuple[str, ...], str, str]]) -> str: + """Return the dump of ``tree`` without docstrings, closure renames undone.""" + for node in ast.walk(tree): + body = getattr(node, "body", None) + if isinstance(body, list) and body and isinstance(body[0], ast.Expr): + value = body[0].value + if isinstance(value, ast.Constant) and isinstance(value.value, str): + del body[0] + for stack, new, old in renamed: + scope: ast.AST = tree + for part in stack: + scope = next( + n + for n in ast.walk(scope) + if isinstance(n, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) + and n.name == part + ) + for node in ast.walk(scope): + if isinstance(node, ast.Name) and node.id == new: + node.id = old + if ( + isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef) + and node.name == new + ): + node.name = old + return ast.dump(tree) + + +def main(argv: list[str] | None = None) -> int: + """Slim the docstrings of the given files or directories; return the exit status. + + Parameters + ---------- + argv : list of str, optional + The command-line arguments; ``sys.argv[1:]`` by default. + + Returns + ------- + int + 0, or 1 when ``--check`` finds a changed AST or a file that does not compile. + """ + parser = argparse.ArgumentParser(prog="docstring_diet", description=__doc__) + parser.add_argument("paths", nargs="+", type=Path) + parser.add_argument("--write", action="store_true", help="rewrite the files") + parser.add_argument("--check", action="store_true", help="prove the AST unchanged") + parser.add_argument("--report", action="store_true", help="print what changed") + parser.add_argument( + "--rename-closures", + action="store_true", + help="rename validated nested functions to _name", + ) + args = parser.parse_args(argv) + files = [ + f for p in args.paths for f in (sorted(p.rglob("*.py")) if p.is_dir() else [p]) + ] + status, total, out = 0, 0, sys.stdout + for path in files: + source = path.read_text(encoding="utf-8") + diet = _Diet(path, source, args.rename_closures) + diet.visit(ast.parse(source), (), func=False, seen=True) + new = diet.apply() + report = diet.report + report.removed = source.count("\n") - new.count("\n") + total += report.removed + if args.check and new != source: + try: + compile(new, str(path), "exec") + same = _stripped(ast.parse(source), []) == _stripped( + ast.parse(new), diet.renamed + ) + except SyntaxError as error: + same = False + out.write(f"CHECK FAILED {path}: {error}\n") + if not same: + status = 1 + out.write(f"CHECK FAILED {path}: the stripped AST changed\n") + continue + if args.write and new != source: + path.write_text(new, encoding="utf-8") + if args.report and (new != source or report.skipped or report.closures): + out.write(f"{path}: -{report.removed} lines\n") + for title, items in ( + ("examples dropped", report.examples), + ("validated closures", report.closures), + ("skipped", report.skipped), + ("flattened", report.flattened), + ): + out.writelines(f" {title}: {item}\n" for item in items) + if args.report: + out.write(f"total: -{total} lines in {len(files)} files\n") + return status + + +if __name__ == "__main__": + raise SystemExit(main()) From 3c91766965ac450ef2e61249553b8ec9ba42d351 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:04:59 +0000 Subject: [PATCH 02/50] Slim private docstrings in solprop.stepper Mechanical: python tools/docstring_diet.py src/solprop/stepper --write --check --report --rename-closures Co-Authored-By: Claude Opus 5.5 --- src/solprop/stepper/multistep.py | 29 ++-------- src/solprop/stepper/schedule.py | 27 +++------- src/solprop/stepper/tableau.py | 91 +++----------------------------- 3 files changed, 16 insertions(+), 131 deletions(-) diff --git a/src/solprop/stepper/multistep.py b/src/solprop/stepper/multistep.py index 9be2557e..9f5d4b74 100644 --- a/src/solprop/stepper/multistep.py +++ b/src/solprop/stepper/multistep.py @@ -715,20 +715,8 @@ def _basis_rows( (``solprop._helpers``), each with these operations in this order, so the values are the same bit for bit. - Parameters - ---------- - points : numpy.ndarray - The distinct nodes, float64. - upper : float - The integral's upper limit. - integrate : bool - 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). + - Returns: the coefficients, shape ``(n, n)`` (zero when integrating), and the + integrals, shape ``(n,)`` (unset when not). """ n = points.shape[0] rows = np.zeros((n, n)) @@ -960,18 +948,7 @@ def milne_factor( def _check_nodes(theta: Float[np.ndarray, " n_nodes"]) -> None: - """Refuse empty, non-finite or repeated nodes. - - Parameters - ---------- - theta : numpy.ndarray - The nodes. - - Raises - ------ - StepperError - If the nodes are empty, not finite or not distinct. - """ + """Refuse empty, non-finite or repeated nodes.""" if theta.size == 0 or not np.all(np.isfinite(theta)): msg = f"nodes must be non-empty and finite, got {theta}" raise StepperError(msg) diff --git a/src/solprop/stepper/schedule.py b/src/solprop/stepper/schedule.py index c9b9c9a6..82180cc1 100644 --- a/src/solprop/stepper/schedule.py +++ b/src/solprop/stepper/schedule.py @@ -502,18 +502,10 @@ def rescaled(self, factor: float) -> "FixedSchedule": def _interval_index(edges: Float[np.ndarray, " n_edges"], position: float) -> int: """Return the index of the interval of ``edges`` containing ``position``. - Parameters - ---------- - edges : numpy.ndarray - Strictly increasing interval edges of shape ``(n + 1,)``, m. - position : float - Position to locate, m. - - Returns - ------- - int - Interval index, clamped to ``[0, n - 1]`` so that a position on either end of - the covered range belongs to the interval that touches it. + - ``edges``: strictly increasing interval edges of shape ``(n + 1,)``, m. + - ``position``: position to locate, m. + - Returns: interval index, clamped to ``[0, n - 1]`` so that a position on either + end of the covered range belongs to the interval that touches it. """ index = int(np.searchsorted(edges, position, side="right")) - 1 return min(max(index, 0), int(edges.size) - 2) @@ -994,15 +986,8 @@ def _bound_order( ) -> tuple[float, int]: """Order a cap or a landing: shortest first, then the highest priority. - Parameters - ---------- - bound : tuple - The step it allows, m, and what it is, then its position for a landing. - - Returns - ------- - tuple - The step and the negated priority. + - ``bound``: the step it allows, m, and what it is, then its position for a landing. + - Returns: the step and the negated priority. """ return bound[0], -_LANDING_PRIORITY[bound[1]] diff --git a/src/solprop/stepper/tableau.py b/src/solprop/stepper/tableau.py index df545122..5a66ff66 100644 --- a/src/solprop/stepper/tableau.py +++ b/src/solprop/stepper/tableau.py @@ -90,18 +90,7 @@ def _readonly(values: Float[np.ndarray, "*shape"]) -> Float[np.ndarray, "*shape"]: - """Return a private float64 copy that cannot be written through. - - Parameters - ---------- - values : numpy.ndarray - Array of any shape, dimensionless. - - Returns - ------- - numpy.ndarray - A float64 copy with the write flag cleared. - """ + """Return a private float64 copy that cannot be written through.""" copy = np.array(values, dtype=np.float64) copy.setflags(write=False) return copy @@ -297,16 +286,6 @@ def _dot(left: Float[np.ndarray, " n"], right: Float[np.ndarray, " n"]) -> float add error of its own: ``math.fsum`` is correctly rounded, whereas a BLAS dot product reassociates and costs ~1e-15 on the Tsit5 order-5 trees, enough to read a genuine order 5 as order 4. - - Parameters - ---------- - left, right : numpy.ndarray - Vectors of the same length, dimensionless. - - Returns - ------- - float - The inner product, rounded once. """ return math.fsum(left * right) @@ -314,20 +293,7 @@ def _dot(left: Float[np.ndarray, " n"], right: Float[np.ndarray, " n"]) -> float def _matvec( matrix: Float[np.ndarray, "n n"], vector: Float[np.ndarray, " n"] ) -> Float[np.ndarray, " n"]: - """Return ``matrix @ vector``, each row reduced with :func:`math.fsum`. - - Parameters - ---------- - matrix : numpy.ndarray - Square matrix, dimensionless. - vector : numpy.ndarray - Vector of matching length, dimensionless. - - Returns - ------- - numpy.ndarray - The product, of the same length as ``vector``. - """ + """Return ``matrix @ vector``, each row reduced with :func:`math.fsum`.""" return np.array([_dot(row, vector) for row in matrix]) @@ -338,19 +304,7 @@ def _elementary_weights( ) -> dict[str, float]: """Evaluate the 17 elementary weights of the trees through order 5. - Parameters - ---------- - nodes : numpy.ndarray - Stage abscissae ``c`` of shape ``(n_stages,)``, dimensionless. - matrix : numpy.ndarray - Butcher matrix ``A`` of shape ``(n_stages, n_stages)``, dimensionless. - weights : numpy.ndarray - Weight vector of shape ``(n_stages,)``, dimensionless. - - Returns - ------- - dict - Elementary weight of every tree, keyed by tree label. + - Returns: elementary weight of every tree, keyed by tree label. """ node = nodes node2 = node * node @@ -386,19 +340,7 @@ def _residuals( ) -> Mapping[str, float]: """Return the absolute order-condition residuals, keyed by tree label. - Parameters - ---------- - nodes : numpy.ndarray - Stage abscissae ``c`` of shape ``(n_stages,)``, dimensionless. - matrix : numpy.ndarray - Butcher matrix ``A`` of shape ``(n_stages, n_stages)``, dimensionless. - weights : numpy.ndarray - Weight vector of shape ``(n_stages,)``, dimensionless. - - Returns - ------- - collections.abc.Mapping - Absolute residual of every tree, keyed by tree label. + - Returns: absolute residual of every tree, keyed by tree label. """ attained = _elementary_weights(nodes, matrix, weights) return MappingProxyType( @@ -409,17 +351,8 @@ def _residuals( def _attained_order(residuals: Mapping[str, float], tolerance: float) -> int: """Return the highest order whose conditions all sit within ``tolerance``. - Parameters - ---------- - residuals : collections.abc.Mapping - Absolute residuals keyed by tree label, dimensionless. - tolerance : float - Threshold below which a condition counts as satisfied, dimensionless. - - Returns - ------- - int - The attained order, 0 if even the first condition fails. + - ``residuals``: absolute residuals keyed by tree label, dimensionless. + - Returns: the attained order, 0 if even the first condition fails. """ attained = 0 for order in range(1, MAX_CHECKED_ORDER + 1): @@ -437,17 +370,7 @@ def _attained_order(residuals: Mapping[str, float], tolerance: float) -> int: def _worst_residual(residuals: Mapping[str, float], up_to: int) -> float: """Return the largest residual among the conditions of order ``up_to`` and below. - Parameters - ---------- - residuals : collections.abc.Mapping - Absolute residuals keyed by tree label, dimensionless. - up_to : int - Highest condition order to include. - - Returns - ------- - float - The largest residual in that set. + - ``residuals``: absolute residuals keyed by tree label, dimensionless. """ return max( residuals[label] for order, label, _ in _ORDER_CONDITIONS if order <= up_to From 194950a93750c34f25639ce57d1b2abb43e1efab Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:04:59 +0000 Subject: [PATCH 03/50] Slim private docstrings in solprop.calibration Mechanical: python tools/docstring_diet.py src/solprop/calibration --write --check --report --rename-closures Co-Authored-By: Claude Opus 5.5 --- src/solprop/calibration/_native.py | 33 +- src/solprop/calibration/child.py | 110 ++---- src/solprop/calibration/command.py | 36 +- src/solprop/calibration/controller.py | 497 ++++---------------------- src/solprop/calibration/first_use.py | 89 +---- src/solprop/calibration/grain.py | 30 +- src/solprop/calibration/problems.py | 48 +-- src/solprop/calibration/refine.py | 96 +---- 8 files changed, 109 insertions(+), 830 deletions(-) diff --git a/src/solprop/calibration/_native.py b/src/solprop/calibration/_native.py index d7b99f5d..7d025185 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 abdac28d..2fc253c8 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 636480f5..2633b91c 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 da68b84d..69dc4da7 100644 --- a/src/solprop/calibration/controller.py +++ b/src/solprop/calibration/controller.py @@ -298,46 +298,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 +324,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 +370,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 +381,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 +398,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 +489,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 +537,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 +547,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 +583,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 +629,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 +646,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 +677,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 +714,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 +730,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 +755,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 +770,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 +845,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 +897,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 +936,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 +973,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 +1023,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 +1074,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 +1086,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 +1130,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 +1185,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 +1200,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 +1327,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 +1359,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 +1571,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 +1586,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 e01a9fb7..d480e7e7 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 4f44b9bf..0ee759ad 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 13ed5bc3..4d0d8374 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 fd21ab41..5bf04740 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 @@ -268,21 +247,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 +268,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 +288,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 +308,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}") From 84c49155711a010f254ae1ab2b5446c7cf6834bf Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:05:28 +0000 Subject: [PATCH 04/50] Shorten _accurate's docstring by hand, as the slimming left it Co-Authored-By: Claude Opus 5.5 --- src/solprop/calibration/refine.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/solprop/calibration/refine.py b/src/solprop/calibration/refine.py index 5bf04740..8b87a6c9 100644 --- a/src/solprop/calibration/refine.py +++ b/src/solprop/calibration/refine.py @@ -183,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`. + """Return whether `found` is finite and close to `reference`, window by window. - 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. - - 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)): From afcc2d76d11aefd2d3432bdb91defb299394bf42 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:12:43 +0000 Subject: [PATCH 05/50] Slim private docstrings in solprop.modes Mechanical: python tools/docstring_diet.py src/solprop/modes --write --check --report --rename-closures Co-Authored-By: Claude Opus 5.5 --- src/solprop/modes/_stepindex_vector.py | 102 +---- src/solprop/modes/antiresonant.py | 267 ++----------- src/solprop/modes/base.py | 54 +-- src/solprop/modes/capillary.py | 448 ++++------------------ src/solprop/modes/collection.py | 92 +---- src/solprop/modes/connection.py | 112 +----- src/solprop/modes/fill.py | 178 ++------- src/solprop/modes/graded_index.py | 168 ++------- src/solprop/modes/guide.py | 180 ++------- src/solprop/modes/material.py | 56 +-- src/solprop/modes/protocol.py | 5 +- src/solprop/modes/quadrature.py | 196 ++-------- src/solprop/modes/rectangular.py | 145 ++------ src/solprop/modes/slab.py | 89 +---- src/solprop/modes/stepindex.py | 494 ++++--------------------- src/solprop/modes/tabulated.py | 209 ++--------- src/solprop/modes/vector.py | 20 +- 17 files changed, 415 insertions(+), 2400 deletions(-) diff --git a/src/solprop/modes/_stepindex_vector.py b/src/solprop/modes/_stepindex_vector.py index 89ab586b..c58a5bd9 100644 --- a/src/solprop/modes/_stepindex_vector.py +++ b/src/solprop/modes/_stepindex_vector.py @@ -36,21 +36,8 @@ 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. + - Returns: dimensionless radial function, argument derivative and divided value. """ if inside: value = jv(order, argument) / jv(order, edge) @@ -115,19 +102,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 +162,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 +183,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 +291,9 @@ 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. + - 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,18 +302,7 @@ 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. - - Returns - ------- - float - Dimensionless-radius integrand before the squared-radius factor. - """ + """Evaluate the angularly integrated radial flux.""" points = radius * x * np.column_stack((np.cos(angles), np.sin(angles))) e, h = _prepared_fields(parameters, points) return float( diff --git a/src/solprop/modes/antiresonant.py b/src/solprop/modes/antiresonant.py index 3184e86e..c330b400 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,12 @@ 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. + - ``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 +610,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 +677,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 +767,8 @@ 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. """ # Zeroing only the Bessel root uses precisely the parent's susceptibility # reduction and stable refractivity arithmetic, including complex species. @@ -1028,24 +845,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 +898,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 168db689..3dacea8e 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 a63a746f..d27e4943 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 @@ -854,15 +831,8 @@ def sampled_terms(self, omega: Float[np.ndarray, " n_omega"]) -> MarcatiliTerms: def _sample_terms(self, values: Float[np.ndarray, " n_omega"]) -> MarcatiliTerms: """Sample the gas and cladding models at validated frequencies. - Parameters - ---------- - values : numpy.ndarray - Finite, strictly positive angular frequencies in rad/s. - - Returns - ------- - MarcatiliTerms - The frequency-only terms, as :meth:`sampled_terms` documents. + - ``values``: finite, strictly positive angular frequencies in rad/s. + - Returns: the frequency-only terms, as :meth:`sampled_terms` documents. """ susceptibility, dsusceptibility = self._sampled_susceptibility(values) if isinstance(self.cladding_index, GlassIndex): @@ -888,17 +858,8 @@ def _waveguide( ) -> tuple[Inexact[np.ndarray, " n_omega"], Inexact[np.ndarray, " n_omega"]]: """Return the waveguide term of the full model and its frequency derivative. - Parameters - ---------- - terms : MarcatiliTerms - The sampled frequency-dependent quantities. - radius : float - Core radius in m at the position of interest. - - Returns - ------- - tuple of numpy.ndarray - ``w = (u/(ka))^2 (1 - i v/(ka))^2`` and ``dw/domega``. + - ``radius``: core radius in m at the position of interest. + - Returns: ``w = (u/(ka))^2 (1 - i v/(ka))^2`` and ``dw/domega``. """ omega = terms.omega scale = C / (omega * radius) @@ -920,20 +881,11 @@ def _model_values( ) -> tuple[Inexact[np.ndarray, " n_omega"], Inexact[np.ndarray, " n_omega"]]: """Return ``n_eff - 1`` and ``dn_eff/domega`` before the loss specification. - Parameters - ---------- - terms : MarcatiliTerms - The sampled frequency-dependent quantities. - radius : float - Core radius in m. - density : float or numpy.ndarray - Number density in m^-3, scalar for the existing gas spelling or one - entry per sampled mixture component. - - Returns - ------- - tuple of numpy.ndarray - The refractivity and its derivative, with the model's own loss included. + - ``radius``: core radius in m. + - ``density``: number density in m^-3, scalar for the existing gas spelling or + one entry per sampled mixture component. + - Returns: the refractivity and its derivative, with the model's own loss + included. """ omega = terms.omega if terms.core_susceptibility.ndim == 1: @@ -1321,17 +1273,8 @@ def _loss_on_slope( The ``z`` counterpart of ``_apply_loss`` and ``_floored``, zeroing the slope wherever they zero the frequency derivative. - Parameters - ---------- - refractivity : numpy.ndarray - The model's ``n_eff - 1`` with its own loss, before the specification. - derivative : numpy.ndarray - Its ``z`` derivative in 1/m. - - Returns - ------- - numpy.ndarray - The ``z`` derivative of what :meth:`neff` returns, in 1/m. + - ``derivative``: its ``z`` derivative in 1/m. + - Returns: the ``z`` derivative of what :meth:`neff` returns, in 1/m. """ specification = self.loss if callable(specification): @@ -1353,21 +1296,9 @@ def _scalars( ) -> tuple[float, float | Float[np.ndarray, " n_species"]]: """Return the core radius and the number density at one position. - Parameters - ---------- - z : float - Position along the waveguide, m. - - Returns - ------- - tuple of float - Core radius in m and number density in m^-3 -- the two scalars that carry - the whole z dependence of the effective index. - - Raises - ------ - ModeError - If the core radius is not positive there. + - ``z``: position along the waveguide, m. + - Returns: core radius in m and number density in m^-3 -- the two scalars that + carry the whole z dependence of the effective index. """ position = np.array([float(z)]) radius = float(self.core_radius(position)[0]) @@ -1518,11 +1449,8 @@ def field( def _effective_area_factor(self) -> float: """Return the radius-independent part of the effective area. - Returns - ------- - float - ``A_eff / a^2``, the ratio of the squared second moment of the transverse - profile to its fourth moment, evaluated once at construction. + - Returns: ``A_eff / a^2``, the ratio of the squared second moment of the + transverse profile to its fourth moment, evaluated once at construction. """ order = self.n if self.kind == "HE" else 2 denominator = quad( @@ -1557,10 +1485,7 @@ def _shared_sampling() -> Iterator[None]: A scope opened inside another is the enclosing one. - Yields - ------ - None - Nothing; the scope ends when the block exits. + - Yields: nothing; the scope ends when the block exits. """ if _SHARED_TERMS.get() is not None: # Nested in an enclosing scope, which keeps sharing its terms. @@ -1576,15 +1501,7 @@ def _shared_sampling() -> Iterator[None]: def _same_rows(first: tuple[object, ...], second: tuple[object, ...]) -> bool: """Return whether two fills' row descriptions name the same models and absences. - Parameters - ---------- - first, second : tuple - :meth:`~solprop.modes.fill.GasFillMixin._fill_rows` of each fill. - - Returns - ------- - bool - True when the models are the same objects, in order, with equal absences. + - Returns: True when the models are the same objects, in order, with equal absences. """ if len(first) != len(second): return False @@ -1602,20 +1519,7 @@ def _checked_omega( ) -> Float[np.ndarray, " n_omega"]: """Validate an angular-frequency argument for a capillary mode. - 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 or not strictly positive. + - ``omega``: angular frequencies in rad/s, shape ``(n_omega,)``. """ values = np.asarray(omega, dtype=float) if not np.all(np.isfinite(values)): @@ -1827,14 +1731,7 @@ def __post_init__(self) -> None: self._check_method() def _check_method(self) -> None: - """Check the varying-section method and the two declarations only CF4 reads. - - Raises - ------ - ModeError - If the method is unknown, or if a growth limit or a fallback is declared - without CF4 or out of range. - """ + """Check the varying-section method and the two declarations only CF4 reads.""" if self.method not in ("frozen_residual", "cf4"): msg = ( "CoherentWall.method is 'frozen_residual' or 'cf4' for a guide that " @@ -1896,10 +1793,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 "CoherentWall: the condensed Robin wall of a capillary block", [ ("wall impedance", self.impedance), @@ -1911,26 +1805,14 @@ def _describe(self) -> tuple[str, list[tuple[str, object]]]: ] def _model_text(self) -> str: - """Return the printed one-line model the declaration builds. - - Returns - ------- - str - The wall impedance, the paraxial generator and the flow it takes. - """ + """Return the printed one-line model the declaration builds.""" wall = "TE/TM impedances" if self.impedance == "te_tm" else "mean impedance" if self.method == "cf4": return f"{wall}, paraxial, commutator-free per section" return f"{wall}, paraxial, frozen per section" def _method_text(self) -> str: - """Return the printed varying-section method with the CF4 guards it declares. - - Returns - ------- - str - For example ``cf4, growth limit 2 (default), fallback frozen_residual``. - """ + """Return the printed varying-section method with the CF4 guards it declares.""" if self.method != "cf4": return self.method limit = "default" if self.growth_limit is None else f"{self.growth_limit:g}" @@ -2050,10 +1932,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 "WallLimits: where the condensed paraxial wall model stops being one", [ ("channels", self.n_modes), @@ -2162,10 +2041,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. """ band = self.generator[:, :, self.active] loss = -np.real(np.einsum("mmw->mw", band)) if band.size else np.zeros((1, 1)) @@ -2367,10 +2243,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. """ band = self.generator[:, :, self.active] loss = -np.real(np.einsum("mmw->mw", band)) if band.size else np.zeros((1, 1)) @@ -2416,29 +2289,6 @@ def _wall_block_modes( And the condensation inverts the **full** model's transverse eigenvalue, so ``model="reduced"``, whose leading-order expansion of ``n_eff`` is a different carrier, would be overridden by the block it sits beside. - - Parameters - ---------- - modes : sequence of Mode - The mode set of the run, in the state's channel order. It is typed as the - protocol rather than as :class:`MarcatiliMode` so that a set carrying another - kind of mode reaches the refusal below and is named, instead of failing the - runtime type check the test session installs. - wall : bool, optional - Whether the block is a coherent wall's. The **taper coupling** alone - (:func:`taper_connection`) reads the radial orders and the radius, never the - loss or the capillary model, so ``False`` admits any loss and either model - and keeps every other condition. - - Returns - ------- - tuple of MarcatiliMode - The same modes, as a tuple. - - Raises - ------ - ModeError - Naming what makes the set incompatible. """ block = tuple(modes) if not block: @@ -2485,15 +2335,7 @@ def _wall_channel_settings(mode: MarcatiliMode) -> tuple[tuple[str, object], ... and two tapers that agree at the entrance are two different guides everywhere else. - Parameters - ---------- - mode : MarcatiliMode - One channel of the block. - - Returns - ------- - tuple - ``(name, value)`` pairs, comparable and printable. + - Returns: ``(name, value)`` pairs, comparable and printable. """ return ( ("phi", mode.phi), @@ -2511,20 +2353,12 @@ def _wall_channel_settings(mode: MarcatiliMode) -> tuple[tuple[str, object], ... def _profile_key(profile: ZProfile) -> str: """Return a comparable, printable identity of one declared z profile. - Parameters - ---------- - profile : solprop.profiles.ZProfile - The declared path. - - Returns - ------- - str - The sorted identity mapping of :func:`solprop.profiles.profile_identity`, - rendered so that two paths compare equal exactly when the identity does. A - path that has no structural identity -- an arbitrary callable -- is keyed on - the object itself, so two channels sharing one declared path agree and two - that were handed separate callables are refused, which is the conservative - answer for something this package cannot inspect. + - Returns: the sorted identity mapping of :func:`solprop.profiles.profile_identity`, + rendered so that two paths compare equal exactly when the identity does. A path + that has no structural identity -- an arbitrary callable -- is keyed on the object + itself, so two channels sharing one declared path agree and two that were handed + separate callables are refused, which is the conservative answer for something + this package cannot inspect. """ try: identity = profile_identity(profile) @@ -2538,20 +2372,7 @@ def _check_wall_channels( ) -> None: """Refuse a block whose channels do not declare one guide, by name. - Parameters - ---------- - block : tuple of MarcatiliMode - The contiguous ``HE_11`` to ``HE_1N`` set, in channel order. - wall : bool, optional - Whether the block is a coherent wall's, which is the loss model and needs the - full capillary model. ``False`` -- the taper coupling alone -- skips those two - conditions and names the taper coupling in the others. - - Raises - ------ - ModeError - Naming the channel and the loss or capillary model the block would override, - the channel whose section varies, or the setting two channels disagree on. + - ``block``: the contiguous ``HE_11`` to ``HE_1N`` set, in channel order. """ owner = "a coherent wall block" if wall else "the taper coupling" for index, mode in enumerate(block): @@ -2630,23 +2451,13 @@ def _check_wall_guide( middle of the band and differs between them is not a thing this package can build. - Parameters - ---------- - block : tuple of MarcatiliMode - The contiguous ``HE_11`` to ``HE_1N`` set, in channel order. - band : numpy.ndarray - The active frequencies in rad/s, ascending, non-empty. - probe : str or None, optional - Which family's cladding term to compare the channels through. ``None`` - compares each channel's own, which is right when they share one family. A - **vector** block does not: its families are the point of it, and their - cladding terms differ by construction, so it compares every channel through - one named kind, which is the same permittivity read the same way. - - Raises - ------ - ModeError - Naming the channel whose cladding or fill is not the lowest channel's. + - ``block``: the contiguous ``HE_11`` to ``HE_1N`` set, in channel order. + - ``band``: the active frequencies in rad/s, ascending, non-empty. + - ``probe``: which family's cladding term to compare the channels through. ``None`` + compares each channel's own, which is right when they share one family. A + **vector** block does not: its families are the point of it, and their cladding + terms differ by construction, so it compares every channel through one named kind, + which is the same permittivity read the same way. """ if len(block) == 1: return @@ -2682,17 +2493,7 @@ def _core_susceptibility( ) -> Complex[np.ndarray, " n_omega"]: """Return the core susceptibility of a fill at one position. - Parameters - ---------- - terms : MarcatiliTerms - The sampled frequency-dependent quantities. - density : float or numpy.ndarray - Number density in m^-3, scalar or one entry per sampled species. - - Returns - ------- - numpy.ndarray - ``chi(omega)``, complex, shape ``(n_omega,)``. + - ``density``: number density in m^-3, scalar or one entry per sampled species. """ if terms.core_susceptibility.ndim == 1: return np.asarray(terms.core_susceptibility * density, dtype=complex) @@ -2860,20 +2661,7 @@ def _vector_wall_families( must be the contiguous ``m = 1 .. N_f`` set the condensation truncates. Everything else is refused by name. - Parameters - ---------- - block : tuple of MarcatiliMode - The channels in the state's order. - - Returns - ------- - tuple - The order ``nu`` and its families, in channel order. - - Raises - ------ - ModeError - Naming what makes the set more or less than one azimuthal order. + - Returns: the order ``nu`` and its families, in channel order. """ runs: list[list[MarcatiliMode]] = [] for mode in block: @@ -2953,20 +2741,7 @@ def _vector_wall_block( ) -> tuple[int, tuple[WallFamily, ...], tuple[MarcatiliMode, ...]]: """Check a mode set is one azimuthal order of a bare capillary, and split it. - Parameters - ---------- - modes : sequence of Mode - The mode set of the run, in the state's channel order. - - Returns - ------- - tuple - The azimuthal order, its families and the same modes as a tuple. - - Raises - ------ - ModeError - Naming what makes the set incompatible. + - Returns: the azimuthal order, its families and the same modes as a tuple. """ block = tuple(modes) if not block: @@ -3000,17 +2775,9 @@ def _vector_impedances( deliberate: a constant, tabulated or mixed cladding then reaches the wall model by exactly the path any TE or TM channel of this guide would take. - Parameters - ---------- - mode : MarcatiliMode - The channel the block reads its guide from. - band : numpy.ndarray - The active frequencies in rad/s, ascending, strictly positive. - - Returns - ------- - numpy.ndarray - The radial and tangential impedances in m, complex, shape ``(2, n_active)``. + - ``band``: the active frequencies in rad/s, ascending, strictly positive. + - Returns: the radial and tangential impedances in m, complex, shape ``(2, + n_active)``. """ vacuum = band / C return np.stack( @@ -3423,17 +3190,8 @@ def _scalar_connection( ) -> Float[np.ndarray, "n_modes_scalar_out n_modes_scalar_in"]: """Return the connection of a checked ``HE_1m`` block at one position. - Parameters - ---------- - block : tuple of MarcatiliMode - The checked block. - z : float - Position in m. - - Returns - ------- - numpy.ndarray - ``C`` in 1/m. + - ``z``: position in m. + - Returns: ``C`` in 1/m. """ first = block[0] radius = float(first.core_radius(np.array([float(z)]))[0]) @@ -3447,19 +3205,9 @@ def _bessel_connection( ) -> Float[np.ndarray, "n_family_out n_family_in"]: """Return the connection of one radial family of Bessel order `order`. - Parameters - ---------- - zeros : numpy.ndarray - The family's retained zeros ``u_m`` of ``J_order``, ascending. - order : int - The family's Bessel order ``p``, ``>= 0``. - rate : float - ``a'/a`` in 1/m. - - Returns - ------- - numpy.ndarray - ``C`` of the family in 1/m, real, antisymmetric, with a zero diagonal. + - ``order``: the family's Bessel order ``p``, ``>= 0``. + - ``rate``: ``a'/a`` in 1/m. + - Returns: ``C`` of the family in 1/m, real, antisymmetric, with a zero diagonal. """ count = zeros.size signs = np.sign(jv(order + 1, zeros)) @@ -3575,26 +3323,13 @@ def _judged_bins( ) -> Bool[np.ndarray, " n_band"]: """Return which evaluated bins a wall block's validity ratios are taken over. - Parameters - ---------- - mask : numpy.ndarray - The evaluated bins, bool, shape ``(n_omega,)``, at least one true. - scored : numpy.ndarray or None - The scored bins, bool, shape ``(n_omega,)``; ``None`` takes every evaluated - bin. - - Returns - ------- - numpy.ndarray - Bool over the evaluated bins alone, at least one true. A scored mask that - holds **no** evaluated bin -- requested limits narrower than one bin, which - a monochromatic study grid can declare -- takes every evaluated bin, the - conservative choice: it never admits a block the whole band would refuse. - - Raises - ------ - ModeError - If `scored` does not match the frequency axis. + - ``mask``: the evaluated bins, bool, shape ``(n_omega,)``, at least one true. + - ``scored``: the scored bins, bool, shape ``(n_omega,)``; ``None`` takes every + evaluated bin. + - Returns: bool over the evaluated bins alone, at least one true. A scored mask that + holds **no** evaluated bin -- requested limits narrower than one bin, which a + monochromatic study grid can declare -- takes every evaluated bin, the + conservative choice: it never admits a block the whole band would refuse. """ if scored is None: return np.ones(int(np.count_nonzero(mask)), dtype=bool) @@ -3616,19 +3351,9 @@ def _scattered( ) -> np.ndarray: """Return active-band values placed back on the whole frequency axis. - Parameters - ---------- - values : numpy.ndarray - The values on the active bins, with the frequency axis last. - mask : numpy.ndarray - Which bins those are, bool, shape ``(n_omega,)``. - leading : tuple of int - The shape in front of the frequency axis. - - Returns - ------- - numpy.ndarray - Complex, shape ``(*leading, n_omega)``, exactly zero off the mask. + - ``values``: the values on the active bins, with the frequency axis last. + - ``leading``: the shape in front of the frequency axis. + - Returns: complex, shape ``(*leading, n_omega)``, exactly zero off the mask. """ out = np.zeros((*leading, len(mask)), dtype=complex) out[..., mask] = values @@ -3663,21 +3388,8 @@ def _check_wall_passivity( against a sign slipped into this arithmetic, not against a cladding a caller can pass today. - Parameters - ---------- - generator : numpy.ndarray - The optics generator on the active band, shape ``(n_modes, n_modes, n_omega)``. - wavenumber : numpy.ndarray - The in-core wavenumber on the active band, 1/m. - radius : float - Core radius in m. - count : int - Channel count, used in the refusal. - - Raises - ------ - ModeError - If any Hermitian eigenvalue is positive beyond the rounding of the block. + - ``wavenumber``: the in-core wavenumber on the active band, 1/m. + - ``radius``: core radius in m. """ moved = np.moveaxis(generator, (0, 1), (-2, -1)) hermitian = 0.5 * (moved + np.conj(np.swapaxes(moved, -1, -2))) @@ -3697,25 +3409,7 @@ def _check_wall_passivity( def _check_wall_limits(limits: WallLimits, validity_limit: float) -> None: - """Refuse or warn on the validity ratios of a wall block, by name. - - Parameters - ---------- - limits : WallLimits - The measured ratios. - validity_limit : float - Largest admitted ratio. - - Raises - ------ - ModeError - If the worst ratio exceeds `validity_limit`. - - Warns - ----- - GrazingIncidenceWarning - If it exceeds a tenth of `validity_limit`. - """ + """Refuse or warn on the validity ratios of a wall block, by name.""" name, value = limits.named() if value > validity_limit: msg = ( diff --git a/src/solprop/modes/collection.py b/src/solprop/modes/collection.py index 8c0f0898..81d2dee5 100644 --- a/src/solprop/modes/collection.py +++ b/src/solprop/modes/collection.py @@ -76,22 +76,8 @@ def _normalisation(mode: Mode, z: float) -> float: """Return a mode's closed-form normalisation. - Parameters - ---------- - mode : Mode - The mode. - z : float - Position along the waveguide, m. - - Returns - ------- - float - ``N`` in W. - - Raises - ------ - ModeError - If the mode has no closed-form normalisation. + - ``z``: position along the waveguide, m. + - Returns: ``N`` in W. """ closed_form: Callable[[float], float] | None = getattr(mode, "normalisation", None) if not callable(closed_form): @@ -144,21 +130,12 @@ def overlap(first: Mode, second: Mode, z: float = 0.0) -> float: 0.5 * EPS0 * C / math.sqrt(_normalisation(first, z) * _normalisation(second, z)) ) - def integrand(inner: float, outer: float) -> float: + def _integrand(inner: float, outer: float) -> float: """Return the product of the two profiles times the area element. - Parameters - ---------- - inner : float - The first cross-section coordinate, ``r`` or ``x`` in m. dblquad passes - the *inner* variable first, which is why the order looks reversed. - outer : float - The second coordinate, ``theta`` in rad or ``y`` in m. - - Returns - ------- - float - The integrand of the overlap. + - ``inner``: the first cross-section coordinate, ``r`` or ``x`` in m. dblquad + passes the *inner* variable first, which is why the order looks reversed. + - ``outer``: the second coordinate, ``theta`` in rad or ``y`` in m. """ point = np.array([[inner, outer]]) product = float(np.sum(first.field(point, z) * second.field(point, z))) @@ -169,7 +146,7 @@ def integrand(inner: float, outer: float) -> float: return scale * _overlap_along_x(first, second, (lower[0], upper[0]), z) # The outer variable is the second coordinate and the inner the first, with the # polar Jacobian r folded into the integrand above. - value, _ = dblquad(integrand, lower[1], upper[1], lower[0], upper[0]) + value, _ = dblquad(_integrand, lower[1], upper[1], lower[0], upper[0]) return scale * value @@ -178,33 +155,15 @@ def _overlap_along_x( ) -> float: """Return the integral over x of two slab profiles' product, per metre of y. - Parameters - ---------- - first, second : Mode - Slab modes sharing one gap. - limits : tuple of float - The gap's edges in m. - z : float - Position along the waveguide, m. - - Returns - ------- - float - ``int e_1 . e_2 dx`` in m, integrated adaptively. + - ``limits``: the gap's edges in m. + - ``z``: position along the waveguide, m. + - Returns: ``int e_1 . e_2 dx`` in m, integrated adaptively. """ def along_x(x: float) -> float: """Return the product of the two slab profiles at one x. - Parameters - ---------- - x : float - Transverse coordinate in m. - - Returns - ------- - float - The integrand of the per-unit-width overlap. + - ``x``: transverse coordinate in m. """ point = np.array([[x, 0.0]]) return float(np.sum(first.field(point, z) * second.field(point, z))) @@ -310,15 +269,8 @@ def _measure_of(mode: Mode) -> TransverseMeasure: specified mode keeps its source in its dispersion's ``mode``, and its area -- ``aeff()`` of that source unless one was declared -- is in the source's units. - Parameters - ---------- - mode : Mode - The mode. - - Returns - ------- - str - The first declared ``transverse_measure`` along the chain, else ``"area"``. + - Returns: the first declared ``transverse_measure`` along the chain, else + ``"area"``. """ one: object = mode while one is not None: @@ -425,18 +377,7 @@ def __post_init__(self) -> None: ) def _same_stepindex_guide(self, mode: Mode) -> bool: - """Check whether a channel shares the first physical guide. - - Parameters - ---------- - mode : StepIndexMode - Existing bound lossless mode. - - Returns - ------- - bool - Whether radius paths and both material laws agree. - """ + """Check whether a channel shares the first physical guide.""" first = self.modes[0] return ( isinstance(first, StepIndexMode) @@ -863,10 +804,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. """ count = len(self.modes) return f"ModeCollection: {count} mode{'s' * (count != 1)}", [ diff --git a/src/solprop/modes/connection.py b/src/solprop/modes/connection.py index be241ab4..2849a335 100644 --- a/src/solprop/modes/connection.py +++ b/src/solprop/modes/connection.py @@ -270,10 +270,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 "ModalConnection: the cost of a basis that moves along z", [ ("channels", self.n_modes), @@ -495,22 +492,7 @@ def _panel_identity( ) -> tuple[float, tuple[float, ...], int, int]: """Return the truncation radius, panel edges and resolution a rule declares. - Parameters - ---------- - provider : solprop.modes.vector.ModalProvider - The guide, named in the refusal. - identity : collections.abc.Mapping - One rule's own identity. - - Returns - ------- - tuple - The outer radius in m, its panel edges in m, and the two node counts. - - Raises - ------ - solprop.exceptions.ModeError - If the rule does not declare all four. + - Returns: the outer radius in m, its panel edges in m, and the two node counts. """ scalars = ("outer_radius", "n_r", "n_theta") missing = [ @@ -535,17 +517,8 @@ def _panel_identity( def _interfaces(provider: ModalProvider, z: float) -> tuple[float, ...]: """Return a provider's declared material radii at one position, increasing. - Parameters - ---------- - provider : solprop.modes.vector.ModalProvider - The guide. - z : float - Position in m. - - Returns - ------- - tuple of float - The distinct positive finite radii in m, increasing. + - ``z``: position in m. + - Returns: the distinct positive finite radii in m, increasing. """ values: set[float] = set() for region in provider.regions: @@ -767,26 +740,10 @@ def _measured_connection( ) -> ModalConnection: """Measure and certify the connection at one frequency on a built stencil. - Parameters - ---------- - provider : solprop.modes.vector.ModalProvider - The guide. - omega : float - Positive angular frequency in rad/s. - z : float - Position in m. - stencil : tuple - The three positions in m, the step in m and one rule per position, from - :func:`_stencil` and :func:`connection_nodes`. - degenerate_blocks : sequence of sequence of int - Channel blocks the tracking may rotate inside. - basis : solprop.modes.guide.ChannelBasis or None - A declared constant unitary change of the channel coordinates. - - Returns - ------- - ModalConnection - The connection, the metric, its measured rate and the certificate. + - ``omega``: positive angular frequency in rad/s. + - ``z``: position in m. + - ``stencil``: the three positions in m, the step in m and one rule per position, + from :func:`_stencil` and :func:`connection_nodes`. """ # Imported here: solprop.translate imports solprop.modes, so a module-level # import of the tracking would close a cycle through the translate package body. @@ -882,20 +839,9 @@ def _flux_pairing( ``X(A, B)_mn = int (A_n x conj(B_m)).zhat dA``. Taking the Gram with the same two einsums is what makes ``S + S^H`` the derivative of the Gram itself. - Parameters - ---------- - electric, magnetic : numpy.ndarray - The derivatives ``dE/dz`` and ``dH/dz`` in V/(m sqrt(W) m) and - A/(m sqrt(W) m), shape ``(n_nodes, 3, n_modes)``. - sample : solprop.modes.vector.ModalSample - The centre sample, whose fields fill the left-hand slot. - weights : numpy.ndarray - The shared area weights in m², shape ``(n_nodes,)``. - - Returns - ------- - numpy.ndarray - ``S`` in 1/m, shape ``(n_modes, n_modes)``. + - ``electric``, ``magnetic``: the derivatives ``dE/dz`` and ``dH/dz`` in V/(m + sqrt(W) m) and A/(m sqrt(W) m), shape ``(n_nodes, 3, n_modes)``. + - Returns: ``S`` in 1/m, shape ``(n_modes, n_modes)``. """ field, magnetics = sample.electric, sample.magnetic left = np.einsum( @@ -912,24 +858,10 @@ def _stencil( ) -> tuple[tuple[float, float, float], float]: """Return three positions the provider can be solved at, and the step used. - Parameters - ---------- - provider : solprop.modes.vector.ModalProvider - The guide, whose z domain and breakpoints bound the stencil. - z : float - The position in m. - step : float or None - The declared step in m, or ``None`` for :data:`STEP_FRACTION` of the scale. - - Returns - ------- - tuple - The three increasing positions in m and the step in m. - - Raises - ------ - solprop.exceptions.ModeError - If `step` is out of range or no stencil of it fits inside the z domain. + - ``z``: the position in m. + - ``step``: the declared step in m, or ``None`` for :data:`STEP_FRACTION` of the + scale. + - Returns: the three increasing positions in m and the step in m. """ low, high = (float(one) for one in provider.z_domain) # The scale is the interval `z` lies in, not the whole domain: a derivative @@ -969,17 +901,9 @@ def _lagrange_slope(positions: Sequence[float], z: float) -> tuple[float, float, Exact for a quadratic through `positions`, so a shifted stencil carries the same order as a centred one and a centred one reduces to ``(-1, 0, 1)/(2h)``. - Parameters - ---------- - positions : sequence of float - Three distinct increasing positions in m. - z : float - Where the derivative is taken, m. - - Returns - ------- - tuple of float - The weights in 1/m, summing to zero. + - ``positions``: three distinct increasing positions in m. + - ``z``: where the derivative is taken, m. + - Returns: the weights in 1/m, summing to zero. """ first, second, third = (float(one) for one in positions) return ( diff --git a/src/solprop/modes/fill.py b/src/solprop/modes/fill.py index 1b88969e..489e4369 100644 --- a/src/solprop/modes/fill.py +++ b/src/solprop/modes/fill.py @@ -78,17 +78,6 @@ def _sqrt1p_minus_one( ``log1p`` forms ``1 + x`` and rounds there (1.3e-12 relative at ``1e-4 + 1e-7j`` on NumPy 2.5). Every gas-filled guide forms its refractivity here. - - Parameters - ---------- - argument : numpy.ndarray - ``n_eff**2 - 1``, real or complex, shape ``(n_omega,)``. - - Returns - ------- - numpy.ndarray - ``sqrt(1 + argument) - 1``, complex, shape ``(n_omega,)``, on the principal - branch. """ value = np.asarray(argument, dtype=complex) return np.asarray(value / (np.sqrt(1.0 + value) + 1.0)) @@ -118,23 +107,14 @@ class GasFillMixin(Displayed): _gain_warned: bool def _loss_text(self) -> object: - """Return the loss specification for printing. - - Returns - ------- - object - The flag or the factor as it was given, or ``"callable"`` for a loss curve. - """ + """Return the loss specification for printing.""" return self.loss if isinstance(self.loss, (bool, int, float)) else "callable" def _fill_text(self) -> str: """Return the fill as a person would say it, for printing. - Returns - ------- - str - ``"evacuated"``, ``"He 1 bar"`` or, for a mixture, its labels and its - shared pressure; a gradient is quoted at ``z = 0``. + - Returns: ``"evacuated"``, ``"He 1 bar"`` or, for a mixture, its labels and its + shared pressure; a gradient is quoted at ``z = 0``. """ entrance = np.array([0.0]) if self.mixture is not None: @@ -148,13 +128,7 @@ def _fill_text(self) -> str: return f"{self.gas} {float(as_profile(self.pressure)(entrance)[0]):g} bar" def _check_fill_arguments(self) -> None: - """Check the fill declaration and loss specification. - - Raises - ------ - ModeError - If temperature, loss or a competing mixture declaration is invalid. - """ + """Check the fill declaration and loss specification.""" if self.temperature is None: # Not given: room temperature for a gas, the mixture's own beside one. temperature = ( @@ -317,16 +291,7 @@ def absorption_by_species( return np.zeros((1, omega.size)) def _fill_paths(self) -> Mapping[str, ZProfile]: - """Return the fill's scalar paths, including constant declarations. - - Returns - ------- - collections.abc.Mapping - Density, shared pressure or independently labelled density paths. A - shared fill whose temperature varies along z is declared by its labelled - densities: one pressure does not fix them, so it takes - one parameter per population. - """ + """Return the fill's scalar paths, including constant declarations.""" paths: dict[str, ZProfile] = {} if self.mixture is None or len(self.mixture.components) == 1: paths["density"] = self.density @@ -343,20 +308,8 @@ def _fill_paths(self) -> Mapping[str, ZProfile]: def _frozen_fill(self, values: Mapping[str, float]) -> "GasFillMixin": """Copy the mode with its named material paths frozen. - Parameters - ---------- - values : collections.abc.Mapping - Fill parameter values in SI density or shared pressure in bar. - - Returns - ------- - GasFillMixin - A shallow copy sharing the frequency-only model quantities. - - Raises - ------ - ModeError - If a supplied density is negative. + - ``values``: fill parameter values in SI density or shared pressure in bar. + - Returns: a shallow copy sharing the frequency-only model quantities. """ frozen = copy.copy(self) if "density" in values: @@ -389,22 +342,9 @@ def _sampled_susceptibility( ) -> tuple[Inexact[np.ndarray, "*shape"], Inexact[np.ndarray, "*shape"]]: """Sample the material susceptibility and its frequency derivative. - Parameters - ---------- - omega : numpy.ndarray - Positive angular frequencies in rad/s, already checked by the mode. - - Returns - ------- - tuple of numpy.ndarray - Susceptibility in m³ and its derivative in m³ s/rad, one spectral row - for a legacy gas and one row per population for an explicit mixture. - - Warns - ----- - DiluteScalingWarning - Once per gas, where an optmat-selected population's linear density - scaling departs from optmat's finite-density law beyond its bound. + - ``omega``: positive angular frequencies in rad/s, already checked by the mode. + - Returns: susceptibility in m³ and its derivative in m³ s/rad, one spectral row + for a legacy gas and one row per population for an explicit mixture. """ # One evaluation context: each material's range warning is raised once for # the susceptibility and its derivative together. @@ -418,16 +358,9 @@ def _dilute_checks( ) -> tuple[DiluteScalingCheck, ...]: """Measure the dilute-scaling error of the fill's optmat-selected gases. - Parameters - ---------- - omega : numpy.ndarray - Positive angular frequencies in rad/s of the sampled band. - - Returns - ------- - tuple of DiluteScalingCheck - One per optmat population (or the optmat law of a single gas) with a - non-zero density, at its largest density along the fill. + - ``omega``: positive angular frequencies in rad/s of the sampled band. + - Returns: one per optmat population (or the optmat law of a single gas) with a + non-zero density, at its largest density along the fill. """ if self.mixture is not None: return self.mixture.dilute_scaling_checks(omega) @@ -464,15 +397,7 @@ def _sample_fill( ) -> tuple[Inexact[np.ndarray, "*shape"], Inexact[np.ndarray, "*shape"]]: """Sample the fill's susceptibility rows and their derivatives. - Parameters - ---------- - omega : numpy.ndarray - Positive angular frequencies in rad/s, already checked by the mode. - - Returns - ------- - tuple of numpy.ndarray - As :meth:`_sampled_susceptibility`. + - ``omega``: positive angular frequencies in rad/s, already checked by the mode. """ values = omega if self.mixture is not None: @@ -506,14 +431,7 @@ def _sample_fill( return susceptibility, dsusceptibility def _absent_components(self) -> tuple[bool, ...]: - """Return which mixture populations are identically absent. - - Returns - ------- - tuple of bool - Per component of the mixture, whether its density is the constant zero. - Only a fill with a mixture asks. - """ + """Return which mixture populations are identically absent.""" assert self.mixture is not None return tuple( c.density.is_constant and float(c.density(np.array([0.0]))[0]) == 0.0 @@ -526,11 +444,8 @@ def _fill_rows(self) -> tuple[object, ...]: Two fills with equal results here sample the same rows: the same models, in the same order, with the same populations replaced by zeros (:meth:`_sample_fill`). - Returns - ------- - tuple - The susceptibility model of a fill without a mixture; otherwise each - component's model and whether it is absent. + - Returns: the susceptibility model of a fill without a mixture; otherwise each + component's model and whether it is absent. """ if self.mixture is None: return (self.susceptibility,) @@ -550,19 +465,8 @@ def _apply_loss( ) -> tuple[Complex[np.ndarray, " n_omega"], Complex[np.ndarray, " n_omega"]]: """Apply the loss specification to a model refractivity and its derivative. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s. - refractivity : numpy.ndarray - ``n_eff - 1`` with the model's own loss. - derivative : numpy.ndarray - ``dn_eff/domega`` with the model's own loss. - - Returns - ------- - tuple of numpy.ndarray - The refractivity and derivative with the requested loss. + - ``omega``: angular frequencies in rad/s. + - Returns: the refractivity and derivative with the requested loss. """ specification = self.loss if isinstance(specification, bool): @@ -603,17 +507,7 @@ def _no_gain( It is clamped here on every path, the derivative is zeroed where the clamp binds, and the mode logs the fact once. - Parameters - ---------- - refractivity : numpy.ndarray - ``n_eff - 1`` with the model's loss. - derivative : numpy.ndarray - ``dn_eff/domega`` with the model's loss. - - Returns - ------- - tuple of numpy.ndarray - The same, with a non-negative imaginary part. + - Returns: the same, with a non-negative imaginary part. """ gain = np.imag(refractivity) < 0.0 if not np.any(gain): @@ -640,19 +534,11 @@ def _loss_derivative( ) -> Float[np.ndarray, " n_omega"]: """Return the frequency derivative of a loss curve supplied as a callable. - Parameters - ---------- - curve : callable - The loss curve ``alpha(omega)`` in 1/m. - omega : numpy.ndarray - Angular frequencies in rad/s. - - Returns - ------- - numpy.ndarray - ``d(alpha)/d(omega)`` in 1/(m rad/s), analytic when the callable carries a - ``derivative`` attribute and otherwise a Richardson-extrapolated central - difference with a relative floor near 1e-10. + - ``curve``: the loss curve ``alpha(omega)`` in 1/m. + - ``omega``: angular frequencies in rad/s. + - Returns: ``d(alpha)/d(omega)`` in 1/(m rad/s), analytic when the callable + carries a ``derivative`` attribute and otherwise a Richardson-extrapolated + central difference with a relative floor near 1e-10. """ analytic = getattr(curve, "derivative", None) if callable(analytic): @@ -674,18 +560,8 @@ def _floored( ) -> tuple[Complex[np.ndarray, " n_omega"], Complex[np.ndarray, " n_omega"]]: """Apply the full model's evanescent floor to a refractivity and derivative. - Parameters - ---------- - refractivity : numpy.ndarray - ``n_eff - 1``. - derivative : numpy.ndarray - ``dn_eff/domega``. - - Returns - ------- - tuple of numpy.ndarray - The same, with ``Re n_eff`` floored at :data:`EVANESCENT_INDEX_FLOOR` and - the derivative set to zero where the floor binds. + - Returns: the same, with ``Re n_eff`` floored at :data:`EVANESCENT_INDEX_FLOOR` + and the derivative set to zero where the floor binds. """ if self.model != "full": return refractivity, derivative diff --git a/src/solprop/modes/graded_index.py b/src/solprop/modes/graded_index.py index 3ddd3c8f..9ffee472 100644 --- a/src/solprop/modes/graded_index.py +++ b/src/solprop/modes/graded_index.py @@ -310,10 +310,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 "GradedIndexProvider: radial P1 elements, exact Bessel exterior", [ ("sector", f"{self.family}{self.azimuthal_order}"), @@ -360,15 +357,8 @@ def __post_init__(self) -> None: def _radius(self, z: float) -> float: """Evaluate and validate the declared radius path. - Parameters - ---------- - z : float - Position in m within the declared path. - - Returns - ------- - float - Positive core radius in m. + - ``z``: position in m within the declared path. + - Returns: positive core radius in m. """ path = as_profile(self.radius) value = float(path(np.array([z], dtype=float))[0]) @@ -554,23 +544,7 @@ def _eps( ) -> Float[np.ndarray, " n_points"]: """Evaluate the lossless radial permittivity on both materials. - Parameters - ---------- - omega : float - Positive angular frequency in rad/s. - radial_fraction : numpy.ndarray - Dimensionless radii divided by the core radius, shape (n_points,); - anything above one takes the exterior value. - - Returns - ------- - numpy.ndarray - Relative permittivity, shape (n_points,). - - Raises - ------ - ModeError - If a permittivity is lossy, non-finite or not positive. + - ``omega``: positive angular frequency in rad/s. """ core = self.permittivity.permittivity(omega, np.minimum(radial_fraction, 1.0)) exterior = self.exterior.permittivity(np.array([omega], dtype=float))[0] @@ -586,16 +560,7 @@ def _eps( def _weak_guidance_contrast(self, omega: float) -> float: """Return the axis-to-exterior relative permittivity contrast. - Parameters - ---------- - omega : float - Positive angular frequency in rad/s. - - Returns - ------- - float - Dimensionless ``eps_axis / eps_exterior - 1``, the quantity the LP - weak-guidance approximation is declared valid over. + - ``omega``: positive angular frequency in rad/s. """ axis = float(self._eps(omega, np.zeros(1))[0]) exterior = float(self._eps(omega, np.full(1, 2.0))[0]) @@ -605,23 +570,9 @@ def _weak_guidance_contrast(self, omega: float) -> float: def _solution(self, omega: float, z: float) -> _RadialSolution: """Solve the variational eigenproblem with an exact exterior. - Parameters - ---------- - omega : float - Positive angular frequency in rad/s. - z : float - Position in m within the declared path. - - Returns - ------- - _RadialSolution - Dimensionless radial mesh, profiles, beta and interface defects. - - Raises - ------ - ModeError - If the frequency, the LP contrast or a requested channel's binding - is outside the provider's declared validity. + - ``omega``: positive angular frequency in rad/s. + - ``z``: position in m within the declared path. + - Returns: dimensionless radial mesh, profiles, beta and interface defects. """ radius = self._radius(z) if ( @@ -733,23 +684,7 @@ def _trace_defect( ) -> Float[np.ndarray, " n_modes"]: """Return the one-sided derivative-trace defect at the material interface. - Parameters - ---------- - nodes : numpy.ndarray - Dimensionless radial mesh, shape (n_radial_nodes,). - profiles : numpy.ndarray - Nodal eigenfunctions, shape (n_radial_profiles, n_modes_profiles). - omega : float - Positive angular frequency in rad/s. - exterior : float - Exterior relative permittivity, dimensionless. - - Returns - ------- - numpy.ndarray - Scaled defect of the continuous quantity of the sector, shape - (n_modes,): the radial gradient for TE/TM and its inverse-permittivity - weighting for TM, the plain slope for LP. + - ``omega``: positive angular frequency in rad/s. """ interface = int(np.searchsorted(nodes, 1.0)) left = (profiles[interface] - profiles[interface - 1]) / ( @@ -771,20 +706,8 @@ def _require_bound_channels( ) -> None: """Refuse a channel the declared finite domain cannot carry. - Parameters - ---------- - solution : _RadialSolution - Solved radial eigenfunctions and propagation constants. - omega : float - Positive angular frequency in rad/s. - radius : float - Core radius in m. - - Raises - ------ - ModeError - If a channel keeps more than :data:`MAX_EXTERIOR_TAIL_FRACTION` of its - power beyond the matching radius. + - ``omega``: positive angular frequency in rad/s. + - ``radius``: core radius in m. """ # Above cutoff is not the same as bound on this domain: a channel whose # exterior decay is slow puts its power in the analytic continuation, not @@ -812,20 +735,10 @@ def _matrices( ]: """Assemble the radial weak mass and Helmholtz operators. - Parameters - ---------- - omega : float - Positive angular frequency in rad/s. - nodes : numpy.ndarray - Explicit dimensionless radial mesh, shape (n_radial_nodes,), - increasing from the axis to the matching radius. - wave_number : float - Vacuum wave number multiplied by core radius, dimensionless. - - Returns - ------- - tuple of numpy.ndarray - Dimensionless mass and operator matrices on the radial nodes. + - ``omega``: positive angular frequency in rad/s. + - ``nodes``: explicit dimensionless radial mesh, shape (n_radial_nodes,), + increasing from the axis to the matching radius. + - Returns: dimensionless mass and operator matrices on the radial nodes. """ mass = np.zeros((nodes.size, nodes.size)) operator = np.zeros_like(mass) @@ -1104,19 +1017,9 @@ def _norms( ) -> Float[np.ndarray, " n_modes"]: """Integrate full-domain power independently of requested field nodes. - Parameters - ---------- - solution : _RadialSolution - Solved radial eigenfunctions and propagation constants. - omega : float - Positive angular frequency in rad/s. - radius : float - Core radius in m. - - Returns - ------- - numpy.ndarray - Positive power norms in W, shape (n_modes,). + - ``omega``: positive angular frequency in rad/s. + - ``radius``: core radius in m. + - Returns: positive power norms in W, shape (n_modes,). """ key = (omega, radius) if key in solution.norms: @@ -1177,21 +1080,8 @@ def _tail_integral( ) -> Float[np.ndarray, " n_modes"]: """Integrate the squared decaying radial profile exactly. - Parameters - ---------- - solution : _RadialSolution - Solved radial eigenfunctions and propagation constants. - omega : float - Positive angular frequency in rad/s. - radius : float - Core radius in m. - lower : float - Lower integration radius divided by core radius. - - Returns - ------- - numpy.ndarray - Dimensionless radial integrals, shape (n_modes,). + - ``omega``: positive angular frequency in rad/s. + - ``radius``: core radius in m. """ exterior = float( np.real(self.exterior.permittivity(np.array([omega], dtype=float))[0]) @@ -1262,21 +1152,9 @@ def _tail_fraction( ) -> Float[np.ndarray, " n_modes"]: """Return the exact power fraction of each channel beyond a radius. - Parameters - ---------- - solution : _RadialSolution - Solved radial eigenfunctions and propagation constants. - omega : float - Positive angular frequency in rad/s. - radius : float - Core radius in m. - outer_radius : float - Radius in m at or beyond the Bessel matching radius. - - Returns - ------- - numpy.ndarray - Per-channel discarded power fractions, shape (n_modes,). + - ``omega``: positive angular frequency in rad/s. + - ``radius``: core radius in m. + - ``outer_radius``: radius in m at or beyond the Bessel matching radius. """ exterior = float( np.real(self.exterior.permittivity(np.array([omega], dtype=float))[0]) diff --git a/src/solprop/modes/guide.py b/src/solprop/modes/guide.py index a5946f74..212cc690 100644 --- a/src/solprop/modes/guide.py +++ b/src/solprop/modes/guide.py @@ -279,10 +279,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 "ChannelBasis: a declared unitary change of a guide's channel basis", [ ("gauge", self.gauge_id), @@ -339,18 +336,7 @@ def __post_init__(self) -> None: ) def _check_blocks(self, count: int) -> None: - """Require disjoint valid blocks and a matrix that is the identity outside. - - Parameters - ---------- - count : int - Validated channel count, dimensionless. - - Raises - ------ - solprop.exceptions.ModeError - Naming what is out of range. - """ + """Require disjoint valid blocks and a matrix that is the identity outside.""" seen: set[int] = set() membership = np.full(count, -1, dtype=int) for position, block in enumerate(self.blocks): @@ -617,10 +603,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. """ low, high = self.z_domain return "StepIndexProvider: exact bound E/H, unit full-domain power", [ @@ -647,15 +630,7 @@ def __post_init__(self) -> None: self._check_material_agreement() def _check_material_agreement(self) -> None: - """Require each region's law to be the index its channels were solved with. - - Raises - ------ - ModeError - If a region's law does not cover the channels' profile wavelength, or - its permittivity there departs from the channels' own index squared by - more than :data:`MATERIAL_AGREEMENT_RTOL`. - """ + """Require each region's law to be the index its channels were solved with.""" mode = self.modes[0] omega = float(2 * np.pi * C / mode.profile_wavelength) # The resolved laws, not the declared ones: `core=None` means fused silica @@ -1190,10 +1165,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 "ModalPath: how a moving guide was sampled along z", [ ("sections", f"{self.n_intervals} ({self.sections} per kink interval)"), @@ -1339,10 +1311,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. """ low, high = self.table.wavelength_range return f"ProviderChannelMode: {self.label.display_label}, from a provider", [ @@ -1431,17 +1400,7 @@ def area_path(self) -> ZProfile | None: def _along(self, values: Sequence[np.ndarray], z: float) -> np.ndarray: """Interpolate per-position values of this channel's path at one position. - Parameters - ---------- - values : sequence of numpy.ndarray - One value per path position. - z : float - Position in m. - - Returns - ------- - numpy.ndarray - The value at `z`. + - ``z``: position in m. """ path = self.path assert path is not None @@ -1759,15 +1718,7 @@ class _PathArea: def __call__(self, z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: """Return the area at every position, m². - Parameters - ---------- - z : numpy.ndarray - Positions in m. - - Returns - ------- - numpy.ndarray - Areas in m², same shape. + - ``z``: positions in m. """ return np.array([self.channel.aeff(float(one)) for one in np.asarray(z)]) @@ -1782,15 +1733,7 @@ class _SolvedPathArea: def __call__(self, z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: """Return the area at every position, m². - Parameters - ---------- - z : numpy.ndarray - Positions in m. - - Returns - ------- - numpy.ndarray - Areas in m², same shape. + - ``z``: positions in m. """ values = np.asarray(self.areas) return np.array( @@ -1801,19 +1744,8 @@ def __call__(self, z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: def _profile_defect(interpolated: ModalSample, solved: ModalSample) -> float: """Return the relative maximum-norm defect between two aligned samples' fields. - Parameters - ---------- - interpolated : solprop.modes.vector.ModalSample - The table's frequency interpolation at one frequency. - solved : solprop.modes.vector.ModalSample - A fresh provider solve there, already rotated onto the interpolation by - :func:`solprop.translate.modal_tables.align_modal_sample`. - - Returns - ------- - float - The larger of the electric and magnetic relative defects, dimensionless - and non-negative. + - Returns: the larger of the electric and magnetic relative defects, dimensionless + and non-negative. """ # Neither scale can vanish: a validated sample's flux metric is positive # definite, which no identically zero E or H produces. @@ -2018,10 +1950,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. """ low, high = self.omega_span blocks = self.degenerate_blocks @@ -2094,10 +2023,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. """ labels = [label.display_label for label in self.mode_collection.channel_labels] sampling = self.sampling @@ -2627,16 +2553,8 @@ def identity(self) -> Mapping[str, JsonValue]: def _moving_geometry(provider: ModalProvider) -> list[str]: """Return what moves along z in a provider's declared geometry, by name. - Parameters - ---------- - provider : ModalProvider - The guide. - - Returns - ------- - list of str - Each region radius whose path is not constant, and the interior kinks; empty - for a guide whose geometry does not move. + - Returns: each region radius whose path is not constant, and the interior kinks; + empty for a guide whose geometry does not move. """ moving = [ f"{region.name}.{name}" @@ -2659,20 +2577,7 @@ def _check_entrance_rule( ) -> None: """Refuse an entrance rule the provider would not build again at every position. - Parameters - ---------- - provider : ModalProvider - The moving guide. - nodes : ModalNodeRule - The declared entrance rule. - z : float - The entrance, m. - - Raises - ------ - ModeError - If the provider, asked for the rule the way `nodes` says it was asked for, - returns a different one at the entrance. + - ``z``: the entrance, m. """ # Imported here: solprop.translate imports solprop.modes. from solprop.translate.modal_path import path_rule # noqa: PLC0415 @@ -2700,23 +2605,7 @@ def _sampled_path( ) -> tuple[ModalPath, list[ProviderChannelMode]]: """Sample a moving guide's path and give each channel its index along it. - Parameters - ---------- - provider : ModalProvider - The moving guide. - table : solprop.translate.modal_tables.DenseModalTable - The entrance snapshot, already solved and checked. - modes : sequence of ProviderChannelMode - The channels, built from the entrance snapshot. - sections : int - Sections per kink interval. - rtol : float - The held-out budget, before the floor. - - Returns - ------- - tuple - The path record and the channels carrying it. + - Returns: the path record and the channels carrying it. """ # Imported here: solprop.translate imports solprop.modes. from solprop.translate.modal_path import ( # noqa: PLC0415 @@ -2799,35 +2688,12 @@ def _entrance_table( ) -> tuple[list[str], float, ModalNodeRule, object]: """Solve a guide's snapshot table, at its entrance when its geometry moves. - Parameters - ---------- - provider : ModalProvider - The guide. - frequencies : numpy.ndarray - The knots in rad/s. - z : float - The declared snapshot position, m. - nodes : ModalNodeRule or None - The declared rule, or ``None`` for the provider's own at the lowest knot. - degenerate_blocks : sequence of sequence of int - The declared degenerate blocks. - basis : ChannelBasis or None - The declared change of basis. - path_declared : bool - Whether a path's sections or budget were declared. - - Returns - ------- - tuple - What moves (empty for a snapshot), the position the table is at, the rule - and the table. - - Raises - ------ - ModeError - If a path is declared for a snapshot, a moving guide is asked for a - snapshot position other than its entrance, or its rule is not the - provider's own. + - ``frequencies``: the knots in rad/s. + - ``z``: the declared snapshot position, m. + - ``nodes``: the declared rule, or ``None`` for the provider's own at the lowest + knot. + - Returns: what moves (empty for a snapshot), the position the table is at, the rule + and the table. """ # Imported here: solprop.translate imports solprop.modes. from solprop.translate.modal_path import path_snapshot # noqa: PLC0415 diff --git a/src/solprop/modes/material.py b/src/solprop/modes/material.py index 6b383dc1..73275599 100644 --- a/src/solprop/modes/material.py +++ b/src/solprop/modes/material.py @@ -237,15 +237,7 @@ def identity(self) -> Mapping[str, JsonValue]: def _radius_text(value: float | ZProfile | None) -> str: """Return a printable radius without evaluating a declared path. - Parameters - ---------- - value : float or ZProfile or None - Radius in m, a declared path, or None for the unbounded exterior. - - Returns - ------- - str - An engineering radius, the path's one-line description, or ``unbounded``. + - ``value``: radius in m, a declared path, or None for the unbounded exterior. """ if value is None: return "unbounded" @@ -386,10 +378,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 "MaterialKerr: an instantaneous isotropic Kerr response", [ ("declared as", "chi3" if self.chi3 is not None else "n2"), @@ -465,16 +454,9 @@ def susceptibility(self, material: PermittivityLaw, omega: float) -> float: def _conversion(self, omega: float) -> float: """Return the frequency the ``n2`` conversion reads the index at, rad/s. - Parameters - ---------- - omega : float - The frequency asked for, used when no measurement wavelength is - declared. - - Returns - ------- - float - ``2 pi c / at`` when `at` is declared, and `omega` otherwise. + - ``omega``: the frequency asked for, used when no measurement wavelength is + declared. + - Returns: ``2 pi c / at`` when `at` is declared, and `omega` otherwise. """ return float(omega) if self.at is None else 2.0 * np.pi * C / float(self.at) @@ -509,22 +491,8 @@ def nonlinear_index(self, material: PermittivityLaw, omega: float) -> float: def _linear_index(material: PermittivityLaw, omega: float) -> float: """Return a material's real linear refractive index at one frequency. - Parameters - ---------- - material : PermittivityLaw - The law to evaluate. - omega : float - Positive angular frequency in rad/s inside the material's domain. - - Returns - ------- - float - ``n_0 = sqrt(Re eps_r)``, dimensionless and positive. - - Raises - ------ - ModeError - If the frequency or the resulting index is unusable. + - ``omega``: positive angular frequency in rad/s inside the material's domain. + - Returns: ``n_0 = sqrt(Re eps_r)``, dimensionless and positive. """ if not np.isfinite(omega) or omega <= 0.0: raise ModeError(f"omega must be positive and finite, got {omega!r}") @@ -609,10 +577,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 "GasKerr: the electronic response of a region's gas fill", [ ("fill", repr(self.fill)), @@ -696,10 +661,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 f"MaterialRegion: {self.name}", [ ("inner", _radius_text(self.inner_radius)), diff --git a/src/solprop/modes/protocol.py b/src/solprop/modes/protocol.py index ad03740b..b0429e1b 100644 --- a/src/solprop/modes/protocol.py +++ b/src/solprop/modes/protocol.py @@ -173,10 +173,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 f"ChannelLabel: {self.display_label}", [ ("family", self.family), diff --git a/src/solprop/modes/quadrature.py b/src/solprop/modes/quadrature.py index 67a0f449..01c7b9b0 100644 --- a/src/solprop/modes/quadrature.py +++ b/src/solprop/modes/quadrature.py @@ -226,16 +226,8 @@ def _legendre_recurrence( ) -> tuple[Float[np.ndarray, " n_coefficients"], Float[np.ndarray, " n_coefficients"]]: """Return the three-term recurrence coefficients of the Legendre polynomials. - Parameters - ---------- - count : int - How many coefficients to return. - - Returns - ------- - tuple of numpy.ndarray - ``alpha`` (all zero, the weight being symmetric) and ``beta``, with - ``beta[0]`` the measure of the interval. + - Returns: ``alpha`` (all zero, the weight being symmetric) and ``beta``, with + ``beta[0]`` the measure of the interval. """ alpha = np.zeros(count) beta = np.zeros(count) @@ -255,15 +247,8 @@ def _jacobi_kronrod( can be read against the source. It is validated against QuadGK's own nodes and weights, which use the same algorithm independently. - Parameters - ---------- - n : int - Order of the underlying Gauss rule; the extension has ``2 n + 1`` points. - - Returns - ------- - tuple of numpy.ndarray - The diagonal and off-diagonal recurrence coefficients, length ``2 n + 1``. + - Returns: the diagonal and off-diagonal recurrence coefficients, length ``2 n + + 1``. References ---------- @@ -374,18 +359,8 @@ def _radial_rule( ]: """Return the radial nodes on ``(0, 1)`` with their fine and coarse weights. - Parameters - ---------- - n_r : int - Requested node count; rounded up to ``2 n + 1`` for a Kronrod rule. - kronrod : bool - Whether to use the Kronrod extension and its embedded coarse rule. - - Returns - ------- - tuple of numpy.ndarray - ``xi``, ``w`` and ``w_coarse`` on ``xi = r / a``. Without a Kronrod extension - there is no embedded rule and the coarse weights equal the fine ones. + - Returns: ``xi``, ``w`` and ``w_coarse`` on ``xi = r / a``. Without a Kronrod + extension there is no embedded rule and the coarse weights equal the fine ones. """ if kronrod: # The smallest n with 2n + 1 >= n_r. @@ -406,15 +381,7 @@ def _field_scale_measure( field scales at a ``count``-point Gauss-Legendre rule in ``xi`` on ``(0, 1)`` and the weights carry ``xi eta`` and the Jacobian of the map. - Parameters - ---------- - count : int - Gauss-Legendre points in ``xi``. - - Returns - ------- - tuple of numpy.ndarray - ``eta`` and the discrete measure, both of length `count`. + - Returns: ``eta`` and the discrete measure, both of length `count`. """ nodes, weights = roots_legendre(count) xi = 0.5 * (nodes + 1.0) @@ -435,17 +402,9 @@ def _lanczos( discrete measure, and unlike the discretised Stieltjes procedure it does not lose orthogonality at high order. ``beta[0]`` is the total mass. - Parameters - ---------- - points, measure : numpy.ndarray - The support and the positive masses of the measure. - n : int - Coefficients wanted, >= 1 and at most the number of points. - - Returns - ------- - tuple of numpy.ndarray - ``alpha`` and ``beta``, each of length `n`. + - ``points``, ``measure``: the support and the positive masses of the measure. + - ``n``: coefficients wanted, >= 1 and at most the number of points. + - Returns: ``alpha`` and ``beta``, each of length `n`. """ alpha = np.zeros(n) beta = np.zeros(n) @@ -469,20 +428,9 @@ def _field_scale_recurrence( ) -> tuple[Float[np.ndarray, " n"], Float[np.ndarray, " n"], int]: """Return converged recurrence coefficients of the field-scale measure. - Parameters - ---------- - n : int - Coefficients wanted, >= 1. - - Returns - ------- - tuple - ``alpha``, ``beta`` and the measure's point count at which they converged. - - Raises - ------ - ModeError - If no count in ``_MEASURE_COUNTS`` settles the coefficients. + - ``n``: coefficients wanted, >= 1. + - Returns: ``alpha``, ``beta`` and the measure's point count at which they + converged. """ previous = None for count in _MEASURE_COUNTS: @@ -615,23 +563,8 @@ def _certify( ) -> None: """Check a field-scale rule against a measure refined once more. - Parameters - ---------- - n : int - The order, for the message. - eta, weights : numpy.ndarray - The rule. - degree : int or None - The polynomial degree it must integrate exactly; ``None`` for the one-node - special rule, which must match moments 1 and 3 only. - count : int - The measure point count the rule was built from. - - Raises - ------ - ModeError - If a weight is not finite and positive, a node is outside ``(0, 1]`` or - repeated, or a moment misses ``_MOMENT_TOLERANCE``. + - ``degree``: the polynomial degree it must integrate exactly; ``None`` for the + one-node special rule, which must match moments 1 and 3 only. """ if ( not np.all(np.isfinite(weights)) @@ -658,15 +591,8 @@ def _certify( def _xi_of_eta(eta: float) -> float: """Return the radius ``xi = r / a`` at which the HE_11 field scale equals `eta`. - Parameters - ---------- - eta : float - A field scale in ``(0, 1]``. - - Returns - ------- - float - The root of ``J_0(u xi) = eta`` in ``[0, 1)``; exactly 0 on the axis. + - ``eta``: a field scale in ``(0, 1]``. + - Returns: the root of ``J_0(u xi) = eta`` in ``[0, 1)``; exactly 0 on the axis. """ if eta >= 1.0: return 0.0 @@ -686,20 +612,11 @@ def _field_scale_radial( ) -> tuple[Float[np.ndarray, " n"], Float[np.ndarray, " n"]]: """Return a field-scale rule as radii and dimensionless radial area weights. - Parameters - ---------- - n : int - Nodes. - radau : bool - Fix one node on the axis. - - Returns - ------- - tuple of numpy.ndarray - ``xi`` in increasing order and ``q = lambda / eta``, the weights of - ``int f xi d xi``: the area form, finite at the axis, which the ``d xi`` form - would need to divide by ``xi`` (SEP 19 section 4.2). They are source-adapted - and do not integrate a constant area density exactly. + - ``radau``: fix one node on the axis. + - Returns: ``xi`` in increasing order and ``q = lambda / eta``, the weights of ``int + f xi d xi``: the area form, finite at the axis, which the ``d xi`` form would need + to divide by ``xi`` (SEP 19 section 4.2). They are source-adapted and do not + integrate a constant area density exactly. """ eta, weights = field_scale_rule(n, radau=radau) xi = np.array([_xi_of_eta(float(value)) for value in eta]) @@ -747,18 +664,9 @@ def _azimuthal_rule( ]: """Return the azimuthal nodes with their fine and coarse weights. - Parameters - ---------- - n_theta : int - Node count for the full rule; ignored when `full` is false. - full : bool - Whether the integrand varies with the azimuthal angle. - - Returns - ------- - tuple of numpy.ndarray - ``theta``, ``w`` and ``w_coarse``. The azimuthally symmetric rule is one node - at ``theta = 0`` carrying the whole ``2 pi``. + - ``n_theta``: node count for the full rule; ignored when `full` is false. + - Returns: ``theta``, ``w`` and ``w_coarse``. The azimuthally symmetric rule is one + node at ``theta = 0`` carrying the whole ``2 pi``. """ if not full: single = np.array([2.0 * math.pi]) @@ -917,20 +825,7 @@ def _limits( ) -> tuple[tuple[float, ...], tuple[float, ...]]: """Check that a mode's geometry matches this rule and return its limits. - Parameters - ---------- - dimlimits : tuple - As returned by :meth:`solprop.modes.protocol.Mode.dimlimits`. - - Returns - ------- - tuple - The lower and upper limits. - - Raises - ------ - ModeError - If the cross-section kind does not match the rule. + - Returns: the lower and upper limits. """ kind, lower, upper = dimlimits if kind != self.kind: @@ -1104,25 +999,7 @@ def _check_request( # noqa: PLR0917 - called positionally Typed loosely on purpose: it is the guard behind :func:`quadrature_rule`'s own ``Literal`` annotations, so it must see and name a value those would reject. - Parameters - ---------- - kind : str - The cross-section, as :func:`quadrature_rule`. - full : bool - Whether the second coordinate is resolved. - n_r : object - The first-coordinate count, checked to be an integer here. - n_theta : int - The second-coordinate count. - radial_rule : str - The first-coordinate family, checked against :data:`RadialRule` here. - breaks : tuple of float - Radial panel boundaries. - - Raises - ------ - ModeError - As :func:`quadrature_rule` describes. + - ``full``: whether the second coordinate is resolved. """ if kind not in ("polar", "cartesian", "slab"): raise ModeError(f"kind must be 'polar', 'cartesian' or 'slab', got {kind!r}") @@ -1652,22 +1529,9 @@ def _normalisation(mode: Mode, z: float) -> float: which would turn the orthonormality test into a tautology; the closed form is what makes that test say something. - Parameters - ---------- - mode : Mode - The mode, which must provide ``normalisation(z)``. - z : float - Position along the waveguide, m. - - Returns - ------- - float - ``N`` in W. - - Raises - ------ - ModeError - If the mode has no closed-form normalisation. + - ``mode``: the mode, which must provide ``normalisation(z)``. + - ``z``: position along the waveguide, m. + - Returns: ``N`` in W. """ closed_form: Callable[[float], float] | None = getattr(mode, "normalisation", None) if not callable(closed_form): diff --git a/src/solprop/modes/rectangular.py b/src/solprop/modes/rectangular.py index 12dbfcea..9526b18d 100644 --- a/src/solprop/modes/rectangular.py +++ b/src/solprop/modes/rectangular.py @@ -85,23 +85,10 @@ def _transverse_term( ) -> tuple[Complex[np.ndarray, " n_omega"], Complex[np.ndarray, " n_omega"]]: """Return one wall pair's squared transverse wavenumber divided by k squared. - Parameters - ---------- - omega : numpy.ndarray - Frequencies in rad/s; complex continuation is allowed. - width : float - Positive half-width in m. - order : int - Positive transverse mode index. - correction : numpy.ndarray - Dimensionless TE-like or TM-like reflection coefficient. - slope : numpy.ndarray - Frequency derivative of the correction, in s/rad. - - Returns - ------- - tuple of numpy.ndarray - Dimensionless decrement and its derivative in s/rad. + - ``omega``: frequencies in rad/s; complex continuation is allowed. + - ``width``: positive half-width in m. + - ``slope``: frequency derivative of the correction, in s/rad. + - Returns: dimensionless decrement and its derivative in s/rad. """ scale = C / (omega * width) leading = (order * math.pi * scale / 2) ** 2 @@ -138,19 +125,8 @@ def _warn_validity( (:meth:`~solprop.modes.fill.GasFillMixin._frozen_fill` copies the reference), so a guide that varies along z warns once, not once per sampled position. - Parameters - ---------- - terms : _RectTerms - Core and wall samples on the evaluated band, real frequencies. - pairs : sequence of tuple - One ``(half_width, order, reflection)`` per wall pair, half-width in m and - reflection ``"te"`` (E parallel to that pair's walls) or ``"tm"``. - - Warns - ----- - solprop.exceptions.GrazingIncidenceWarning - The first time either ratio exceeds :data:`_VALIDITY_RATIO` on this mode, - naming the ratio, its value and the wavelength. + - ``pairs``: one ``(half_width, order, reflection)`` per wall pair, half-width + in m and reflection ``"te"`` (E parallel to that pair's walls) or ``"tm"``. """ if self._validity_warned[0]: return @@ -185,22 +161,8 @@ def _warn_validity( def _terms(self, omega: Float[np.ndarray, " n_omega"], z: float) -> _RectTerms: """Sample the core and wall permittivities and frequency slopes. - Parameters - ---------- - omega : numpy.ndarray - Positive real frequencies in rad/s. - z : float - Position in m at which to evaluate the fill. - - Returns - ------- - _RectTerms - Frequency-dependent quantities for the local filled guide. - - Raises - ------ - ModeError - If omega is empty, non-positive or non-finite. + - ``omega``: positive real frequencies in rad/s. + - ``z``: position in m at which to evaluate the fill. """ if not omega.size or not np.all(np.isfinite(omega)) or np.any(omega <= 0): raise ModeError( @@ -243,21 +205,8 @@ def _wall_coefficients( ]: """Return the TE-like and TM-like wall coefficients and their slopes. - Parameters - ---------- - terms : _RectTerms - Core and wall samples, with holomorphic frequency continuation allowed. - - Returns - ------- - tuple of numpy.ndarray - ``q_TE = 1/s``, its derivative, ``q_TM = eps_cl/(eps_co s)`` and its - derivative, with ``s`` the filled-core contrast; derivatives in s/rad. - - Raises - ------ - ModeError - If the wall contrast vanishes. + - Returns: ``q_TE = 1/s``, its derivative, ``q_TM = eps_cl/(eps_co s)`` and its + derivative, with ``s`` the filled-core contrast; derivatives in s/rad. """ contrast, dcontrast = self._wall_contrast(terms) if np.any(contrast == 0): @@ -281,15 +230,7 @@ def _wall_contrast( ) -> tuple[Complex[np.ndarray, " n_omega"], Complex[np.ndarray, " n_omega"]]: """Return the filled-core wall contrast and its derivative. - Parameters - ---------- - terms : _RectTerms - Local material samples. - - Returns - ------- - tuple of numpy.ndarray - Relative permittivity contrast and its frequency slope in s/rad. + - Returns: relative permittivity contrast and its frequency slope in s/rad. """ core, dcore = self._core_permittivity(terms) return terms.wall - core, terms.dwall - dcore @@ -303,15 +244,7 @@ def _core_permittivity( reflection coefficient's permittivity ratio, so both read it here and a witness subclass can restore the evacuated-core convention in one place. - Parameters - ---------- - terms : _RectTerms - Local material samples. - - Returns - ------- - tuple of numpy.ndarray - Relative core permittivity and its frequency slope in s/rad. + - Returns: relative core permittivity and its frequency slope in s/rad. """ return 1.0 + terms.chi, terms.dchi @@ -429,20 +362,8 @@ def __post_init__(self) -> None: def _widths(self, z: float) -> tuple[float, float]: """Return the positive half-widths at one longitudinal position. - Parameters - ---------- - z : float - Position in m. - - Returns - ------- - tuple of float - Half-widths a and b in m. - - Raises - ------ - ModeError - If either half-width is non-positive or non-finite. + - ``z``: position in m. + - Returns: Half-widths a and b in m. """ position = np.asarray([z]) a, b = float(self.width_x(position)[0]), float(self.width_y(position)[0]) @@ -623,22 +544,8 @@ def _model_values( ) -> tuple[Complex[np.ndarray, " n_omega"], Complex[np.ndarray, " n_omega"]]: """Evaluate the complex reflection model before the shared loss policy. - Parameters - ---------- - terms : _RectTerms - Core and wall samples, with holomorphic frequency continuation allowed. - a, b : float - Positive half-widths in m. - - Returns - ------- - tuple of numpy.ndarray - Refractivity and its frequency derivative in s/rad. - - Raises - ------ - ModeError - If the wall contrast vanishes or the model produces a non-finite result. + - ``a``, ``b``: positive half-widths in m. + - Returns: refractivity and its frequency derivative in s/rad. """ te, dte, tm, dtm = self._wall_coefficients(terms) x, dx = (tm, dtm) if self.polarisation == "x" else (te, dte) @@ -659,17 +566,10 @@ def _values( ) -> tuple[Complex[np.ndarray, " n_omega"], Complex[np.ndarray, " n_omega"]]: """Apply the shared loss policy and floor to the local model. - Parameters - ---------- - omega : numpy.ndarray - Positive frequencies in rad/s. - z : float - Position in m. - - Returns - ------- - tuple of numpy.ndarray - Refractivity and its derivative in s/rad after loss and floor policies. + - ``omega``: positive frequencies in rad/s. + - ``z``: position in m. + - Returns: refractivity and its derivative in s/rad after loss and floor + policies. """ a, b = self._widths(z) terms = self._terms(omega, z) @@ -799,10 +699,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. """ entrance = np.array([0.0]) cladding = self.cladding_index diff --git a/src/solprop/modes/slab.py b/src/solprop/modes/slab.py index b92e4fc9..8bea8720 100644 --- a/src/solprop/modes/slab.py +++ b/src/solprop/modes/slab.py @@ -248,20 +248,8 @@ def __post_init__(self) -> None: def _half_gap(self, z: float) -> float: """Return the positive half-gap at one longitudinal position. - Parameters - ---------- - z : float - Position in m. - - Returns - ------- - float - Half-gap ``a`` in m. - - Raises - ------ - ModeError - If the half-gap is non-positive or non-finite there. + - ``z``: position in m. + - Returns: Half-gap ``a`` in m. """ a = float(self.width_x(np.asarray([z]))[0]) if not (math.isfinite(a) and a > 0): @@ -443,22 +431,8 @@ def _model_values( ) -> tuple[Complex[np.ndarray, " n_omega"], Complex[np.ndarray, " n_omega"]]: """Evaluate the one-wall-pair reflection model before the loss policy. - Parameters - ---------- - terms : _RectTerms - Core and wall samples, with holomorphic frequency continuation allowed. - a : float - Positive half-gap in m. - - Returns - ------- - tuple of numpy.ndarray - Refractivity and its frequency derivative in s/rad. - - Raises - ------ - ModeError - If the wall contrast vanishes or the model produces a non-finite result. + - ``a``: positive half-gap in m. + - Returns: refractivity and its frequency derivative in s/rad. """ te, dte, tm, dtm = self._wall_coefficients(terms) # E parallel to the plates is the TE-like (s) reflection, E normal to them @@ -480,17 +454,10 @@ def _values( ) -> tuple[Complex[np.ndarray, " n_omega"], Complex[np.ndarray, " n_omega"]]: """Apply the shared loss policy and floor to the local model. - Parameters - ---------- - omega : numpy.ndarray - Positive frequencies in rad/s. - z : float - Position in m. - - Returns - ------- - tuple of numpy.ndarray - Refractivity and its derivative in s/rad after loss and floor policies. + - ``omega``: positive frequencies in rad/s. + - ``z``: position in m. + - Returns: refractivity and its derivative in s/rad after loss and floor + policies. """ a = self._half_gap(z) terms = self._terms(omega, z) @@ -615,10 +582,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. """ cladding = self.cladding_index return f"SlabMode {self.channel_label.display_label}", [ @@ -910,20 +874,7 @@ def gaussian_to_per_unit_width(value: float, w_y: float) -> float: def _checked_radius(w_y: float) -> float: """Return a Gaussian radius after checking it. - Parameters - ---------- - w_y : float - 1/e field radius in m. - - Returns - ------- - float - The same radius. - - Raises - ------ - ModeError - If it is not positive and finite. + - ``w_y``: 1/e field radius in m. """ if not (math.isfinite(w_y) and w_y > 0): raise ModeError(f"the Gaussian radius w_y must be positive, got {w_y!r}") @@ -1226,10 +1177,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. """ labels = ", ".join(mode.channel_label.display_label for mode in self.modes) return "PlanarGrid: slab modes in x, periodic samples in y", [ @@ -1247,20 +1195,7 @@ def _describe(self) -> tuple[str, list[tuple[str, object]]]: def _check_planar_modes(modes: Sequence[Mode]) -> None: - """Refuse, by name, a mode set the hybrid planar transform cannot carry. - - Parameters - ---------- - modes : sequence of Mode - The proposed channels. - - Raises - ------ - ModeError - Naming what is wrong: an empty set, a mode that is not a slab mode, a - repeated channel, different gaps, TE and TM together, different fills, or a - gap that varies along z. - """ + """Refuse, by name, a mode set the hybrid planar transform cannot carry.""" if not modes: raise ModeError("a planar grid needs at least one slab mode") for mode in modes: diff --git a/src/solprop/modes/stepindex.py b/src/solprop/modes/stepindex.py index aae9913e..7afbbabf 100644 --- a/src/solprop/modes/stepindex.py +++ b/src/solprop/modes/stepindex.py @@ -80,17 +80,7 @@ def _k_ratio( solution of that recurrence in increasing order, so the forward quotient is stable: an error in ``q_v`` is multiplied by ``q_(v+1)**2 < 1``. - Parameters - ---------- - n : int - Positive azimuthal order. - w : float or numpy.ndarray - Positive dimensionless cladding decay parameter. - - Returns - ------- - numpy.ndarray - The positive ratio, with the shape of `w`. + - Returns: the positive ratio, with the shape of `w`. """ upper = kve(n, w) finite = np.isfinite(upper) @@ -115,18 +105,6 @@ def _log_slope( ``K_n' = -(K_(n-1) + K_(n+1))/2`` from scaled functions where ``kve(n + 1, w)`` is finite, and ``K_n'/K_n = -K_(n-1)/K_n - n/w`` from :func:`_k_ratio` where it overflows. - - Parameters - ---------- - n : int - Non-negative azimuthal order. - w : float or numpy.ndarray - Positive dimensionless cladding decay parameter. - - Returns - ------- - numpy.ndarray - The logarithmic derivative, negative, with the shape of `w`. """ upper = kve(n + 1, w) finite = np.isfinite(upper) @@ -154,19 +132,9 @@ def _k_decay( ``(K_(v+1)/K_v)(w r)/(K_(v+1)/K_v)(w)``, at most one for r >= 1, so the product can only underflow, to the negligible field it describes. - Parameters - ---------- - order : int - Non-negative Bessel order. - w : float - Positive dimensionless cladding decay parameter. - radius : numpy.ndarray - Dimensionless radii r/a, at least one. - - Returns - ------- - numpy.ndarray - The ratio, between zero and one for order >= 1, with the shape of `radius`. + - ``radius``: dimensionless radii r/a, at least one. + - Returns: the ratio, between zero and one for order >= 1, with the shape of + `radius`. """ decay = np.exp(-w * (radius - 1)) denominator = kve(order, w) @@ -191,23 +159,8 @@ def _characteristic( ) -> Float[np.ndarray, "*shape"]: """Evaluate the pole-free product with positive Bessel and endpoint factors removed. - Parameters - ---------- - u : float or numpy.ndarray - Dimensionless core transverse frequency, strictly between zero and V. - v : float or numpy.ndarray - Positive dimensionless fibre frequency V, matching u in shape. - ratio : float or numpy.ndarray - Squared real cladding/core index ratio, between zero and one. - n : int - Non-negative azimuthal order. - kind : str - HE, EH, TE or TM family. - - Returns - ------- - numpy.ndarray - Pole-free characteristic value, with the broadcast input shape. + - ``u``: dimensionless core transverse frequency, strictly between zero and V. + - ``ratio``: squared real cladding/core index ratio, between zero and one. """ w = np.sqrt((v - u) * (v + u)) bessel = jv(n, u) @@ -233,23 +186,9 @@ def _partials( ) -> tuple[float, float, float]: """Differentiate with respect to u, w and the squared index ratio. - Parameters - ---------- - u : float or numpy.ndarray - Dimensionless core transverse frequency, strictly between zero and V. - w : float - Positive dimensionless cladding decay parameter. - ratio : float or numpy.ndarray - Squared real cladding/core index ratio, between zero and one. - n : int - Non-negative azimuthal order. - kind : str - HE, EH, TE or TM family. - - Returns - ------- - tuple of float - Partial derivatives with respect to u, w and the squared index ratio. + - ``u``: dimensionless core transverse frequency, strictly between zero and V. + - ``ratio``: squared real cladding/core index ratio, between zero and one. + - Returns: partial derivatives with respect to u, w and the squared index ratio. Notes ----- @@ -299,21 +238,9 @@ def _partials( def _family(u: float, v: float, ratio: float, n: int) -> _Kind: """Identify the vanishing branch of the quadratic in J_n'/(u J_n). - Parameters - ---------- - u : float or numpy.ndarray - Dimensionless core transverse frequency, strictly between zero and V. - v : float or numpy.ndarray - Positive dimensionless fibre frequency V, matching u in shape. - ratio : float or numpy.ndarray - Squared real cladding/core index ratio, between zero and one. - n : int - Non-negative azimuthal order. - - Returns - ------- - str - HE or EH according to the vanishing quadratic branch. + - ``u``: dimensionless core transverse frequency, strictly between zero and V. + - ``v``: positive dimensionless fibre frequency V, matching u in shape. + - ``ratio``: squared real cladding/core index ratio, between zero and one. """ w = math.sqrt((v - u) * (v + u)) a = float(jvp(n, u) / (u * jv(n, u))) @@ -330,17 +257,7 @@ def _family(u: float, v: float, ratio: float, n: int) -> _Kind: def _zero_block(order: int, size: int) -> Float[np.ndarray, " n_zeros"]: """Cache one block of the first zeros of one Bessel order. - Parameters - ---------- - order : int - Non-negative Bessel order. - size : int - Positive number of zeros, a power of two. - - Returns - ------- - numpy.ndarray - The first `size` zeros of J_order, dimensionless, read-only. + - Returns: the first `size` zeros of J_order, dimensionless, read-only. """ block = jn_zeros(order, size) block.flags.writeable = False @@ -355,37 +272,14 @@ def _zeros(order: int, count: int) -> Float[np.ndarray, " n_zeros"]: returns the same leading zeros, bit for bit, whatever count it is asked for, so a block rounded up to a power of two is computed once and sliced. - Parameters - ---------- - order : int - Non-negative Bessel order. - count : int - Positive number of zeros. - - Returns - ------- - numpy.ndarray - The first `count` zeros of J_order, dimensionless, read-only. + - Returns: the first `count` zeros of J_order, dimensionless, read-only. """ return _zero_block(order, max(64, 1 << (count - 1).bit_length()))[:count] @cache def _bessel_nodes(n: int, count: int) -> Float[np.ndarray, " n_zeros"]: - """Cache frequency-independent Bessel-zero scan nodes. - - Parameters - ---------- - n : int - Non-negative azimuthal order. - count : int - Positive number of zeros per Bessel order. - - Returns - ------- - numpy.ndarray - Bessel zeros for orders n-1, n and n+1, dimensionless. - """ + """Cache frequency-independent Bessel-zero scan nodes.""" return np.concatenate([_zeros(abs(order), count) for order in (n - 1, n, n + 1)]) @@ -398,23 +292,10 @@ def _cutoffs( lies between its m-th and (m+1)-th cutoff, so this list is both the expected root count and the interval each root must fall in. - Parameters - ---------- - v : float - Positive dimensionless fibre frequency V. - ratio : float - Squared real cladding/core index ratio, between zero and one. - n : int - Non-negative azimuthal order. - kind : str - HE, EH, TE or TM family. - count : int, optional - Positive number of lowest cutoffs wanted; all of them below V when omitted. - - Returns - ------- - list of float - Cutoffs of that family strictly below V, at most `count`, dimensionless. + - ``ratio``: squared real cladding/core index ratio, between zero and one. + - ``count``: positive number of lowest cutoffs wanted; all of them below V when + omitted. + - Returns: cutoffs of that family strictly below V, at most `count`, dimensionless. """ if kind in ("TE", "TM", "EH"): limit = max(1, math.ceil(v / math.pi) + 1) if count is None else count @@ -432,18 +313,7 @@ def _cutoffs( def _check_frequency(v: float) -> None: - """Refuse a fibre frequency above the largest one the root scan is qualified at. - - Parameters - ---------- - v : float - Largest dimensionless fibre frequency V that will be solved. - - Raises - ------ - ModeError - If V exceeds :data:`_MAX_V`. - """ + """Refuse a fibre frequency above the largest one the root scan is qualified at.""" if v > _MAX_V: raise ModeError( f"V={v:.16g} exceeds {_MAX_V:g}, the largest V the step-index root " @@ -462,40 +332,11 @@ def _refined_roots( # noqa: PLR0917 - called positionally ) -> dict[str, list[float]]: """Refine every sign change on a node set and label each root by family. - Parameters - ---------- - nodes : numpy.ndarray - Increasing scan nodes in (0, V), dimensionless. - values : numpy.ndarray - The characteristic at those nodes, evaluated once by the caller. - v : float - Positive dimensionless fibre frequency V. - ratio : float - Squared real cladding/core index ratio, between zero and one. - n : int - Non-negative azimuthal order. - kind : str - HE, EH, TE or TM family; the hybrid families share one product. - - Returns - ------- - dict - Family name to its roots u in increasing order. + - ``ratio``: squared real cladding/core index ratio, between zero and one. """ def equation(one: float) -> float: - """Evaluate the characteristic at one trial root. - - Parameters - ---------- - one : float - Trial dimensionless transverse frequency u. - - Returns - ------- - float - Characteristic value for the scalar root search. - """ + """Evaluate the characteristic at one trial root.""" return float(_characteristic(one, v, ratio, n, kind)) found: dict[str, list[float]] = {"HE": [], "EH": [], "TE": [], "TM": []} @@ -528,24 +369,10 @@ def _branch( The HE factor is written with ``R - (1 + ratio) b/2`` rationalised, so that the terms in ``1/w**4`` that cancel near cutoff are cancelled symbolically. - Parameters - ---------- - u : float or numpy.ndarray - Dimensionless core transverse frequencies, strictly between zero and V. - v : float - Positive dimensionless fibre frequency V. - ratio : float - Squared real cladding/core index ratio, between zero and one. - n : int - Positive azimuthal order. - kind : str - HE or EH family. - - Returns - ------- - numpy.ndarray - The family's factor at each u, with the shape of `u`; only its sign and - its roots are used. + - ``u``: dimensionless core transverse frequencies, strictly between zero and V. + - ``ratio``: squared real cladding/core index ratio, between zero and one. + - Returns: the family's factor at each u, with the shape of `u`; only its sign and + its roots are used. """ w = np.sqrt((v - u) * (v + u)) bessel = jv(n, u) @@ -586,28 +413,8 @@ def _recovered( replaced and the alignment check that follows still sees every root's radial order. - Parameters - ---------- - nodes : numpy.ndarray - Increasing scan nodes, dimensionless, spanning the cutoff intervals. - roots : list of float - The family's roots found by the product scan, in increasing order. - limits : list of float - The family's exact cutoffs, in increasing order, then the top of the scan. - scan : tuple - V, the squared index ratio, the azimuthal order n and the family (HE or EH). - - Returns - ------- - list of float - The roots with the recovered ones added, in increasing order. - - Raises - ------ - ModeError - If the factor changes sign more than once in an empty cutoff interval, - which one guided root per interval rules out: which candidate is the root - cannot be decided. + - ``scan``: v, the squared index ratio, the azimuthal order n and the family (HE or + EH). """ v, ratio, n, kind = scan bounds = np.asarray(limits) @@ -668,21 +475,8 @@ def _scan_nodes( so a bracket, and the root refined from it, does not depend on how much of the grid is taken. - Parameters - ---------- - v : float - Positive dimensionless fibre frequency V. - n : int - Non-negative azimuthal order. - low : float - Smallest u to cover, dimensionless, at least zero. - high : float - Largest u to cover, dimensionless, at most V. - - Returns - ------- - numpy.ndarray - Increasing nodes in (0, V), dimensionless. + - ``low``: smallest u to cover, dimensionless, at least zero. + - ``high``: largest u to cover, dimensionless, at most V. """ # Endpoints exclude the trivial factors at u=0 and w=0, which are not # guided modes. @@ -730,32 +524,12 @@ def _roots( roots above the last one found, whose ``u`` is within float64 rounding of V at its own cutoff; the caller reports that as an unresolvable mode. - Parameters - ---------- - v : float - Positive dimensionless fibre frequency V, at most :data:`_MAX_V`. - ratio : float - Squared real cladding/core index ratio, between zero and one. - n : int - Non-negative azimuthal order. - kind : str - HE, EH, TE or TM family. - orders : int, optional - Positive number of lowest radial orders wanted; every guided root when - omitted. The cost grows with the orders scanned, not with V. - - Returns - ------- - list of float - Guided roots u in increasing order, hence descending effective index: - the lowest `orders` of them, or fewer where fewer are guided. - - Raises - ------ - ModeError - If V exceeds :data:`_MAX_V`; if the roots do not align one for one with - the family's cutoffs; or if a root below the top of the scan cannot be - resolved. + - ``v``: positive dimensionless fibre frequency V, at most :data:`_MAX_V`. + - ``ratio``: squared real cladding/core index ratio, between zero and one. + - ``orders``: positive number of lowest radial orders wanted; every guided root when + omitted. The cost grows with the orders scanned, not with V. + - Returns: guided roots u in increasing order, hence descending effective index: the + lowest `orders` of them, or fewer where fewer are guided. """ _check_frequency(v) cutoffs = _cutoffs(v, ratio, n, kind, None if orders is None else orders + 1) @@ -780,15 +554,7 @@ def _roots( def aligned(found: list[float]) -> bool: """Say whether every root lies between its own cutoff and the next. - Parameters - ---------- - found : list of float - Roots in increasing order. - - Returns - ------- - bool - True when the i-th root is in the i-th cutoff interval for every i. + - Returns: True when the i-th root is in the i-th cutoff interval for every i. """ return len(found) <= len(expected) and all( limits[order] < root <= limits[order + 1] @@ -840,21 +606,7 @@ class _Profile: def _cutoff(kind: _Kind, n: int, m: int, ratio: float) -> float: """Return the exact cutoff at a fixed squared material-index ratio. - Parameters - ---------- - kind : str - HE, EH, TE or TM family. - n : int - Non-negative azimuthal order. - m : int - Positive radial order, counted from highest effective index. - ratio : float or numpy.ndarray - Squared real cladding/core index ratio, between zero and one. - - Returns - ------- - float - Exact dimensionless cutoff for the fixed material-index ratio. + - ``ratio``: squared real cladding/core index ratio, between zero and one. """ if kind in ("TE", "TM"): return float(_zeros(0, m)[-1]) @@ -887,15 +639,8 @@ def _index_and_derivative( ) -> tuple[Complex[np.ndarray, "*shape"], Complex[np.ndarray, "*shape"]]: """Return the index and its frequency derivative. - Parameters - ---------- - omega : array_like or float - Positive angular frequencies in rad/s. - - Returns - ------- - tuple of numpy.ndarray - Complex refractive index and slope in s/rad, with the input shape. + - ``omega``: positive angular frequencies in rad/s. + - Returns: complex refractive index and slope in s/rad, with the input shape. """ index, derivative = _material(self.cladding, omega) addition = self.numerical_aperture**2 / (2 * index) @@ -914,17 +659,9 @@ def _material( drops and warns beyond its bound; any other glass's real part is taken as documented on :class:`StepIndexMode`. - Parameters - ---------- - material : GlassIndex or float - Index model or positive constant refractive index. - omega : array_like or float - Positive angular frequencies in rad/s. - - Returns - ------- - tuple of numpy.ndarray - Real index and derivative in s/rad, with the input shape. + - ``material``: index model or positive constant refractive index. + - ``omega``: positive angular frequencies in rad/s. + - Returns: real index and derivative in s/rad, with the input shape. """ if isinstance(material, GlassIndex): lossless = lossless_index(material) @@ -937,15 +674,7 @@ def _material( def _checked_omega(omega: ArrayLike | float) -> Float[np.ndarray, " n_omega"]: """Require a nonempty vector of positive finite real angular frequencies. - Parameters - ---------- - omega : array_like or float - Positive angular frequencies in rad/s. - - Returns - ------- - numpy.ndarray - Validated frequency vector, shape (n_omega,). + - ``omega``: positive angular frequencies in rad/s. """ if np.iscomplexobj(omega): raise ModeError("omega must be real") @@ -1176,15 +905,8 @@ def from_numerical_aperture( def _radius(self, z: float) -> float: """Evaluate and validate the radius at every requested position. - Parameters - ---------- - z : float - Position along the fibre in m. - - Returns - ------- - float - Positive core radius in m. + - ``z``: position along the fibre in m. + - Returns: positive core radius in m. """ radius = float(self.core_radius(np.asarray([z]))[0]) if not math.isfinite(radius) or radius <= 0: @@ -1196,17 +918,9 @@ def _eigenpairs( ) -> tuple[Float[np.ndarray, " n_omega"], Float[np.ndarray, " n_omega"]]: """Select the m-th root of the requested vector family at each frequency. - Parameters - ---------- - omega : array_like or float - Positive angular frequencies in rad/s. - z : float - Position along the fibre in m. - - Returns - ------- - tuple of numpy.ndarray - Effective indices and their transverse roots u, shape (n_omega,). + - ``omega``: positive angular frequencies in rad/s. + - ``z``: position along the fibre in m. + - Returns: effective indices and their transverse roots u, shape (n_omega,). """ core, _ = _material(self._core, omega) cladding, _ = _material(self._cladding, omega) @@ -1257,20 +971,11 @@ def _eigenpairs( def _missing_root(self, v: float, ratio: float) -> str: """Return the diagnostic for a radial root the scan did not find. - Parameters - ---------- - v : float - Normalised frequency V at which the root is missing. - ratio : float - Squared real cladding/core index ratio, between zero and one. - - Returns - ------- - str - A below-cutoff message when V is at or below the exact cutoff; otherwise - the root exists but lies within float64 rounding of V (see - :func:`_roots`), and the message says so rather than calling the mode - below cutoff. + - ``ratio``: squared real cladding/core index ratio, between zero and one. + - Returns: a below-cutoff message when V is at or below the exact cutoff; + otherwise the root exists but lies within float64 rounding of V (see + :func:`_roots`), and the message says so rather than calling the mode below + cutoff. """ cutoff = _cutoff(self.kind, self.n, self.m, ratio) name = f"{self.kind}{self.n}{self.m}" @@ -1290,15 +995,8 @@ def _loss_values( ) -> tuple[Float[np.ndarray, " n_omega"], Float[np.ndarray, " n_omega"]]: """Return absolute power attenuation and its slope. - Parameters - ---------- - omega : array_like or float - Positive angular frequencies in rad/s. - - Returns - ------- - tuple of numpy.ndarray - Attenuation in 1/m and its derivative in s/(rad m). + - ``omega``: positive angular frequencies in rad/s. + - Returns: attenuation in 1/m and its derivative in s/(rad m). """ if not callable(self.loss): return np.full_like(omega, self.loss), np.zeros_like(omega) @@ -1573,15 +1271,7 @@ def at_parameters(self, values: Mapping[str, float]) -> "StepIndexMode": def _solve_profile(self, z: float) -> _Profile: """Evaluate the transverse-profile parameters at the declared wavelength. - Parameters - ---------- - z : float - Position along the fibre in m. - - Returns - ------- - _Profile - Radius and dimensionless transverse eigenpair at profile_wavelength. + - ``z``: position along the fibre in m. """ omega = np.asarray([2 * math.pi * C / self.profile_wavelength]) _, transverse = self._eigenpairs(omega, z) @@ -1606,27 +1296,13 @@ def _solve_profile(self, z: float) -> _Profile: @cached_property def _entrance_profile(self) -> _Profile: - """Keep the required entrance profile on this immutable mode. - - Returns - ------- - _Profile - Entrance eigenpair at profile_wavelength. - """ + """Keep the required entrance profile on this immutable mode.""" return self._solve_profile(0.0) def _profile_at(self, z: float) -> _Profile: """Reuse a constant geometry's profile and recompute a tapered one. - Parameters - ---------- - z : float - Position along the fibre in m. - - Returns - ------- - _Profile - Eigenpair at this geometry and the declared profile wavelength. + - ``z``: position along the fibre in m. """ return ( self._entrance_profile @@ -1672,10 +1348,7 @@ def cutoff_parameter(self) -> float: def _materials(self) -> tuple[str, str]: """Return the resolved core and cladding as text, for printing. - Returns - ------- - tuple of str - A glass by its name and source, anything else by its ``repr``. + - Returns: a glass by its name and source, anything else by its ``repr``. """ names = [] for model in (self._core, self._cladding): @@ -1708,10 +1381,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. """ radius = float(self.core_radius(np.array([0.0]))[0]) core, cladding = self._materials() @@ -1748,17 +1418,9 @@ def _radial_fields( ) -> tuple[Float[np.ndarray, "*shape"], Float[np.ndarray, "*shape"]]: """Evaluate radial coefficients before the parity factors are applied. - Parameters - ---------- - radius : numpy.ndarray - Dimensionless non-negative radii r/a. - profile : _Profile - Lossless eigenpair at the declared profile wavelength. - - Returns - ------- - tuple of numpy.ndarray - Unnormalised radial and tangential coefficients, with the radius shape. + - ``radius``: dimensionless non-negative radii r/a. + - Returns: unnormalised radial and tangential coefficients, with the radius + shape. """ inside = radius <= 1.0 radial, tangential = np.zeros_like(radius), np.zeros_like(radius) @@ -1899,15 +1561,8 @@ def electromagnetic_field( def _integrals(self, z: float) -> tuple[float, float]: """Integrate intensity and its square after azimuthal integration. - Parameters - ---------- - z : float - Position along the fibre in m. - - Returns - ------- - tuple of float - Squared-field and quartic-field area integrals in m². + - ``z``: position along the fibre in m. + - Returns: Squared-field and quartic-field area integrals in m². """ profile = self._profile_at(z) outer = self.dimlimits(z)[2][0] / profile.radius @@ -1915,17 +1570,7 @@ def _integrals(self, z: float) -> tuple[float, float]: def integrand(radius: float, power: int) -> float: """Evaluate the radial intensity moment. - Parameters - ---------- - radius : float - Dimensionless non-negative radius r/a. - power : int - Intensity moment, one or two. - - Returns - ------- - float - Radial moment including its Jacobian and exact azimuthal integral. + - ``radius``: dimensionless non-negative radius r/a. """ radial, tangential = self._radial_fields(np.asarray(radius), profile) x, y = float(radial) ** 2, float(tangential) ** 2 @@ -1959,10 +1604,7 @@ def integrand(radius: float, power: int) -> float: def _entrance_integrals(self) -> tuple[float, float]: """Cache the two profile integrals of a constant geometry. - Returns - ------- - tuple of float - Squared-field and quartic-field area integrals in m². + - Returns: Squared-field and quartic-field area integrals in m². """ return self._integrals(0.0) diff --git a/src/solprop/modes/tabulated.py b/src/solprop/modes/tabulated.py index 616dd25e..548708fe 100644 --- a/src/solprop/modes/tabulated.py +++ b/src/solprop/modes/tabulated.py @@ -51,17 +51,7 @@ def _relaxed_end(value: float, direction: float) -> float: """Shift a table end by the admitted ulp margin. - Parameters - ---------- - value : float - End node in rad/s. - direction : float - Sign of the shift, ``-numpy.inf`` below the node or ``numpy.inf`` above. - - Returns - ------- - float - The node moved ``_END_ULP`` representable steps in that direction. + - ``value``: end node in rad/s. """ result = float(value) for _ in range(_END_ULP): @@ -72,22 +62,7 @@ def _relaxed_end(value: float, direction: float) -> float: def _positive_vector(values: ArrayLike, name: str) -> Float[np.ndarray, " n_nodes"]: """Copy a positive finite vector. - Parameters - ---------- - values : array_like - One-dimensional coordinates or areas in the caller's SI units. - name : str - Quantity named in an error. - - Returns - ------- - numpy.ndarray - Positive finite float64 values, shape ``(n_nodes,)``. - - Raises - ------ - ModeError - If the input is not a positive finite vector. + - Returns: positive finite float64 values, shape ``(n_nodes,)``. """ result = np.array(values, dtype=np.float64, copy=True) if result.ndim != 1 or not np.all(np.isfinite(result) & (result > 0)): @@ -328,20 +303,8 @@ def _checked(self, omega: ArrayLike) -> Float[np.ndarray, " n_omega"]: clamped onto it, so that a frequency converted from a table-end wavelength evaluates instead of landing outside the splines. - Parameters - ---------- - omega : array_like - Angular frequencies in rad/s, shape ``(n_omega,)``. - - Returns - ------- - numpy.ndarray - Validated frequencies in rad/s, inside the table's support. - - Raises - ------ - ModeError - If frequencies are invalid or outside the table. + - ``omega``: angular frequencies in rad/s, shape ``(n_omega,)``. + - Returns: validated frequencies in rad/s, inside the table's support. """ values = _positive_vector(omega, "omega") nodes = np.asarray(self.omega) @@ -509,10 +472,7 @@ def effective_area(self, omega: ArrayLike) -> Float[np.ndarray, " n_omega"]: 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. """ low, high = self.wavelength_range return "IndexTable: a tabulated effective index", [ @@ -544,13 +504,7 @@ class _TableMode(MarcatiliMode): reference_wavelength: float | None = None def _check_arguments(self) -> None: - """Refuse geometry or area declarations incompatible with a fixed table. - - Raises - ------ - ModeError - If the inherited inputs, geometry or reference wavelength are invalid. - """ + """Refuse geometry or area declarations incompatible with a fixed table.""" super()._check_arguments() if self.model != "full": raise ModeError('a tabulated mode does not support model="reduced"') @@ -577,20 +531,7 @@ def _check_arguments(self) -> None: def sampled_terms(self, omega: Float[np.ndarray, " n_omega"]) -> MarcatiliTerms: """Sample the gas response without evaluating any cladding model. - Parameters - ---------- - omega : numpy.ndarray - Positive angular frequencies in rad/s, shape ``(n_omega,)``. - - Returns - ------- - MarcatiliTerms - Gas susceptibility and derivative, with zero cladding terms. - - Raises - ------ - ModeError - If a frequency is not positive and finite or lies outside the table. + - ``omega``: positive angular frequencies in rad/s, shape ``(n_omega,)``. """ # The parent's check first, so that the whole grid including its DC bin # still earns the capillary's zero-frequency diagnostic rather than the @@ -609,15 +550,8 @@ def sampled_terms(self, omega: Float[np.ndarray, " n_omega"]) -> MarcatiliTerms: def aeff(self, z: float = 0.0) -> float: """Return the propagation area at the declared reference wavelength. - Parameters - ---------- - z : float, optional - Position in m; the table fixes the geometry. - - Returns - ------- - float - Area in m^2, or Marcatili's closed form when there is no area column. + - ``z``: position in m; the table fixes the geometry. + - Returns: area in m^2, or Marcatili's closed form when there is no area column. """ if self.reference_wavelength is None: return super().aeff(z) @@ -630,56 +564,29 @@ def effective_area( ) -> Float[np.ndarray, " n_omega"]: """Return the table's frequency-dependent effective area. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s, shape ``(n_omega,)``, inside the table. - z : float, optional - Position in m; ignored because the geometry is fixed. - - Returns - ------- - numpy.ndarray - Areas in m^2, shape ``(n_omega,)``. - - Raises - ------ - ModeError - If there is no area column or the table cannot evaluate this query. + - ``omega``: angular frequencies in rad/s, shape ``(n_omega,)``, inside the + table. + - ``z``: position in m; ignored because the geometry is fixed. + - Returns: areas in m^2, shape ``(n_omega,)``. """ del z return self.table.effective_area(omega) 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() kept = [row for row in rows if row[0] not in ("model", "cladding")] return title, [*kept, ("table", repr(self.table))] def __repr__(self) -> str: - """Return the mode, geometry, fill and table support. - - Returns - ------- - str - Compact mode description. - """ + """Return the mode, geometry, fill and table support.""" radius = float(self.core_radius(np.array([0.0]))[0]) fill = ( repr(self.mixture) @@ -716,17 +623,9 @@ def _waveguide( ) -> tuple[Inexact[np.ndarray, " n_omega"], Inexact[np.ndarray, " n_omega"]]: """Return the evacuated transverse-wavenumber term and its derivative. - Parameters - ---------- - terms : MarcatiliTerms - Frequency samples in rad/s. - radius : float - Field radius in m; the table fixes the waveguide geometry. - - Returns - ------- - tuple of numpy.ndarray - Dimensionless ``1 - n_v**2`` and its derivative in s/rad. + - ``terms``: frequency samples in rad/s. + - ``radius``: field radius in m; the table fixes the waveguide geometry. + - Returns: dimensionless ``1 - n_v**2`` and its derivative in s/rad. """ # Geometry is fixed by the table; radius serves the field, normalisation # and the closed-form area only. Form 1 - n_v^2 without cancellation. @@ -811,19 +710,10 @@ def _model_values( ) -> tuple[Inexact[np.ndarray, " n_omega"], Inexact[np.ndarray, " n_omega"]]: """Return the filled table's refractivity and derivative. - Parameters - ---------- - terms : MarcatiliTerms - Frequency samples in rad/s. - radius : float - Field radius in m; ignored by the stored linear response. - density : float or numpy.ndarray - Number densities in m^-3; ignored by the stored linear response. - - Returns - ------- - tuple of numpy.ndarray - Complex refractivity and its derivative in s/rad. + - ``terms``: frequency samples in rad/s. + - ``radius``: field radius in m; ignored by the stored linear response. + - ``density``: number densities in m^-3; ignored by the stored linear response. + - Returns: complex refractivity and its derivative in s/rad. """ # Both geometry and fill are already included in the stored linear index. del radius, density @@ -859,23 +749,12 @@ class _SampledIndexTable(IndexTable): ``repr=False``: a regenerated dataclass ``__repr__`` dumped 809 characters of arrays and undid :class:`IndexTable`'s one-line description. - Parameters - ---------- - omega : array_like - Positive finite angular frequencies in rad/s, shape ``(n_nodes,)``. - neff_values : array_like - Finite complex effective indices matching `omega`. - area : array_like or None, optional - Positive finite effective areas in m^2 at the same samples. - loss_interpolation : {'log', 'linear'}, optional - Imaginary-index interpolation policy; ``linear`` by default here, because - a lossless source has no logarithm. - derivative_values : array_like - Source ``dneff_domega`` in s/rad at the same frequencies, stored sorted - and read-only, shape ``(n_sampled_derivatives,)``. - source_identity : collections.abc.Mapping - The sampled mode's type, repr, snapshot position, frequency domain and - area model. + - ``omega``: positive finite angular frequencies in rad/s, shape ``(n_nodes,)``. + - ``area``: positive finite effective areas in m^2 at the same samples. + - ``derivative_values``: source ``dneff_domega`` in s/rad at the same frequencies, + stored sorted and read-only, shape ``(n_sampled_derivatives,)``. + - ``source_identity``: the sampled mode's type, repr, snapshot position, frequency + domain and area model. """ derivative_values: Complex[np.ndarray, " n_sampled_derivatives"] @@ -893,21 +772,12 @@ def __init__( ) -> None: """Store the sampled derivatives, then validate as any index table. - Parameters - ---------- - omega : array_like - Positive frequencies in rad/s, shape ``(n_nodes,)``, in either order. - neff_values : array_like - Passive indices at those frequencies, same shape. - area : array_like or None, optional - Positive effective areas in m^2 at the same samples. - loss_interpolation : {'log', 'linear'}, optional - Imaginary-index interpolation policy; ``linear`` here, because a - lossless source has no logarithm. - derivative_values : array_like - Source ``dneff_domega`` in s/rad at the same frequencies. - source_identity : collections.abc.Mapping - Provenance of the sampled mode. + - ``omega``: positive frequencies in rad/s, shape ``(n_nodes,)``, in either + order. + - ``neff_values``: passive indices at those frequencies, same shape. + - ``area``: positive effective areas in m^2 at the same samples. + - ``derivative_values``: source ``dneff_domega`` in s/rad at the same + frequencies. """ # Set before delegating: IndexTable.__init__ calls __post_init__, which # fits the Hermite interpolants from these two fields. Declaring them as @@ -918,14 +788,7 @@ def __init__( super().__init__(omega, neff_values, area, loss_interpolation) def __post_init__(self) -> None: - """Sort the derivatives with the samples and fit Hermite interpolants. - - Raises - ------ - ModeError - If the derivatives are non-finite or disagree with the frequencies, - or if the inherited table requirements fail. - """ + """Sort the derivatives with the samples and fit Hermite interpolants.""" # The base sorts by frequency and does not report the permutation, so take # it from the same input here and apply it to the derivatives. order = np.argsort(_positive_vector(self.omega, "omega")) diff --git a/src/solprop/modes/vector.py b/src/solprop/modes/vector.py index 027b7f8c..ee40c000 100644 --- a/src/solprop/modes/vector.py +++ b/src/solprop/modes/vector.py @@ -62,10 +62,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 "ModalNodeRule: Cartesian nodes with declared material ownership", [ ("nodes", len(self.weights)), @@ -199,10 +196,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. """ nodes, _, modes = np.shape(self.electric) return "ModalSample: internal-convention E/H on physical nodes", [ @@ -286,15 +280,7 @@ def __post_init__(self) -> None: def _validate_flux(self, modes: int) -> None: """Require positive Hermitian flux and a nonsingular reciprocal pairing. - Parameters - ---------- - modes : int - Validated positive channel count, dimensionless. - - Raises - ------ - ModeError - If a physical metric is indefinite or a reciprocal pairing is singular. + - ``modes``: validated positive channel count, dimensionless. """ tolerance = 64 * modes * np.finfo(float).eps * np.linalg.norm(self.flux_metric) if np.linalg.norm(self.flux_metric - self.flux_metric.conj().T) > tolerance: From 476697c368bc5c1f782a9d7e7c06682d92435f64 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:12:53 +0000 Subject: [PATCH 06/50] Slim private docstrings in solprop.translate Mechanical: python tools/docstring_diet.py $(ls src/solprop/translate/*.py | grep -v precision.py) --write --check --report --rename-closures precision.py is left out (it goes in the next PR). The two D200 skips (PropagationProblem._check_spatial_collar_stability, SeparableOperator._adopt_user_rate) are rewritten by hand. Co-Authored-By: Claude Opus 5.5 --- src/solprop/translate/chebyshev.py | 399 +---- src/solprop/translate/collar_check.py | 164 +- src/solprop/translate/coupled.py | 292 +--- src/solprop/translate/coupling.py | 26 +- src/solprop/translate/envelope_plasma.py | 48 +- src/solprop/translate/expansion.py | 191 +-- src/solprop/translate/flux.py | 74 +- src/solprop/translate/free.py | 27 +- src/solprop/translate/general.py | 246 +-- src/solprop/translate/ionisation.py | 96 +- src/solprop/translate/linop.py | 699 ++------ src/solprop/translate/modal.py | 63 +- src/solprop/translate/modal_path.py | 83 +- src/solprop/translate/modal_tables.py | 205 +-- src/solprop/translate/planar.py | 44 +- src/solprop/translate/plasma_integration.py | 60 +- src/solprop/translate/plasma_source.py | 18 +- src/solprop/translate/problem.py | 1592 +++---------------- src/solprop/translate/radial.py | 170 +- src/solprop/translate/raman.py | 58 +- src/solprop/translate/responses.py | 492 +----- src/solprop/translate/spatial.py | 86 +- src/solprop/translate/stage.py | 163 +- src/solprop/translate/tdse.py | 208 +-- src/solprop/translate/tdse_source.py | 28 +- src/solprop/translate/transverse.py | 126 +- 26 files changed, 759 insertions(+), 4899 deletions(-) diff --git a/src/solprop/translate/chebyshev.py b/src/solprop/translate/chebyshev.py index 172517d8..9b63ddba 100644 --- a/src/solprop/translate/chebyshev.py +++ b/src/solprop/translate/chebyshev.py @@ -135,25 +135,15 @@ def _clenshaw_points( # noqa: PLR0912, PLR0917 - one loop, its cases inline; ca the values of one call per position bit for bit. The NumPy reference backend runs it as written and the native backend its C++ transcription (``solprop._helpers``). - Parameters - ---------- - z : numpy.ndarray - Positions, m, float64, shape ``(n_points,)``. - edges : numpy.ndarray - The breakpoints, float64, shape ``(n_intervals + 1,)``, increasing. - lower, upper : float - The span widened by its edge slack, m; a position outside is refused. - c : numpy.ndarray - Coefficients, shape ``(n_intervals, degree + 1, n_values)``, float64 or - complex128. - out : numpy.ndarray - The values, shape ``(n_points, n_values)``, of `c`'s dtype; overwritten. - - Returns - ------- - int - The first position outside ``[lower, upper]``, or ``-1`` when none is (the - rows from it on are then not written). + - ``z``: positions, m, float64, shape ``(n_points,)``. + - ``edges``: the breakpoints, float64, shape ``(n_intervals + 1,)``, increasing. + - ``lower``, ``upper``: the span widened by its edge slack, m; a position outside is + refused. + - ``c``: coefficients, shape ``(n_intervals, degree + 1, n_values)``, float64 or + complex128. + - ``out``: the values, shape ``(n_points, n_values)``, of `c`'s dtype; overwritten. + - Returns: the first position outside ``[lower, upper]``, or ``-1`` when none is + (the rows from it on are then not written). """ n, m = c.shape[1], c.shape[2] last = edges.shape[0] - 2 @@ -878,19 +868,7 @@ def _along_paths( ) -> Inexact[np.ndarray, " n_z *value"]: """Evaluate the paths at ``z`` and hand ``z`` and their values to ``f_of_params``. - Parameters - ---------- - paths : sequence of callable - Scalar paths of z. - f_of_params : callable - Called as ``f_of_params(z, *values)``, one array per path. - z : numpy.ndarray - Positions, m, shape ``(n_z,)``. - - Returns - ------- - numpy.ndarray - Samples of shape ``(n_z, *value_shape)``. + - ``z``: positions, m, shape ``(n_z,)``. """ return f_of_params(z, *(path(z) for path in paths)) @@ -905,28 +883,10 @@ def _validate( # noqa: PLR0917 - called positionally ) -> float: """Check the build arguments and return the absolute width floor. - Parameters - ---------- - z0, z1 : float - Ends of the domain, m. - tol : float - Target bound on the uniform relative error. - n_max : int - Highest degree tried before bisecting. - min_width : float or None - Narrowest interval allowed, m, or None for the default fraction of the span. - max_intervals : int - Interval budget. - - Returns - ------- - float - The width floor, m, absolute and derived once from the whole span. - - Raises - ------ - TranslationError - Naming the offending argument. + - ``z0``, ``z1``: ends of the domain, m. + - ``min_width``: narrowest interval allowed, m, or None for the default fraction of + the span. + - Returns: the width floor, m, absolute and derived once from the whole span. """ if not z1 > z0: msg = f"need z1 > z0, got z0 = {z0!r}, z1 = {z1!r}" @@ -959,22 +919,9 @@ def _mandatory_edges( ) -> Float[np.ndarray, " n_edges"]: """Return the sorted, deduplicated mandatory edges including both domain ends. - Parameters - ---------- - z0, z1 : float - Ends of the domain, m. - mandatory_breakpoints : array_like - Declared breakpoints, m. - - Returns - ------- - numpy.ndarray - Edges in increasing order, m, starting at ``z0`` and ending at ``z1``. - - Raises - ------ - TranslationError - If a declared breakpoint is not finite or lies outside ``[z0, z1]``. + - ``z0``, ``z1``: ends of the domain, m. + - ``mandatory_breakpoints``: declared breakpoints, m. + - Returns: edges in increasing order, m, starting at ``z0`` and ending at ``z1``. """ declared = np.asarray(mandatory_breakpoints, dtype=np.float64).reshape(-1) if declared.size > 0 and not np.all(np.isfinite(declared)): @@ -1000,27 +947,9 @@ def _split_segments( ) -> tuple[list[_Piece], bool]: """Resolve ``f`` on every segment between consecutive edges. - Parameters - ---------- - f : callable - The vectorised sampler. - edges : numpy.ndarray - Mandatory edges in increasing order, m. - tol : float - Target bound on the uniform relative error. - n_max : int - Highest degree tried before bisecting. - min_width : float - Narrowest interval allowed, m. - max_intervals : int - Interval budget, shared across the segments. - - Returns - ------- - list of _Piece - The resolved intervals, in increasing z. - bool - True when the budget was exceeded. + - ``edges``: mandatory edges in increasing order, m. + - ``min_width``: narrowest interval allowed, m. + - Returns: True when the budget was exceeded. """ pieces: list[_Piece] = [] exceeded = False @@ -1045,11 +974,6 @@ class _BatchedSampler: The first call of a walk samples its first group alone, which fixes the size of one sample; after that groups are concatenated into calls of at most :data:`_BATCH_BYTES` of samples each, and the samples split back per group. - - Parameters - ---------- - f : callable - The vectorised sampler. """ def __init__(self, f: _SampleFunction) -> None: @@ -1061,15 +985,8 @@ def __call__( ) -> list[Inexact[np.ndarray, " n_z *value"]]: """Return the samples of each group, as :func:`_sample` returns them. - Parameters - ---------- - groups : sequence of numpy.ndarray - Positions, m, one array per interval. - - Returns - ------- - list of numpy.ndarray - The samples of each group, each C-contiguous. + - ``groups``: positions, m, one array per interval. + - Returns: the samples of each group, each C-contiguous. """ sampled: list[Inexact[np.ndarray, " n_z *value"]] = [] remaining = list(groups) @@ -1090,18 +1007,7 @@ def __call__( return sampled def capacity(self, n_max: int) -> int: - """Return how many intervals' samples at degree `n_max` fit in one batch. - - Parameters - ---------- - n_max : int - Highest degree tried. - - Returns - ------- - int - At least one; one until the first sample fixes the size of a sample. - """ + """Return how many intervals' samples at degree `n_max` fit in one batch.""" if self.per_position == 0: return 1 return max(_BATCH_BYTES // ((n_max + 1) * self.per_position), 1) @@ -1111,15 +1017,8 @@ def _joined( ) -> list[Inexact[np.ndarray, " n_z *value"]]: """Sample the concatenated groups in one call and split the samples back. - Parameters - ---------- - groups : sequence of numpy.ndarray - Positions, m, one array per interval. - - Returns - ------- - list of numpy.ndarray - The samples of each group, each a contiguous copy. + - ``groups``: positions, m, one array per interval. + - Returns: the samples of each group, each a contiguous copy. """ if len(groups) == 1: return [_sample(self.f, groups[0])] @@ -1149,27 +1048,9 @@ def _split( in z. If a batched call raises, the walk falls back to one interval at a time and raises at the interval it always did. - Parameters - ---------- - f : callable - The vectorised sampler. - lo, hi : float - Ends of the segment, m. - tol : float - Target bound on the uniform relative error. - n_max : int - Highest degree tried before bisecting. - min_width : float - Narrowest interval allowed, m. - budget : int - Intervals this segment may produce; the split stops before exceeding it. - - Returns - ------- - list of _Piece - The resolved intervals, in increasing z. - bool - True when the budget was exceeded. + - ``lo``, ``hi``: ends of the segment, m. + - ``min_width``: narrowest interval allowed, m. + - Returns: True when the budget was exceeded. """ # An explicit stack, never recursion: a square-root entrance refines geometrically # and would otherwise risk the recursion limit. Pushing the right half first and @@ -1244,24 +1125,11 @@ def _look_ahead( where the budget runs out inside the levels it looked into. It holds at most :meth:`_BatchedSampler.capacity` intervals ahead of the walk. - Parameters - ---------- - sample : _BatchedSampler - The batched sampler. - frontier : sequence of tuple of float - ``(lo, hi)`` in m, of intervals the walk will visit, the one it needs now - first. - ahead : dict - Outcomes of :func:`_resolve` by interval ``(lo, hi)``, already resolved ahead - of the walk; the new ones are added to it. - tol : float - Target bound on the uniform relative error. - n_max : int - Highest degree tried before bisecting. - min_width : float - Narrowest interval allowed, m. - splits : int - How many more splits the budget allows. + - ``frontier``: ``(lo, hi)`` in m, of intervals the walk will visit, the one it + needs now first. + - ``ahead``: outcomes of :func:`_resolve` by interval ``(lo, hi)``, already resolved + ahead of the walk; the new ones are added to it. + - ``min_width``: narrowest interval allowed, m. """ level = list(frontier) found = 0 @@ -1305,22 +1173,9 @@ def _resolve_many( ``f`` are shared: each round's positions of every interval still being refined are sampled together by `sample`. - Parameters - ---------- - sample : _BatchedSampler - The batched sampler. - intervals : sequence of tuple of float - ``(lo, hi)`` in m. - tol : float - Target bound on the uniform relative error. - n_max : int - Highest degree tried. - - Returns - ------- - list of tuple - Per interval, the coefficients and whether the interval is resolved, as - :func:`_resolve` returns them. + - ``intervals``: ``(lo, hi)`` in m. + - Returns: per interval, the coefficients and whether the interval is resolved, as + :func:`_resolve` returns them. """ degree = min(INITIAL_DEGREE, n_max) values = sample([_sample_positions(degree, lo, hi) for lo, hi in intervals]) @@ -1359,19 +1214,8 @@ def _accept_many( ) -> list[_Outcome]: """Transform each interval's samples and apply :func:`_resolve`'s acceptance test. - Parameters - ---------- - values : sequence of numpy.ndarray - Samples per interval, all of shape ``(n_deg, *value_shape)``. - intervals : sequence of tuple of float - ``(lo, hi)`` in m, per interval. - tol : float - Target bound on the uniform relative error. - - Returns - ------- - list of tuple - Per interval, the coefficients and whether the tail passes the test. + - ``intervals``: ``(lo, hi)`` in m, per interval. + - Returns: per interval, the coefficients and whether the tail passes the test. """ coefficients, ratios, noises = _tails_many(values, intervals) floors = SAMPLING_NOISE_FACTOR * noises @@ -1401,21 +1245,8 @@ def _tails_many( sum NumPy takes pairwise; such intervals, and a lone interval, are taken one at a time. - Parameters - ---------- - values : sequence of numpy.ndarray - Samples per interval, all of shape ``(n_deg, *value_shape)``. - intervals : sequence of tuple of float - ``(lo, hi)`` in m, per interval. - - Returns - ------- - list of numpy.ndarray - The coefficients of each interval, C-contiguous. - numpy.ndarray - The relative coefficient tail of each. - numpy.ndarray - The relative sampling noise of each. + - ``intervals``: ``(lo, hi)`` in m, per interval. + - Returns: the coefficients of each interval, C-contiguous. """ if len(values) < 2 or values[0][0].size < 2: coefficients = [lobatto_coefficients(samples) for samples in values] @@ -1454,23 +1285,9 @@ def _resolve( ) -> tuple[Inexact[np.ndarray, " n_deg *value"], bool]: """Raise the degree on ``[lo, hi]`` until the coefficient tail is negligible. - Parameters - ---------- - f : callable - The vectorised sampler. - lo, hi : float - Ends of the interval, m. - tol : float - Target bound on the uniform relative error. - n_max : int - Highest degree tried. - - Returns - ------- - numpy.ndarray - Chebyshev coefficients on ``[lo, hi]``, shape ``(n_deg, *value_shape)``. - bool - True when the interval is resolved to ``tol``. + - ``lo``, ``hi``: ends of the interval, m. + - Returns: chebyshev coefficients on ``[lo, hi]``, shape ``(n_deg, *value_shape)``. + - Returns: True when the interval is resolved to ``tol``. """ degree = min(INITIAL_DEGREE, n_max) values = _sample(f, _sample_positions(degree, lo, hi)) @@ -1502,17 +1319,8 @@ def _sampling_noise( |a_k|`` on an interval of width ``w``; a noisy tail contributes to it only at the level of the noise times the spacing, which is negligible. - Parameters - ---------- - coefficients : numpy.ndarray - Coefficients on the interval, shape ``(n_deg, *value_shape)``. - lo, hi : float - Ends of the interval, m. - - Returns - ------- - float - The worst relative floor over the value shape; zero for a zero series. + - ``lo``, ``hi``: ends of the interval, m. + - Returns: the worst relative floor over the value shape; zero for a zero series. """ magnitude = np.abs(coefficients) envelope = magnitude.max(axis=0) @@ -1532,23 +1340,7 @@ def _sample( ) -> Inexact[np.ndarray, " n_z *value"]: """Evaluate ``f`` at ``positions`` and check the result. - Parameters - ---------- - f : callable - The vectorised sampler. - positions : numpy.ndarray - Sample positions, m, shape ``(n_z,)``. - - Returns - ------- - numpy.ndarray - Samples of shape ``(n_z, *value_shape)``, float64 or complex128. - - Raises - ------ - TranslationError - If the leading length is wrong, the value shape is empty, or a sample is not - finite. + - ``positions``: sample positions, m, shape ``(n_z,)``. """ raw = np.asarray(f(positions)) values = raw.astype(np.complex128 if np.iscomplexobj(raw) else np.float64) @@ -1576,15 +1368,7 @@ def _nodes(degree: int) -> Float[np.ndarray, " n_deg"]: fix needs, and because they nest: the degree ``2 d`` set contains the degree ``d`` set at even indices, bitwise, so a degree doubling costs ``d`` new evaluations. - Parameters - ---------- - degree : int - Polynomial degree, ``>= 1``. - - Returns - ------- - numpy.ndarray - The ``degree + 1`` points, increasing from ``-1`` to ``+1``. + - ``degree``: polynomial degree, ``>= 1``. """ return -np.cos(np.pi * np.arange(degree + 1) / degree) @@ -1599,17 +1383,10 @@ def _sample_positions(degree: int, lo: float, hi: float) -> Float[np.ndarray, " is one ulp of the interval's own scale rather than of an endpoint, so it stays a real perturbation when an end sits at zero. - Parameters - ---------- - degree : int - Polynomial degree, ``>= 1``. - lo, hi : float - Ends of the interval, m. - - Returns - ------- - numpy.ndarray - Sample positions, m, increasing, strictly inside ``[lo, hi]`` at the ends. + - ``degree``: polynomial degree, ``>= 1``. + - ``lo``, ``hi``: ends of the interval, m. + - Returns: sample positions, m, increasing, strictly inside ``[lo, hi]`` at the + ends. """ positions = 0.5 * (lo + hi) + 0.5 * (hi - lo) * _nodes(degree) inset = float(np.spacing(max(abs(lo), abs(hi), hi - lo))) @@ -1623,18 +1400,9 @@ def _refinement_positions( ) -> Float[np.ndarray, " n_z"]: """Return the positions the degree doubling adds, all interior. - Parameters - ---------- - degree : int - Current degree; the result completes the grid of degree ``2 * degree``. - lo, hi : float - Ends of the interval, m. - - Returns - ------- - numpy.ndarray - The ``degree`` new positions, m, increasing. They are interior, so they need no - endpoint inset. + - ``lo``, ``hi``: ends of the interval, m. + - Returns: the ``degree`` new positions, m, increasing. They are interior, so they + need no endpoint inset. """ odd = -np.cos(np.pi * np.arange(1, 2 * degree, 2) / (2 * degree)) return 0.5 * (lo + hi) + 0.5 * (hi - lo) * odd @@ -1644,21 +1412,7 @@ def _interleave( coarse: Inexact[np.ndarray, " n_z *value"], fine: Inexact[np.ndarray, " n_odd *value"], ) -> Inexact[np.ndarray, " n_fine *value"]: - """Interleave a nested grid's samples with the ones the doubling added. - - Parameters - ---------- - coarse : numpy.ndarray - Samples on the degree ``d`` grid, shape ``(d + 1, *value_shape)``. - fine : numpy.ndarray - Samples at the odd points of the degree ``2 d`` grid, shape ``(d, - *value_shape)``. - - Returns - ------- - numpy.ndarray - Samples on the degree ``2 d`` grid, shape ``(2 d + 1, *value_shape)``. - """ + """Interleave a nested grid's samples with the ones the doubling added.""" merged = np.empty( (2 * coarse.shape[0] - 1, *coarse.shape[1:]), dtype=np.result_type(coarse.dtype, fine.dtype), @@ -1721,16 +1475,6 @@ def _tail_ratio(coefficients: Inexact[np.ndarray, " n_deg *value"]) -> float: operator is not swamped by one with a large operator, with the comparison scale floored at a fixed fraction of the global envelope so that masked-out or negligible elements do not drive the refinement. - - Parameters - ---------- - coefficients : numpy.ndarray - Coefficients, shape ``(n_deg, *value_shape)``. - - Returns - ------- - float - The tail relative to the value scale; zero for an identically zero series. """ magnitude = np.abs(coefficients) envelope = magnitude.max(axis=0) @@ -1753,15 +1497,7 @@ def _trim( constant operator to a single coefficient and a linear one to two — the exact, cheapest fast path of the cumulative operator. - Parameters - ---------- - coefficients : numpy.ndarray - Coefficients, shape ``(n_deg, *value_shape)``. - - Returns - ------- - numpy.ndarray - The leading coefficients that are not noise, at least one. + - Returns: the leading coefficients that are not noise, at least one. """ magnitude = np.abs(coefficients) envelope = magnitude.max(axis=0) @@ -1788,19 +1524,8 @@ def _assemble( ) -> PiecewiseChebyshev: """Pack the resolved intervals into one rectangular representation. - Parameters - ---------- - pieces : list of _Piece - The resolved intervals, in increasing z. - hard_kinks : numpy.ndarray - Interior positions, m, where the represented function itself jumps or kinks. - exceeded : bool - Whether the split ran out of its interval budget. - - Returns - ------- - PiecewiseChebyshev - The representation, with the low-degree intervals zero-padded. + - ``hard_kinks``: interior positions, m, where the represented function itself jumps + or kinks. """ n_deg = max(piece.coefficients.shape[0] for piece in pieces) dtype = np.result_type(*(piece.coefficients.dtype for piece in pieces)) @@ -1823,13 +1548,7 @@ def _assemble( def _log_summary(representation: PiecewiseChebyshev) -> None: - """Log the split's interval count, degrees and widths at DEBUG level. - - Parameters - ---------- - representation : PiecewiseChebyshev - The assembled representation. - """ + """Log the split's interval count, degrees and widths at DEBUG level.""" if not logger.isEnabledFor(logging.DEBUG): return widths = np.diff(representation.breakpoints) diff --git a/src/solprop/translate/collar_check.py b/src/solprop/translate/collar_check.py index 17975061..4354faa4 100644 --- a/src/solprop/translate/collar_check.py +++ b/src/solprop/translate/collar_check.py @@ -134,17 +134,7 @@ def _frequency_nodes( ) -> tuple[np.ndarray, np.ndarray]: """Return the band bins at equal-energy quantiles of the launch, and their shares. - Parameters - ---------- - power : numpy.ndarray - The launch's energy per requested-band bin. - count : int - How many nodes. - - Returns - ------- - tuple of numpy.ndarray - Indices into the band, unique, and the share each stands for. + - Returns: indices into the band, unique, and the share each stands for. """ total = float(np.sum(power)) # A launch with nothing in the band is stood for by the band's first bin, which @@ -169,19 +159,8 @@ def _reach( ``kappa_q / max(k_z(kappa_q), 0.44 k)``, with ``kappa_q`` the wavenumber above which the launch holds no more than :data:`WARNING_SHARE` of its energy (section 5.3). - Parameters - ---------- - kappa : numpy.ndarray - Each column's ``|kappa|``, rad/m. - power : numpy.ndarray - Each column's energy at this frequency. - k : float - The medium's wavenumber, rad/m. - - Returns - ------- - float - ``dx/dz`` of the fastest significant content. + - ``kappa``: each column's ``|kappa|``, rad/m. + - ``k``: the medium's wavenumber, rad/m. """ order = np.argsort(kappa) tail = np.cumsum(power[order][::-1])[::-1] / max(float(np.sum(power)), 1e-300) @@ -196,17 +175,8 @@ def _extent( ) -> float: """Return the launch's extent: the radius holding all but the warning share. - Parameters - ---------- - position : numpy.ndarray - Each node's distance from the axis or centre, m. - density : numpy.ndarray - Each node's weighted energy. - - Returns - ------- - float - The extent, m. + - ``position``: each node's distance from the axis or centre, m. + - Returns: the extent, m. """ order = np.argsort(position) tail = np.cumsum(density[order][::-1])[::-1] / max(float(np.sum(density)), 1e-300) @@ -224,25 +194,11 @@ def _radial_return( # noqa: PLR0917 - called positionally ) -> tuple[np.ndarray, float]: """Return one frequency's aperture difference energy at each plane, radially. - Parameters - ---------- - space : solprop.grid.RadialGrid - The design's grid. - rate : numpy.ndarray - The collar's power rate on its nodes, 1/m; zeros for a bare wall. - launch : numpy.ndarray - The launch's spectral columns at this frequency. - k : float - The medium's wavenumber, rad/m. - planes : numpy.ndarray - Positions, m. - half_size : float - The bare grid's outer radius, m. - - Returns - ------- - tuple - The difference energy per plane, and the launch energy, in one unit. + - ``rate``: the collar's power rate on its nodes, 1/m; zeros for a bare wall. + - ``k``: the medium's wavenumber, rad/m. + - ``planes``: positions, m. + - ``half_size``: the bare grid's outer radius, m. + - Returns: the difference energy per plane, and the launch energy, in one unit. """ hankel = space.hankel diagonal = hankel.j1[:, None] / hankel.j1[None, :] @@ -298,29 +254,15 @@ def _periodic_return( # noqa: PLR0917 - one frequency's comparison A Strang march on the design's own periodic grid, one or two axes, x fastest; the bare grid is the same spacing over `half_sizes`, exact in the wavenumber. - Parameters - ---------- - axes : tuple of numpy.ndarray - The design's centred axes, m, x first. - sizes : tuple of float - The requested half-sizes, m. - rate : numpy.ndarray - The collar's power rate on the nodes, 1/m. - launch : numpy.ndarray - The launch's spectral columns (unshifted FFT order, x fastest). - k : float - The wavenumber, rad/m (``beta_p`` for a slab mode). - planes : numpy.ndarray - Positions, m. - step : float - The march's longest step, m. - half_sizes : tuple of float - The bare grid's half-sizes, m. - - Returns - ------- - tuple - The difference energy per plane, and the launch energy, in one unit. + - ``axes``: the design's centred axes, m, x first. + - ``sizes``: the requested half-sizes, m. + - ``rate``: the collar's power rate on the nodes, 1/m. + - ``launch``: the launch's spectral columns (unshifted FFT order, x fastest). + - ``k``: the wavenumber, rad/m (``beta_p`` for a slab mode). + - ``planes``: positions, m. + - ``step``: the march's longest step, m. + - ``half_sizes``: the bare grid's half-sizes, m. + - Returns: the difference energy per plane, and the launch energy, in one unit. """ shape = tuple(axis.size for axis in reversed(axes)) spacing = tuple(float(axis[1] - axis[0]) for axis in axes) @@ -389,29 +331,10 @@ def _estimate( # noqa: PLR0915, PLR0917 - one estimate at one resolution; calle ) -> tuple[float, tuple[float, ...], int, bool]: """Return ``U_ret`` at one resolution of the check. - Parameters - ---------- - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The design's grid. - rate : numpy.ndarray - The collar's rate on the nodes, 1/m (zeros for the bare wall). - launch : numpy.ndarray - The noise-free launch's spectral columns, ``(n_columns, n_pol, n_omega)``. - dispersion : solprop.spatial.SpatialDispersion - The medium and the temporal grid. - z_max : float - The run's length, m. - width : float - The collar's narrowest width, m. - nodes, planes : int - Frequency nodes and the most planes. - enlarge : float - The bare grid's size as a multiple of its least. - - Returns - ------- - tuple - ``U_ret``, the nodes in rad/s and the planes compared. + - ``rate``: the collar's rate on the nodes, 1/m (zeros for the bare wall). + - ``z_max``: the run's length, m. + - ``width``: the collar's narrowest width, m. + - Returns: ``U_ret``, the nodes in rad/s and the planes compared. """ grid = dispersion.grid band = np.nonzero(grid.inband_mask & grid.active_mask)[0] @@ -556,17 +479,9 @@ def _estimate( # noqa: PLR0915, PLR0917 - one estimate at one resolution; calle def _step(positions: np.ndarray, z_max: float) -> float: """Return the Strang march's longest step: the plane spacing. - Parameters - ---------- - positions : numpy.ndarray - The planes, m. - z_max : float - The run's length, m. - - Returns - ------- - float - The step, m. + - ``positions``: the planes, m. + - ``z_max``: the run's length, m. + - Returns: the step, m. """ return z_max / positions.size @@ -586,27 +501,12 @@ def _half( # noqa: PLR0917 - called positionally ``S >= (R + x_q + v_q L) / 2``, at least the design's own half-size, at most :data:`_LARGEST` times it, times `enlarge`. - Parameters - ---------- - axis : numpy.ndarray - The centred axis, m. - size : float - The requested half-size, m. - field : numpy.ndarray - The launch's physical field, axes reversed (y, x). - reach : float - The launch's reach, ``dx/dz``. - z_max : float - The run's length, m. - enlarge : float - Multiplier of the least half-size. - along : int, optional - The array axis of `field` this coordinate runs along. - - Returns - ------- - tuple - The half-size, m, and whether the bare grid needed more than its cap. + - ``axis``: the centred axis, m. + - ``size``: the requested half-size, m. + - ``field``: the launch's physical field, axes reversed (y, x). + - ``z_max``: the run's length, m. + - ``along``: the array axis of `field` this coordinate runs along. + - Returns: the half-size, m, and whether the bare grid needed more than its cap. """ profile = np.sum( np.abs(field) ** 2, axis=tuple(i for i in range(field.ndim) if i != along) diff --git a/src/solprop/translate/coupled.py b/src/solprop/translate/coupled.py index 5b1fb020..376ad8cd 100644 --- a/src/solprop/translate/coupled.py +++ b/src/solprop/translate/coupled.py @@ -134,22 +134,8 @@ def interval_count(edges: int) -> int: def _vector(values: object, name: str) -> Float[np.ndarray, " n_entries"]: """Return a frozen, finite, strictly increasing float64 vector. - Parameters - ---------- - values : array_like - The candidate axis. - name : str - Field name, used in the refusal. - - Returns - ------- - numpy.ndarray - A read-only C-contiguous copy. - - Raises - ------ - TranslationError - If the values are not a finite, non-empty, increasing vector. + - ``values``: the candidate axis. + - Returns: a read-only C-contiguous copy. """ array = np.array(values, dtype=np.float64, order="C", copy=True) if ( @@ -169,26 +155,9 @@ def _owned( ) -> np.ndarray: """Return a frozen copy of an array, checked against a declared shape. - Parameters - ---------- - values : array_like - The candidate array. - name : str - Field name, used in the refusal. - shape : tuple of int - The shape the record's own extents require. - dtype : type - ``numpy.float64`` or ``numpy.complex128``. - - Returns - ------- - numpy.ndarray - A read-only C-contiguous copy. - - Raises - ------ - TranslationError - If the shape disagrees or a value is not finite. + - ``shape``: the shape the record's own extents require. + - ``dtype``: ``numpy.float64`` or ``numpy.complex128``. + - Returns: a read-only C-contiguous copy. """ array = np.array(values, dtype=dtype, order="C", copy=True) if array.shape != shape: @@ -204,18 +173,7 @@ def _owned( def _degree_axis(array: np.ndarray, name: str) -> None: """Refuse a coefficient block that declares no degree at all. - Parameters - ---------- - array : numpy.ndarray - A coefficient array whose second axis is the Chebyshev degree along z. - name : str - Field name, used in the refusal. - - Raises - ------ - TranslationError - If the degree axis is empty. A block of ``n_deg = 0`` evaluates to nothing - at every position, so it is refused here rather than read as a zero. + - ``array``: a coefficient array whose second axis is the Chebyshev degree along z. """ if not array.shape[1]: msg = ( @@ -228,24 +186,10 @@ def _degree_axis(array: np.ndarray, name: str) -> None: def _square(values: object, name: str, rank: int) -> tuple[np.ndarray, int]: """Return a frozen complex array whose two channel axes agree, with its width. - Parameters - ---------- - values : array_like - The candidate array, whose channel axes are the two before the frequency axis. - name : str - Field name, used in the refusal. - rank : int - Number of axes the array must have. - - Returns - ------- - tuple - The read-only array and its channel count. - - Raises - ------ - TranslationError - If the rank is wrong, the two channel axes disagree, or a value is not finite. + - ``values``: the candidate array, whose channel axes are the two before the + frequency axis. + - ``rank``: number of axes the array must have. + - Returns: the read-only array and its channel count. """ array = np.array(values, dtype=np.complex128, order="C", copy=True) if array.ndim != rank or array.shape[-3] != array.shape[-2] or not array.shape[-2]: @@ -399,10 +343,7 @@ def n_modes(self) -> int: 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 "ModalMetricTables: the physical flux metric of a modal basis", [ ("kind", self.kind), @@ -453,13 +394,7 @@ def __post_init__(self) -> None: self._validate_arrays() def _validate_arrays(self) -> None: - """Check the coefficient array against the kind and the bounds against it. - - Raises - ------ - TranslationError - Naming the field that failed. - """ + """Check the coefficient array against the kind and the bounds against it.""" if (self.dense is not None) != (self.kind == "dense"): msg = ( f"modal_metric.dense must be present exactly for kind='dense'; " @@ -523,16 +458,6 @@ def _validate_dense(self, intervals: int) -> None: real. The budget is ``64 n eps`` times the block's own norm, the same scale :meth:`solprop.modes.vector.ModalSample._validate_flux` uses on one sample's metric, because that is what a ``U.H W U`` product loses to rounding. - - Parameters - ---------- - intervals : int - Interval count from the stored ``z_edges``. - - Raises - ------ - TranslationError - If an extent disagrees or the block is not Hermitian. """ block, modes = _square(self.dense, "modal_metric.dense", 5) if block.shape[0] != intervals or block.shape[4] != self.n_omega: @@ -765,10 +690,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 "CoupledFlowTables: a generator whose channels couple", [ ("representation", self.representation), @@ -814,13 +736,7 @@ def __post_init__(self) -> None: self._validate_bounds() def _validate_representation(self) -> None: - """Check exactly one complete generator representation is stored. - - Raises - ------ - TranslationError - If the dense and factored representations are mixed or both absent. - """ + """Check exactly one complete generator representation is stored.""" factored = (self.fixed_terms is not None, self.term_weights is not None) if self.generator is not None and any(factored): msg = ( @@ -877,13 +793,7 @@ def _validate_representation(self) -> None: object.__setattr__(self, "term_weights", weights) def _validate_optional(self) -> None: - """Check the wall pair and the connection against the record's extents. - - Raises - ------ - TranslationError - If one half of the wall pair is missing or an extent disagrees. - """ + """Check the wall pair and the connection against the record's extents.""" intervals = interval_count(len(self.z_edges)) n_omega = len(self.omega) modes = self.n_modes @@ -936,13 +846,7 @@ def _validate_optional(self) -> None: object.__setattr__(self, "connection", link) def _validate_bounds(self) -> None: - """Check the three certificate rows and the per-section method names. - - Raises - ------ - TranslationError - If a bound is out of range or a section method is unknown. - """ + """Check the three certificate rows and the per-section method names.""" intervals = interval_count(len(self.z_edges)) shape = (intervals, len(self.omega)) for name in ( @@ -1424,20 +1328,10 @@ def _flow_certificate( interaction picture is re-anchored every step, so no inverse action is ever taken over more than one of them. - Parameters - ---------- - vectors, triangular : numpy.ndarray - The Schur factors, shape ``(n_omega, n_modes, n_modes)``. - blocks : numpy.ndarray - The generator itself in the same layout, 1/m. - dz_max : float - Largest step the run may take, m, positive. - - Returns - ------- - tuple of numpy.ndarray - The dimensionless representation error and the conditioning bound, each of - shape ``(n_omega,)``. + - ``blocks``: the generator itself in the same layout, 1/m. + - ``dz_max``: largest step the run may take, m, positive. + - Returns: the dimensionless representation error and the conditioning bound, each + of shape ``(n_omega,)``. """ adjoint = np.conj(np.swapaxes(vectors, -1, -2)) residual = np.linalg.norm(vectors @ triangular @ adjoint - blocks, axis=(-2, -1)) @@ -1607,25 +1501,9 @@ def _checked_wall_pair( compared, because the dense generator's diagonal also carries the transverse term, which the pair does not describe. - Parameters - ---------- - block : numpy.ndarray - The dense generator in 1/m, shape ``(n_modes, n_modes, n_omega)``. - wall_vectors : numpy.ndarray or None - The dimensionless rank-one vector, shape ``(n_modes,)``. - wall_weights : numpy.ndarray or None - Its weight in 1/m, shape ``(n_omega,)``. - - Returns - ------- - tuple of numpy.ndarray or None - The pair in the leaf's own layout -- ``(1, n_modes, n_omega)`` and - ``(1, 1, n_omega)`` -- or ``None`` when none was supplied. - - Raises - ------ - TranslationError - If one half is missing, an extent disagrees, or the reconstruction does not. + - ``wall_weights``: its weight in 1/m, shape ``(n_omega,)``. + - Returns: the pair in the leaf's own layout -- ``(1, n_modes, n_omega)`` and ``(1, + 1, n_omega)`` -- or ``None`` when none was supplied. """ supplied = (wall_vectors is not None, wall_weights is not None) if not any(supplied): @@ -1784,16 +1662,7 @@ def section_expansion( def _coefficient_tail(coefficients: np.ndarray) -> float: """Return the relative size of the last two coefficients of one section. - Parameters - ---------- - coefficients : numpy.ndarray - One section's coefficients, shape ``(n_deg, *value_shape)``. - - Returns - ------- - float - The last two coefficients' largest magnitude over the largest magnitude of the - whole block, or zero for a block that is identically zero. + - ``coefficients``: one section's coefficients, shape ``(n_deg, *value_shape)``. """ scale = float(np.max(abs(coefficients), initial=0.0)) if scale == 0.0: @@ -2325,22 +2194,9 @@ def _residual_enclosure( of the section. That is an enclosure of the stored interpolant, extrema between the samples included, which a sampled maximum is not. - Parameters - ---------- - coefficients : numpy.ndarray - One section's generator coefficients, shape - ``(n_deg, n_modes, n_modes, n_omega)``. - frozen : numpy.ndarray - The generator frozen at that section's midpoint, 1/m. - connection : numpy.ndarray or None - The whole connection block, or ``None``. - index : int - Which section, used to select the connection's own coefficients. - - Returns - ------- - numpy.ndarray - The bound in 1/m per frequency, shape ``(n_omega,)``. + - ``frozen``: the generator frozen at that section's midpoint, 1/m. + - ``connection``: the whole connection block, or ``None``. + - Returns: the bound in 1/m per frequency, shape ``(n_omega,)``. """ terms = coefficients.copy() terms[0] -= frozen @@ -2423,18 +2279,10 @@ def _hermitian_enclosure( symmetric part, of the order of the channels' weight spread; it is subtracted with the rest, because the enclosure is of what is exponentiated. - Parameters - ---------- - coefficients : numpy.ndarray - The generator's Chebyshev coefficients in 1/m. - connection : numpy.ndarray or None - The connection's, in the same layout, or ``None``. - - Returns - ------- - tuple of numpy.ndarray - The lower and upper ends ``m_lo`` and ``m_hi`` in 1/m, each of shape - ``(n_intervals, n_omega)``. + - ``coefficients``: the generator's Chebyshev coefficients in 1/m. + - ``connection``: the connection's, in the same layout, or ``None``. + - Returns: the lower and upper ends ``m_lo`` and ``m_hi`` in 1/m, each of shape + ``(n_intervals, n_omega)``. """ terms = coefficients if connection is None else coefficients - connection moved = np.moveaxis(terms, (2, 3), (-2, -1)) @@ -2460,19 +2308,11 @@ def _reach_spread( union, so that a certificate declared on a section holds for every step the controller may anchor there. - Parameters - ---------- - edges : numpy.ndarray - The section edges in m. - lower, upper : numpy.ndarray - Each section's enclosure, 1/m, shape ``(n_intervals, n_omega)``. - dz_max : float - Largest step, m. - - Returns - ------- - numpy.ndarray - ``m_hi - m_lo`` over each section's reach, 1/m, non-negative. + - ``edges``: the section edges in m. + - ``lower``, ``upper``: each section's enclosure, 1/m, shape ``(n_intervals, + n_omega)``. + - ``dz_max``: largest step, m. + - Returns: ``m_hi - m_lo`` over each section's reach, 1/m, non-negative. """ intervals = lower.shape[0] spread = np.empty_like(lower) @@ -2722,24 +2562,10 @@ def _cf4_rejection( ) -> str: """Return the clause naming why a CF4 section was rejected. - Parameters - ---------- - index : int - The section. - omega : numpy.ndarray - The frequency axis in rad/s. - growth, conditioning : numpy.ndarray - The section's two bounds per frequency, dimensionless. - growth_limit, condition_limit : float - The limits they were held to. - dz_max : float - The step they were declared over, m. - - Returns - ------- - str - One clause naming both bounds at the frequency where they are largest -- the - same frequency for both, each being increasing in the same spread. + - ``omega``: the frequency axis in rad/s. + - ``dz_max``: the step they were declared over, m. + - Returns: one clause naming both bounds at the frequency where they are largest -- + the same frequency for both, each being increasing in the same spread. """ worst = int(np.argmax(growth)) return ( @@ -2801,21 +2627,9 @@ def _flow_generator_at( ) -> Complex[np.ndarray, "n_omega_o n_modes_o_out n_modes_o_in"]: """Return ``L(z) = G(z) - C(z)`` of a step anchored in `section`, frequency first. - Parameters - ---------- - flow : CoupledFlowTables - The leaf, whose edges and degree the weights are built on. - terms : numpy.ndarray - The flattened coefficients of ``G - C`` in 1/m. - z : float - Position in m. - section : int - The step's section, which :func:`flow_weights` resolves the position against. - - Returns - ------- - numpy.ndarray - The generator in 1/m, shape ``(n_omega, n_modes, n_modes)``. + - ``terms``: the flattened coefficients of ``G - C`` in 1/m. + - ``z``: position in m. + - Returns: the generator in 1/m, shape ``(n_omega, n_modes, n_modes)``. """ combined = np.tensordot(flow_weights(flow, z, section), terms, axes=1) return np.ascontiguousarray(np.moveaxis(combined, -1, 0)) @@ -2833,23 +2647,9 @@ def _cf4_action( The translation layer's own evaluation, independent of the backend's scaled Taylor series: the estimator and the executor are two implementations of one formula. - Parameters - ---------- - flow : CoupledFlowTables - The leaf. - terms : numpy.ndarray - The flattened coefficients of ``G - C`` in 1/m. - z : float - Where the step starts, m. - length : float - Its length, m, positive. - section : int - The step's section. - - Returns - ------- - numpy.ndarray - The action, shape ``(n_omega, n_modes, n_modes)``. + - ``terms``: the flattened coefficients of ``G - C`` in 1/m. + - ``z``: where the step starts, m. + - ``length``: its length, m, positive. """ early = _flow_generator_at(flow, terms, z + CF4_NODES[0] * length, section) late = _flow_generator_at(flow, terms, z + CF4_NODES[1] * length, section) diff --git a/src/solprop/translate/coupling.py b/src/solprop/translate/coupling.py index 888e66b3..1137848c 100644 --- a/src/solprop/translate/coupling.py +++ b/src/solprop/translate/coupling.py @@ -98,16 +98,8 @@ def frame_record( def _antiderivative(values: PiecewiseChebyshev) -> dict[str, object]: """Return the edges and coefficients of a scalar table's antiderivative. - Parameters - ---------- - values : solprop.translate.chebyshev.PiecewiseChebyshev - A scalar frame table. - - Returns - ------- - dict - JSON-ready ``breakpoints`` (m) and per-interval ``coefficients``, lowest - degree first, of the exact antiderivative vanishing at the first edge. + - Returns: JSON-ready ``breakpoints`` (m) and per-interval ``coefficients``, lowest + degree first, of the exact antiderivative vanishing at the first edge. """ integral = values.integral() coefficients = np.real(integral.coefficients).reshape(integral.degrees.size, -1) @@ -278,18 +270,8 @@ def input_port( def _scalar_contrast(profiles: ScalarModeBasis, index: np.ndarray) -> np.ndarray: """Return a step-index profile set's weak-guidance contrast at each bin. - Parameters - ---------- - profiles : solprop.coupling.ScalarModeBasis - A step-index LP set, carrying its core and cladding indices. - index : numpy.ndarray - The reference index per bin. - - Returns - ------- - numpy.ndarray - ``max |n^2 - n_ref^2| / (2 n_ref^2)`` over core and cladding, zero where - the reference index is zero, shape ``(n_omega,)``. + - Returns: ``max |n^2 - n_ref^2| / (2 n_ref^2)`` over core and cladding, zero where + the reference index is zero, shape ``(n_omega,)``. """ live = index > 0 contrast = np.zeros(index.shape) diff --git a/src/solprop/translate/envelope_plasma.py b/src/solprop/translate/envelope_plasma.py index 4c5015de..36bae514 100644 --- a/src/solprop/translate/envelope_plasma.py +++ b/src/solprop/translate/envelope_plasma.py @@ -50,15 +50,8 @@ def _readonly(values: np.ndarray) -> np.ndarray: """Copy an array into owned, contiguous, read-only storage. - Parameters - ---------- - values : numpy.ndarray - Numeric array of any shape. - - Returns - ------- - numpy.ndarray - Owned C-contiguous copy. + - ``values``: numeric array of any shape. + - Returns: owned C-contiguous copy. """ result = np.array(values, order="C", copy=True) result.setflags(write=False) @@ -465,21 +458,10 @@ def _integrate_rule( ) -> Float[np.ndarray, " n_moments"]: """Split at the table cutoff and clipping crossings before quadrature. - Parameters - ---------- - major : float - Major-axis physical field in V/m, positive. - ratio : float - Minor/major amplitude ratio in ``[0, 1]``. - rate : RateTable - Instantaneous magnitude table. - rule : AngularRule - Rule on [-1, 1]. - - Returns - ------- - numpy.ndarray - Positive cosine and sine directional moments in s^-1. + - ``major``: Major-axis physical field in V/m, positive. + - ``ratio``: Minor/major amplitude ratio in ``[0, 1]``. + - ``rule``: rule on [-1, 1]. + - Returns: positive cosine and sine directional moments in s^-1. """ edges = [0.0, math.pi / 2] # _refine handles the exactly circular case before reaching this rule. @@ -551,21 +533,9 @@ def _refine( ) -> Float[np.ndarray, " n_moments"]: """Refine the numeric rule independently of its validation record. - Parameters - ---------- - major : float - Non-negative major field in V/m. - ratio : float - Minor/major ratio in [0, 1]. - rate : RateTable - Instantaneous magnitude rate. - rules : tuple of AngularRule - Increasing-order quadrature rules. - - Returns - ------- - numpy.ndarray - Converged cosine and sine moments in s^-1. + - ``major``: Non-negative major field in V/m. + - ``ratio``: Minor/major ratio in [0, 1]. + - Returns: converged cosine and sine moments in s^-1. """ if major < rate.field_min: return np.zeros(2) diff --git a/src/solprop/translate/expansion.py b/src/solprop/translate/expansion.py index ae8bc944..b927ffaa 100644 --- a/src/solprop/translate/expansion.py +++ b/src/solprop/translate/expansion.py @@ -490,34 +490,13 @@ def build_expansion( def _joined_weights( integral: PiecewiseChebyshev, endpoint: PiecewiseChebyshev ) -> PiecewiseChebyshev: - """Join integral weights with endpoint differences on their common refinement. - - Parameters - ---------- - integral : PiecewiseChebyshev - Original integrated polynomial on its z intervals. - endpoint : PiecewiseChebyshev - Endpoint polynomial on the same physical span. - - Returns - ------- - PiecewiseChebyshev - Concatenated integral and endpoint-difference weights. - """ + """Join integral weights with endpoint differences on their common refinement.""" origin = endpoint(endpoint.z_span[0]) def sample(z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, "n_z n_terms"]: """Sample the index at the requested position. - Parameters - ---------- - z : float or numpy.ndarray - Positions within the translated span, in metres. - - Returns - ------- - numpy.ndarray - Values of the sampled weight polynomial at the requested positions. + - ``z``: positions within the translated span, in metres. """ return np.stack( [ @@ -668,18 +647,10 @@ def parameter_roundoff_floor( ] steps = [_FLOOR_STEP_FRACTION * float(high - low) for low, high in ranges] - def at(point: Sequence[float]) -> Inexact[np.ndarray, " *value"]: + def _at(point: Sequence[float]) -> Inexact[np.ndarray, " *value"]: """Sample the quantity at one point of the box. - Parameters - ---------- - point : sequence of float - One value per parameter, in axis order. - - Returns - ------- - numpy.ndarray - The quantity there. + - ``point``: one value per parameter, in axis order. """ return np.asarray(value_at(dict(zip(names, point, strict=True)))) @@ -688,7 +659,7 @@ def at(point: Sequence[float]) -> Inexact[np.ndarray, " *value"]: index: [probes[axis][index[axis]] for axis in range(n_params)] for index in np.ndindex(shape) } - centre = np.stack([at(points[index]) for index in np.ndindex(shape)]) + centre = np.stack([_at(points[index]) for index in np.ndindex(shape)]) centre = centre.reshape(*shape, *centre.shape[1:]) magnitude = np.abs(centre) global_envelope = float(magnitude.max()) @@ -708,7 +679,7 @@ def at(point: Sequence[float]) -> Inexact[np.ndarray, " *value"]: for offset in offsets[index[axis]]: shifted = list(point) shifted[axis] += offset * steps[axis] - stencil.append(centre[index] if offset == 0.0 else at(shifted)) + stencil.append(centre[index] if offset == 0.0 else _at(shifted)) noise[index] = np.abs(stencil[0] - 2.0 * stencil[1] + stencil[2]) worst = max(worst, float(np.max(noise / scale))) return worst @@ -819,22 +790,7 @@ def _parameter_ranges( ) -> Float[np.ndarray, "n_params two"]: """Bracket the values each path reaches over the tabulated range. - Parameters - ---------- - paths : collections.abc.Mapping - The varying paths, by name. - z0, z1 : float - Ends of the range in m. - - Returns - ------- - numpy.ndarray - ``(lo, hi)`` per path in the order of `paths`, shape ``(n_params, 2)``. - - Raises - ------ - TranslationError - If the range is empty, or a path does not vary over it. + - ``z0``, ``z1``: ends of the range in m. """ if not z1 > z0: msg = f"need z1 > z0, got z0 = {z0!r}, z1 = {z1!r}" @@ -879,24 +835,10 @@ def _resolve_parameters( ) -> tuple[Inexact[np.ndarray, " n_terms *value"], Int[np.ndarray, " n_params"]] | None: """Raise the degree of each parameter axis until every coefficient tail is small. - Parameters - ---------- - value_at : callable - The sampler. - names : tuple of str - The parameter names, in axis order. - ranges : numpy.ndarray - ``(lo, hi)`` per parameter. - tol : float - Target bound on the uniform relative error. - n_max : int - Highest degree tried on an axis. - - Returns - ------- - tuple or None - The flattened terms and the trimmed degree per parameter, or ``None`` when an - axis is still unresolved at `n_max`. + - ``names``: the parameter names, in axis order. + - ``n_max``: highest degree tried on an axis. + - Returns: the flattened terms and the trimmed degree per parameter, or ``None`` + when an axis is still unresolved at `n_max`. """ n_params = len(names) degrees = [min(INITIAL_DEGREE, n_max) for _ in names] @@ -959,24 +901,8 @@ def _sample_grid( ) -> Inexact[np.ndarray, " *shape"]: """Evaluate the quantity at every point of a tensor grid of parameter values. - Parameters - ---------- - value_at : callable - The sampler. - names : tuple of str - The parameter names, in axis order. - points : sequence of numpy.ndarray - The nodes of each parameter axis. - - Returns - ------- - numpy.ndarray - Samples of shape ``(*node counts, *value_shape)``. - - Raises - ------ - TranslationError - If a sample is not finite, or the value shape or dtype changes between points. + - ``names``: the parameter names, in axis order. + - ``points``: the nodes of each parameter axis. """ shape = tuple(int(axis.size) for axis in points) samples = [] @@ -1015,33 +941,11 @@ def _weight_tables( # noqa: PLR0917 - called positionally ) -> PiecewiseChebyshev: """Tabulate the products of the basis polynomials along the paths. - Parameters - ---------- - paths : collections.abc.Mapping - The varying paths, by name. - names : tuple of str - The parameter names, in axis order. - ranges : numpy.ndarray - ``(lo, hi)`` per parameter. - degrees : numpy.ndarray - The retained degree per parameter. - z0, z1 : float - Ends of the range in m. - tol : float - Target bound on the uniform relative error. - kinks : array_like - Positions that must be interval edges; those outside the range are dropped. - n_max : int - Highest degree tried on an interval. - max_intervals : int - Interval budget. - integrate : bool - Whether to return the cumulative integrals from `z0`. - - Returns - ------- - solprop.translate.chebyshev.PiecewiseChebyshev - Value shape ``(n_terms,)``, float64, with read-only arrays. + - ``names``: the parameter names, in axis order. + - ``z0``, ``z1``: ends of the range in m. + - ``kinks``: positions that must be interval edges; those outside the range are + dropped. + - Returns: value shape ``(n_terms,)``, float64, with read-only arrays. """ def products( @@ -1049,15 +953,7 @@ def products( ) -> Float[np.ndarray, "n_z n_terms"]: """Return the tensor product of the basis polynomials at each position. - Parameters - ---------- - z : numpy.ndarray - Positions in m, shape ``(n_z,)``. - - Returns - ------- - numpy.ndarray - Shape ``(n_z, n_terms)``, the multi-index flattened in C order. + - ``z``: positions in m, shape ``(n_z,)``. """ columns = [ chebyshev_basis.chebvander( @@ -1112,15 +1008,8 @@ def _mapping_noise(ranges: Float[np.ndarray, "n_params two"]) -> float: :data:`solprop.translate.chebyshev.DEFAULT_TOL` unless a range is narrower than about a thousandth of its values. - Parameters - ---------- - ranges : numpy.ndarray - ``(lo, hi)`` per parameter. - - Returns - ------- - float - The floor, relative to the products' scale; zero when every range is empty. + - Returns: the floor, relative to the products' scale; zero when every range is + empty. """ low, high = ranges[:, 0], ranges[:, 1] width = high - low @@ -1139,25 +1028,7 @@ def _mapped( ) -> Float[np.ndarray, " n_z"]: """Map a path's values onto ``[-1, 1]`` over its expansion range. - Parameters - ---------- - values : numpy.ndarray - The path at some positions. - span : numpy.ndarray - ``(lo, hi)`` of the expansion range. - name : str - The path's name, for the message. - - Returns - ------- - numpy.ndarray - The mapped values, clipped to ``[-1, 1]``. - - Raises - ------ - TranslationError - If a value lies more than :data:`RANGE_SLACK` of the range outside it, which - means the bracket is wrong and the series would be extrapolated. + - Returns: the mapped values, clipped to ``[-1, 1]``. """ low, high = float(span[0]), float(span[1]) mapped = (2.0 * np.asarray(values, dtype=np.float64) - low - high) / (high - low) @@ -1175,15 +1046,7 @@ def _mapped( def _readonly(values: np.ndarray) -> np.ndarray: """Return a C-contiguous, read-only copy of an array. - Parameters - ---------- - values : numpy.ndarray - The array to freeze. - - Returns - ------- - numpy.ndarray - The same values, C-contiguous and not writeable. + - Returns: the same values, C-contiguous and not writeable. """ frozen = np.ascontiguousarray(values) frozen.setflags(write=False) @@ -1191,13 +1054,7 @@ def _readonly(values: np.ndarray) -> np.ndarray: def _log_expansion(expansion: ParameterExpansion) -> None: - """Log the expansion's parameters, degrees and size at DEBUG level. - - Parameters - ---------- - expansion : ParameterExpansion - The expansion just built. - """ + """Log the expansion's parameters, degrees and size at DEBUG level.""" if not logger.isEnabledFor(logging.DEBUG): return logger.debug( diff --git a/src/solprop/translate/flux.py b/src/solprop/translate/flux.py index 408cc858..41020a7f 100644 --- a/src/solprop/translate/flux.py +++ b/src/solprop/translate/flux.py @@ -281,35 +281,22 @@ def flux_from_record(record: Mapping[str, object]) -> FluxPlan: coefficients at its own position. """ - def array(name: str, dtype: type) -> np.ndarray: - """Return one stored coefficient array. - - Parameters - ---------- - name : str - The record's key. - dtype : type - The array's dtype. - - Returns - ------- - numpy.ndarray - The array. - """ + def _array(name: str, dtype: type) -> np.ndarray: + """Return one stored coefficient array.""" return _stored(record[name], dtype) - zeros = np.zeros(array("k_squared", complex).size) + zeros = np.zeros(_array("k_squared", complex).size) kappa = record["kappa_sq"] base = SeparableOperator( - k_squared=array("k_squared", complex), - kperp_sq=array("kperp_sq", float), - k_squared_low=array("k_squared_low", complex), - kperp_sq_low=array("kperp_sq_low", float), + k_squared=_array("k_squared", complex), + kperp_sq=_array("kperp_sq", float), + k_squared_low=_array("k_squared_low", complex), + kperp_sq_low=_array("kperp_sq_low", float), frame=zeros, spectral_rate=zeros, prefactor_numerator=zeros.astype(complex), prefactor_clamped=zeros.astype(complex), - active=array("active", bool), + active=_array("active", bool), evanescent_rate=float(cast("float", record["evanescent_rate"])), n_pol=1, # A measure is evaluated where it is asked; its span is never stepped. @@ -341,44 +328,17 @@ def _admission(record: Mapping[str, object]) -> AdiabaticAdmission: The path's structural identity is kept as the text it was recorded as. - Parameters - ---------- - record : collections.abc.Mapping - The ``admission`` entry of a ``flux_plan`` record. - - Returns - ------- - solprop.adiabatic.AdiabaticAdmission - The certificate the run was admitted under. + - ``record``: the ``admission`` entry of a ``flux_plan`` record. """ def number(name: str) -> float: - """Return one stored scalar. - - Parameters - ---------- - name : str - The record's key. - - Returns - ------- - float - The value. - """ + """Return one stored scalar.""" return float(cast("float", record[name])) def optional(name: str) -> float | None: """Return one stored scalar that may be absent. - Parameters - ---------- - name : str - The record's key. - - Returns - ------- - float or None - The value, or None where it was not recorded. + - Returns: the value, or None where it was not recorded. """ value = record.get(name) return None if value is None else float(cast("float", value)) @@ -411,18 +371,6 @@ def _stored(value: object, dtype: type) -> np.ndarray: A record written to JSON or HDF5 and read back nests each array as the ``{"__ndarray__": ..., "__imaginary__": ...}`` form of :func:`solprop.output.schema.serialise`, which a nested spatial record keeps. - - Parameters - ---------- - value : object - An array, a nested list, or its serialised form. - dtype : type - The array's dtype. - - Returns - ------- - numpy.ndarray - The array. """ if isinstance(value, Mapping) and "__ndarray__" in value: real = np.asarray(value["__ndarray__"], dtype=float) diff --git a/src/solprop/translate/free.py b/src/solprop/translate/free.py index 6fc0ad0f..63d957fd 100644 --- a/src/solprop/translate/free.py +++ b/src/solprop/translate/free.py @@ -23,19 +23,9 @@ def _validate_span(n_pol: int, z_max: float, margin: float) -> float: """Validate spatial polarisation and finite positive translation extent. - Parameters - ---------- - n_pol : int - One or two independent Cartesian components. - z_max : float - Run length in m. - margin : float - Fractional nonnegative extension. - - Returns - ------- - float - Extended positive span in m. + - ``z_max``: run length in m. + - ``margin``: fractional nonnegative extension. + - Returns: extended positive span in m. """ if isinstance(n_pol, bool) or n_pol not in (1, 2): msg = "Cartesian polarisation count must be one or two" @@ -50,14 +40,9 @@ def _validate_span(n_pol: int, z_max: float, margin: float) -> float: def _warn_grazing(space: FreeGrid, grid: TimeGrid, data: SeparableOperator) -> None: """Report the first active cutoff inside the split-square uncertainty bound. - Parameters - ---------- - space : solprop.grid.FreeGrid - Cartesian shape for the reported column coordinates. - grid : solprop.grid.TimeGrid - Frequency bins in rad/s and their active mask. - data : solprop.translate.spatial.SeparableOperator - Validated square high/low parts in m^-2. + - ``space``: cartesian shape for the reported column coordinates. + - ``grid``: frequency bins in rad/s and their active mask. + - ``data``: validated square high/low parts in m^-2. """ order = np.argsort(data.kperp_sq, kind="stable") transverse = data.kperp_sq[order] diff --git a/src/solprop/translate/general.py b/src/solprop/translate/general.py index 6c0b520c..90110012 100644 --- a/src/solprop/translate/general.py +++ b/src/solprop/translate/general.py @@ -409,15 +409,7 @@ def __post_init__(self) -> None: self._check_metric() def _check_moving(self) -> None: - """Copy, validate and freeze a moving guide's flow expansions. - - Raises - ------ - TranslationError - If only one of the two is given, a split block is given beside them, a - shape disagrees with the tables' sections, degree, channels or grid, or - a value is not finite, or a measurement is negative. - """ + """Copy, validate and freeze a moving guide's flow expansions.""" generator, link = self.moving_generator, self.moving_connection for name, value in ( ("moving_error", self.moving_error), @@ -481,15 +473,7 @@ def moving(self) -> bool: return self.moving_generator is not None def _check_coupled(self) -> None: - """Copy, validate and freeze a declared split block's generator. - - Raises - ------ - TranslationError - If the block is not square on this route's channels and grid, is not - finite, is diagonal after all, or the reference channel is not one of - them. - """ + """Copy, validate and freeze a declared split block's generator.""" block = self.coupled_generator if block is None: if self.reference_channel: @@ -532,14 +516,7 @@ def _check_coupled(self) -> None: object.__setattr__(self, "reference_channel", int(self.reference_channel)) def _check_metric(self) -> None: - """Bind a supplied dense metric to the reference it is meant to describe. - - Raises - ------ - TranslationError - If the metric is not dense, is on another basis, or is tabulated on - grids other than the tables' own. - """ + """Bind a supplied dense metric to the reference it is meant to describe.""" metric = self.metric_tables if metric is None: return @@ -648,10 +625,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. """ responses = self.responses return "GeneralModalRoute: the seam leaves of one general modal guide", [ @@ -940,25 +914,7 @@ def _moving_route( whole of each channel's own normalisation, which a capillary carries in its flow's diagonal instead. - Parameters - ---------- - guide : solprop.modes.guide.ModalGuide - A moving guide, whose sampling carries a path. - grid : solprop.grid.TimeGrid - The run's field grid. - z_max : float - Where the run ends, m. - launch : numpy.ndarray or None - The launched spectrum, for the occupied-support gate. - gram_atol : float - The basis budget. - regional : _Regional - The regions' compiled responses (:func:`_regional_responses`). - - Returns - ------- - GeneralModalRoute - The route, carrying the moving maps and flow. + - ``z_max``: where the run ends, m. """ # Imported here: modal_path imports this module's siblings only, but keeping the # moving machinery out of the snapshot route's import path keeps that route's @@ -1218,25 +1174,7 @@ def _warn_over_the_active_band( grid: TimeGrid, sampled: Complex[np.ndarray, "n_modes n_modes n_omega"], ) -> None: - """Warn where the interpolated basis leaves its budget inside the active band. - - Parameters - ---------- - active : float - The interpolated departure maximised over ``grid.active_mask``. - gram_atol : float - The budget the occupied departure is gated against. - grid : solprop.grid.TimeGrid - The run's grid, which names the worst bin's wavelength. - sampled : numpy.ndarray - The sampled flux Gram, shape ``(n_modes, n_modes, n_omega)``, from which - the worst bin is located. - - Warns - ----- - solprop.exceptions.GeneralModalBasisWarning - Naming the worst bin, its wavelength and the fix. - """ + """Warn where the interpolated basis leaves its budget inside the active band.""" if active <= gram_atol: return modes = sampled.shape[0] @@ -1263,23 +1201,7 @@ def _sampled_metric( tables: ModalTransformTables, sampled: Complex[np.ndarray, "n_modes n_modes n_omega"], ) -> ModalMetricTables: - """Build the dense metric of a declared basis from the table's own flux Gram. - - Parameters - ---------- - table : solprop.translate.modal_tables.DenseModalTable - The tracked reference, already in the declared basis. - tables : solprop.translate.modal_tables.ModalTransformTables - The flat record, whose coarse grid and positions the metric shares. - sampled : numpy.ndarray - The reference's flux Gram at those coarse bins, as - :func:`interpolated_gram` builds it. - - Returns - ------- - solprop.translate.coupled.ModalMetricTables - The dense metric of the declared basis. - """ + """Build the dense metric of a declared basis from the table's own flux Gram.""" coarse = tables.omega logger.debug( "dense modal metric: %d channels on %d coarse bins, largest departure from " @@ -1597,16 +1519,8 @@ def guide_identity(guide: ModalGuide) -> Mapping[str, JsonValue]: def _gas_identity(gas: GasKerr) -> dict[str, JsonValue]: """Return a region's gas fill as a run records it. - Parameters - ---------- - gas : solprop.modes.material.GasKerr - The fill. - - Returns - ------- - dict - Each population's label, species, density in m^-3 and optmat record - digests; the electronic law the run takes is recorded with its policy. + - Returns: each population's label, species, density in m^-3 and optmat record + digests; the electronic law the run takes is recorded with its policy. """ return { "populations": [ @@ -1631,17 +1545,9 @@ def _gas_identity(gas: GasKerr) -> dict[str, JsonValue]: def _dispersive_identity(leaf: DispersiveKerrTerms) -> dict[str, JsonValue]: """Return a general modal route's dispersive channels as a run records them. - Parameters - ---------- - leaf : solprop.translate.responses.DispersiveKerrTerms - The compiled channels. - - Returns - ------- - dict - The evaluator, the sector, the tensor approximation and each channel's - label and node region; the models and their support records are the - material manifest's. + - Returns: the evaluator, the sector, the tensor approximation and each channel's + label and node region; the models and their support records are the material + manifest's. """ assert leaf.regions is not None return { @@ -1660,17 +1566,9 @@ def _raman_identity( ) -> dict[str, JsonValue]: """Return a region's delayed response parameters as a run records them. - Parameters - ---------- - parameters : solprop.physdata.raman_types.LinAgrawalRaman or SilicaBundle - The declared time constants and fraction, or an optmat tensor bundle. - - Returns - ------- - dict - The declared parameters field by field; an optmat bundle by its name, its - digest and its Raman fraction, the digest identifying its numbers (the - bundle's own model is recorded in the run's material manifest). + - Returns: the declared parameters field by field; an optmat bundle by its name, its + digest and its Raman fraction, the digest identifying its numbers (the bundle's + own model is recorded in the run's material manifest). """ if isinstance(parameters, SilicaBundle): return { @@ -1873,18 +1771,8 @@ def modal_identity( def _check_active_band(span: tuple[float, float], grid: TimeGrid) -> None: """Refuse a run whose active band reaches outside the guide's own knots. - Parameters - ---------- - span : tuple of float - Lowest and highest knot the guide was solved at, rad/s. - grid : solprop.grid.TimeGrid - The run's grid, whose active mask is the band that must be covered. - - Raises - ------ - TranslationError - Naming both bands, in rad/s and in wavelength, and the factor in frequency - the active band spans. + - ``span``: lowest and highest knot the guide was solved at, rad/s. + - ``grid``: the run's grid, whose active mask is the band that must be covered. """ active = grid.omega[grid.active_mask] low, high = float(active[0]), float(active[-1]) @@ -1926,36 +1814,15 @@ def _regional_responses( becomes a channel of SEP 6's dispersive cubic, in the sector `form` names, with the region as its node region. - Parameters - ---------- - guide : solprop.modes.guide.ModalGuide - The guide whose regions own the responses. - grid : solprop.grid.TimeGrid - The run's grid, which supplies the fine temporal quadrature and the fine - spectral layout. - omega_reference : float - Frequency in rad/s at which a declared ``n2`` becomes an absolute ``chi3``, - and the carrier of a gas fill under a carrier policy. - form : {'analytic', 'real'} - Which instantaneous cubic the compiled tensor is; for a dispersive channel, - the number-conserving sector or the full cubic. - selection : solprop.physdata.selection.MaterialSelection or None - The run's material selection; ``None`` is ``MaterialSelection()``. - span : float - Length in m the density tables of the dispersive channels cover. - - Returns - ------- - _Regional - The constant cubics per responding region, in the guide's own region order - (``None`` when no region has one), and the dispersive channels (``None`` - without a gas region under a dispersive policy). - - Raises - ------ - TranslationError - If a gas fill's species has no Kerr data, or no hyperpolarizability model - under a dispersive policy. + - ``grid``: the run's grid, which supplies the fine temporal quadrature and the fine + spectral layout. + - ``omega_reference``: frequency in rad/s at which a declared ``n2`` becomes an + absolute ``chi3``, and the carrier of a gas fill under a carrier policy. + - ``selection``: the run's material selection; ``None`` is ``MaterialSelection()``. + - ``span``: length in m the density tables of the dispersive channels cover. + - Returns: the constant cubics per responding region, in the guide's own region + order (``None`` when no region has one), and the dispersive channels (``None`` + without a gas region under a dispersive policy). """ active = grid.omega[grid.active_mask] band = (float(active[0]), float(active[-1])) @@ -2040,29 +1907,9 @@ def _gas_responses( ) -> list[tuple[KerrResponse | DispersiveKerrResponse, float]]: """Resolve a region's gas fill under the run's electronic policy. - Parameters - ---------- - region : solprop.modes.material.MaterialRegion - The region, for its name. - gas : solprop.modes.material.GasKerr - Its fill. - selection : solprop.physdata.selection.MaterialSelection - The run's material selection. - omega_reference : float - The run's carrier in rad/s. - form : {'analytic', 'real'} - The cubic's form: ``"real"`` keeps the full cubic. - - Returns - ------- - list of tuple - Per population, its electronic response, labelled - ``":"``, and its number density in m^-3. - - Raises - ------ - TranslationError - Naming the region and the population whose data the policy cannot take. + - ``omega_reference``: the run's carrier in rad/s. + - Returns: per population, its electronic response, labelled + ``":"``, and its number density in m^-3. """ resolved: list[tuple[KerrResponse | DispersiveKerrResponse, float]] = [] for component, density in zip(gas.fill.components, gas.densities(), strict=True): @@ -2092,13 +1939,6 @@ def _log_gas_regions( A general modal guide's node field has three Cartesian components, so the any-polarisation band edge applies. The run is never refused for the frequencies its field occupies. - - Parameters - ---------- - responses : sequence of DispersiveKerrResponse - The gas regions' dispersive channels. - grid : solprop.grid.TimeGrid - The run's grid, whose active band is classified. """ for response in responses: regions = response.model.regions( @@ -2140,18 +1980,6 @@ def _check_harmonic_room(grid: TimeGrid, form: KerrForm) -> None: ``0.136 n_t`` bins on the default grid. The analytic-signal form generates no harmonic and is not gated here. - - Parameters - ---------- - grid : solprop.grid.TimeGrid - The run's field grid. - form : {'analytic', 'real'} - Which instantaneous cubic the compiled tensor is. - - Raises - ------ - TranslationError - Naming both sizes and the oversampling that fixes it. """ if form != "real" or grid.n_to >= 2 * grid.n_t: return @@ -2191,20 +2019,8 @@ def _check_declared_n2( here, naming the region and both intervals, rather than surfacing later as the material's own generic refusal from inside the leaf compile. - Parameters - ---------- - region : solprop.modes.material.MaterialRegion - A region that declares a Kerr response. - band : tuple of float - Lowest and highest active frequency of the run, rad/s. - omega_reference : float - The frequency an undeclared conversion would use, rad/s. - - Raises - ------ - TranslationError - Naming the region, the measured spread and the two ways to declare it, or - naming a declared ``at`` outside the region's own index model. + - ``band``: lowest and highest active frequency of the run, rad/s. + - ``omega_reference``: the frequency an undeclared conversion would use, rad/s. """ kerr = region.kerr if kerr is None or kerr.n2 is None: diff --git a/src/solprop/translate/ionisation.py b/src/solprop/translate/ionisation.py index e47d7935..f0560cd4 100644 --- a/src/solprop/translate/ionisation.py +++ b/src/solprop/translate/ionisation.py @@ -404,17 +404,7 @@ def clear_rate_table_cache() -> None: def _validate_build(field_range: tuple[float, float], rtol: float) -> None: """Check the build arguments. - Parameters - ---------- - field_range : tuple of float - Lower and upper field amplitude in V/m. - rtol : float - Requested relative tolerance. - - Raises - ------ - TranslationError - Naming the offending argument. + - ``field_range``: lower and upper field amplitude in V/m. """ low, high = float(field_range[0]), float(field_range[1]) if not (math.isfinite(low) and math.isfinite(high) and 0.0 < low < high): @@ -441,23 +431,7 @@ def _usable_range( straddled it would spend its whole budget there. Above ``field_max`` the table returns its last value, which is the ceiling to within one sample. - Parameters - ---------- - rate : IonisationRate - The rate to tabulate. - field_range : tuple of float - Lower and upper field amplitude in V/m, already validated. - - Returns - ------- - tuple of float - The field range to build over. - - Raises - ------ - TranslationError - If nothing is left of the range: the rate is zero throughout, it is held from - below the first field, or it is at its ceiling from the first field. + - ``field_range``: lower and upper field amplitude in V/m, already validated. """ # The margin above the threshold is what makes the lowest knot survive the round # trip through log and exp: the rate is exactly zero below the threshold, and one @@ -506,19 +480,8 @@ def _fit( ) -> tuple[Float[np.ndarray, " n_table"], Float[np.ndarray, " n_deg n_intervals"]]: """Fit the cubic spline of ``log W`` on ``n_intervals`` uniform intervals in log E. - Parameters - ---------- - rate : IonisationRate - The rate to tabulate. - field_min, field_max : float - Ends of the range in V/m. - n_intervals : int - Number of knot intervals. - - Returns - ------- - tuple - The knot values of ``log W`` and the spline coefficients. + - ``field_min``, ``field_max``: ends of the range in V/m. + - Returns: the knot values of ``log W`` and the spline coefficients. """ log_field = np.linspace( math.log(field_min), math.log(field_max), n_intervals + 1, dtype=np.float64 @@ -531,22 +494,7 @@ def _fit( def _probe_points( log_field_min: float, d_log_field: float, n_intervals: int ) -> Float[np.ndarray, " n_probe"]: - """Return the logarithmic field amplitudes at which the interpolant is checked. - - Parameters - ---------- - log_field_min : float - Natural logarithm of the lowest field amplitude. - d_log_field : float - Knot spacing in ``log E``. - n_intervals : int - Number of knot intervals. - - Returns - ------- - numpy.ndarray - Probe positions in ``log E``, three per interval, shape ``(n_probe,)``. - """ + """Return the logarithmic field amplitudes at which the interpolant is checked.""" left = log_field_min + d_log_field * np.arange(n_intervals, dtype=np.float64) return (left[:, None] + d_log_field * np.array(_PROBE_OFFSETS)[None, :]).ravel() @@ -651,20 +599,7 @@ def build_rate_table( def _finalise(table: RateTable, error: float) -> RateTable: - """Return a copy of the table with the measured error recorded. - - Parameters - ---------- - table : RateTable - The table built at the winning knot count. - error : float - The largest deviation measured on the probe grid. - - Returns - ------- - RateTable - The same table, with :attr:`RateTable.error` set. - """ + """Return a copy of the table with the measured error recorded.""" return RateTable( log_rate=table.log_rate, coefficients=table.coefficients, @@ -686,16 +621,6 @@ def _digest(key: tuple[object, ...]) -> str: Through :mod:`hashlib` rather than :func:`hash`, whose value for a string is salted per process: the digest names a cache entry that may outlive this process. - - Parameters - ---------- - key : tuple - The output of :func:`rate_table_key`. - - Returns - ------- - str - Sixteen hexadecimal characters. """ return hashlib.blake2b(repr(key).encode("utf-8"), digest_size=8).hexdigest() @@ -703,14 +628,7 @@ def _digest(key: tuple[object, ...]) -> str: def _log_summary(table: RateTable, rate: IonisationRate, elapsed: float) -> None: """Record the table's size, span and accuracy at debug level. - Parameters - ---------- - table : RateTable - The finished table. - rate : IonisationRate - The rate it came from. - elapsed : float - Wall time of the build in seconds. + - ``elapsed``: wall time of the build in seconds. """ if logger.isEnabledFor(logging.DEBUG): logger.debug( diff --git a/src/solprop/translate/linop.py b/src/solprop/translate/linop.py index 628dc21b..ffb9d8e8 100644 --- a/src/solprop/translate/linop.py +++ b/src/solprop/translate/linop.py @@ -389,12 +389,6 @@ def _check_representation(self) -> None: A kind that is neither is caught by the two presence checks together and named by their message, so it needs no membership test of its own -- which could not be reached anyway from a constructor whose annotation is a literal. - - Raises - ------ - TranslationError - If the wrong member is present for the kind named, or an expansion's scalar - tables disagree with the breakpoints, degrees and kinks beside them. """ expansion = self.phi_expansion if (self.separable is not None) != (self.phi_representation == "separable"): @@ -617,23 +611,9 @@ def _value_shape( The operator's values keep a leading broadcast axis of one, the layout the native build reads; the state itself has none (SEP 47 section 6.2). - Parameters - ---------- - operator : numpy.ndarray - ``D`` as ``(n_omega,)``, in the state's ``(n_modes, n_pol, n_omega)`` or - already in the value layout. - n_pol : int - Polarisation components to broadcast a one-dimensional operator over. - - Returns - ------- - numpy.ndarray - The operator with four axes. - - Raises - ------ - TranslationError - If the array has neither one, three nor four axes, or `n_pol` is not 1 or 2. + - ``operator``: ``D`` as ``(n_omega,)``, in the state's ``(n_modes, n_pol, + n_omega)`` or already in the value layout. + - Returns: the operator with four axes. """ values = np.asarray(operator) if values.ndim == 4: @@ -711,15 +691,7 @@ def tabulate( def _readonly(values: np.ndarray) -> np.ndarray: """Return a C-contiguous, read-only copy of an array. - Parameters - ---------- - values : numpy.ndarray - The array to freeze. - - Returns - ------- - numpy.ndarray - The same values, C-contiguous and not writeable. + - Returns: the same values, C-contiguous and not writeable. """ frozen = np.ascontiguousarray(values) frozen.setflags(write=False) @@ -799,19 +771,8 @@ def _zero_edges( already, or when the correction would change the interval by more than `tol` of its largest coefficient: that is not rounding, and the domain check decides. - Parameters - ---------- - row : numpy.ndarray - The interval's Chebyshev coefficients up to its degree, at least two. - left, right : bool - Whether the path is exactly zero at the interval's left or right end. - tol : float - The table's tolerance, relative to the interval's largest coefficient. - - Returns - ------- - numpy.ndarray - The corrected coefficients, or `row` itself when nothing is corrected. + - ``row``: the interval's Chebyshev coefficients up to its degree, at least two. + - Returns: the corrected coefficients, or `row` itself when nothing is corrected. """ ends = chebyshev_basis.chebval(np.array([-1.0, 1.0]), row) if (not left or ends[0] == 0.0) and (not right or ends[1] == 0.0): @@ -945,22 +906,7 @@ def constant_phi( def _checked_span(z_max: float, margin: float) -> float: """Return the end of the tabulated range, validating both arguments. - Parameters - ---------- - z_max : float - Length the run covers, m. - margin : float - Fraction of `z_max` to extend beyond it. - - Returns - ------- - float - ``z_max (1 + margin)``. - - Raises - ------ - TranslationError - If `z_max` is not positive and finite or `margin` is negative or not finite. + - ``z_max``: length the run covers, m. """ if not (math.isfinite(z_max) and z_max > 0.0): msg = f"z_max must be positive and finite, got {z_max!r}" @@ -972,23 +918,7 @@ def _checked_span(z_max: float, margin: float) -> float: def _as_modes(mode: Mode | Sequence[Mode]) -> tuple[Mode, ...]: - """Return the modes of a problem as a tuple, accepting one mode or a set. - - Parameters - ---------- - mode : Mode or sequence of Mode - One mode, or the ordered set of a multimode problem. - - Returns - ------- - tuple of Mode - The modes, in order. - - Raises - ------ - TranslationError - If an empty set is given. - """ + """Return the modes of a problem as a tuple, accepting one mode or a set.""" modes = tuple(mode) if isinstance(mode, Sequence) else (mode,) if not modes: msg = "a problem needs at least one mode" @@ -1187,79 +1117,30 @@ def _dispersion_phi( mode whose parameters do not resolve; the only approximation anywhere in ``Phi`` is the interpolation of ``D`` (``docs/guide/numerics/linear-operator.md``). - Parameters - ---------- - mode : Mode or sequence of Mode - The mode, or the ordered set of a multimode problem. Only ``neff``, - ``dneff_domega``, the declared ``core_radius`` and ``density`` paths and, for - the expansion, ``at_parameters`` are used. - grid : solprop.grid.TimeGrid - The grid; supplies the frequency axis, the active band and the layout. - frame : solprop.linear.Frame - The co-moving frame, usually from :meth:`solprop.linear.Frame.comoving`. - z_max : float - Length the run covers, m, positive and finite. - n_pol : int, optional - Polarisation components, 1 or 2. ``D`` is the same for both components of one - mode, and is stored for each so that a birefringent mode needs no new layout. - absorber_rate : numpy.ndarray or None, optional - Spectral absorber rate in 1/m, shape ``(n_omega,)``, folded into ``D`` before - the integration so the collar is exact as a rate. - loss : solprop.linear.LossPolicy, optional - What to do with an attenuation too large to represent. Applied to ``D`` before - ``Phi`` is integrated, never to an accumulated exponent afterwards. - margin : float, optional - Fraction of `z_max` the table extends beyond the end. Default - :data:`DEFAULT_MARGIN`. - tol : float, optional - Target bound on the uniform error of the table relative to the value scale. The - request is raised to :data:`ROUNDOFF_SAFETY` times the measured round-off floor - of ``D`` when that is larger, because no interpolant can resolve a function - below the noise of its own evaluation, and relaxed further, by decades up to - :data:`MAX_TOL`, if the interval budget is exhausted anyway - (``docs/dev/stepper.md`` section 4, step 4). Both are logged. - n_max : int, optional - Highest Chebyshev degree tried on an interval, or on a parameter, before the - interval is bisected or the expansion gives up. - max_intervals : int, optional - Interval budget; exceeding it sets the fallback flag and logs a warning. - representation : {'auto', 'expansion', 'table'}, optional - Which representation to build. ``"auto"``, the default, prefers the expansion; - ``"table"`` forces the z table, which is what an equivalence test between the - two needs; ``"expansion"`` refuses to fall back. A ``z_constant`` mode set takes - :func:`constant_phi` whatever this says, since that path is exact. - reuse : ParameterExpansion or None, optional - Coefficients of the identical operator function on the same grid, frame and - fixed parameters. The caller must establish function identity. Covered paths - receive new scalar weights; other paths rebuild normally, with a logged reason. - kinks : sequence of float or numpy.ndarray, optional - Further hard kinks in m beside the mode set's breakpoints, as for - :func:`build_phi`. - - Returns - ------- - LinearOperator - Section 3 of the kernel contract, covering ``[0, z_max (1 + margin)]``. - - Raises - ------ - TranslationError - If `z_max`, `margin`, `representation` or the mode set is invalid, or - ``representation="expansion"`` is asked for and cannot be built. - - Examples - -------- - >>> from solprop.grid import EnvelopeGrid - >>> from solprop.linear import Frame - >>> from solprop.nonlinear.gnlse import SpecifiedMode - >>> from solprop.translate.linop import build_phi - >>> grid = EnvelopeGrid.from_limits(1.0, 1030e-9, (700e-9, 2e-6), 2e-12) - >>> mode = SpecifiedMode.from_taylor( - ... [0.0, 0.0, 2e-26], omega_ref=grid.omega0 - ... ) - >>> table = build_phi(mode, grid, Frame.comoving(grid), z_max=1.0) - >>> table.phi_representation, table.n_intervals - ('constant', 1) + - ``grid``: the grid; supplies the frequency axis, the active band and the layout. + - ``z_max``: length the run covers, m, positive and finite. + - ``n_pol``: polarisation components, 1 or 2. ``D`` is the same for both components + of one mode, and is stored for each so that a birefringent mode needs no new + layout. + - ``absorber_rate``: spectral absorber rate in 1/m, shape ``(n_omega,)``, folded + into ``D`` before the integration so the collar is exact as a rate. + - ``tol``: target bound on the uniform error of the table relative to the value + scale. The request is raised to :data:`ROUNDOFF_SAFETY` times the measured + round-off floor of ``D`` when that is larger, because no interpolant can resolve a + function below the noise of its own evaluation, and relaxed further, by decades up + to :data:`MAX_TOL`, if the interval budget is exhausted anyway + (``docs/dev/stepper.md`` section 4, step 4). Both are logged. + - ``representation``: which representation to build. ``"auto"``, the default, + prefers the expansion; ``"table"`` forces the z table, which is what an + equivalence test between the two needs; ``"expansion"`` refuses to fall back. A + ``z_constant`` mode set takes :func:`constant_phi` whatever this says, since that + path is exact. + - ``reuse``: coefficients of the identical operator function on the same grid, frame + and fixed parameters. The caller must establish function identity. Covered paths + receive new scalar weights; other paths rebuild normally, with a logged reason. + - ``kinks``: further hard kinks in m beside the mode set's breakpoints, as for + :func:`build_phi`. + - Returns: section 3 of the kernel contract, covering ``[0, z_max (1 + margin)]``. """ _checked_representation(representation) span = _checked_span(z_max, margin) @@ -1372,35 +1253,13 @@ def _weight_sampler( ) -> Callable[[float], Float[np.ndarray, "n_modes n_pol n_omega"]]: """Sample the prefactor's real index; unused spectral bins have unit weight. - Parameters - ---------- - modes : sequence of solprop.modes.protocol.Mode - Modes in canonical state order. - grid : solprop.grid.TimeGrid - Temporal grid defining frequency and Parseval weights. - n_pol : int - Number of state polarisations, one or two. - index : float or None - Constant index override, or None for the physical effective index. - - Returns - ------- - callable - Positive real index in shape (n_modes, n_pol, n_omega) at one z. + - ``index``: constant index override, or None for the physical effective index. """ def sample(z: float) -> Float[np.ndarray, "n_modes n_pol n_omega"]: """Sample the index at the requested position. - Parameters - ---------- - z : float or numpy.ndarray - Positions within the translated span, in metres. - - Returns - ------- - numpy.ndarray - Values of the sampled weight polynomial at the requested positions. + - ``z``: positions within the translated span, in metres. """ weight = np.ones((len(modes), n_pol, grid.n_omega)) for number, mode in enumerate(modes): @@ -1461,15 +1320,7 @@ def value_at( ) -> Float[np.ndarray, "n_modes n_pol n_omega"]: """Sample the weight inside the declared parameter box. - Parameters - ---------- - values : collections.abc.Mapping - Physical parameter values inside the declared expansion box. - - Returns - ------- - numpy.ndarray - Weight values in the mode and frequency layout. + - Returns: weight values in the mode and frequency layout. """ return _weight_sampler( [mode.at_parameters(values) for mode in frozen], grid, n_pol, index @@ -1485,25 +1336,18 @@ def value_at( power_weight_expansion=expansion, ) - def samples( + def _samples( z: Float[np.ndarray, " n_z"], ) -> Float[np.ndarray, "n_z n_modes n_pol n_omega"]: """Stack weight values along the position axis. - Parameters - ---------- - z : float or numpy.ndarray - Positions within the translated span, in metres. - - Returns - ------- - numpy.ndarray - Power weights with a leading position axis. + - ``z``: positions within the translated span, in metres. + - Returns: power weights with a leading position axis. """ return np.stack([sample(float(one)) for one in z]) table = build( - samples, + _samples, *linear.z_span, tol=DEFAULT_TOL, mandatory_breakpoints=linear.hard_kinks, @@ -1528,13 +1372,7 @@ class _PowerWeightedProblem(Protocol): @property def linear(self) -> LinearOperator: - """Return the translated linear group. - - Returns - ------- - LinearOperator - Operator and optional modal power-weight rows. - """ + """Return the translated linear group.""" ... @@ -1658,35 +1496,14 @@ def _log_weight_sampler( ) -> _OperatorAt: """Sample the real endpoint exponent on the active band, zero elsewhere. - Parameters - ---------- - modes : sequence of solprop.modes.protocol.Mode - Modes in canonical state order. - grid : solprop.grid.TimeGrid - Temporal grid defining frequency and Parseval weights. - n_pol : int - Number of state polarisations, one or two. - index : float or None - Constant index override, or None for the physical effective index. - - Returns - ------- - callable - Minus one half the logarithmic weight in canonical state shape. + - ``index``: constant index override, or None for the physical effective index. + - Returns: minus one half the logarithmic weight in canonical state shape. """ def sample(z: float) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"]: """Sample the index at the requested position. - Parameters - ---------- - z : float or numpy.ndarray - Positions within the translated span, in metres. - - Returns - ------- - numpy.ndarray - Values of the sampled weight polynomial at the requested positions. + - ``z``: positions within the translated span, in metres. """ columns = np.zeros((1, len(modes), n_pol, grid.n_omega), dtype=complex) for number, mode in enumerate(modes): @@ -1706,21 +1523,7 @@ def _power_phi( ) -> LinearOperator: """Add the exact cumulative power conversion without changing constant Phi. - Parameters - ---------- - linear : LinearOperator - Cumulative dispersion operator and its representation. - modes : sequence of solprop.modes.protocol.Mode - Modes in canonical state order. - grid : solprop.grid.TimeGrid - Temporal grid defining frequency and Parseval weights. - index : float or None - Constant index override, or None for the physical effective index. - - Returns - ------- - LinearOperator - Operator including the exact cumulative logarithmic term. + - ``index``: constant index override, or None for the physical effective index. """ if linear.phi_representation == "constant" or index is not None: return linear @@ -1732,15 +1535,7 @@ def value_at( ) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"]: """Sample the weight inside the declared parameter box. - Parameters - ---------- - values : collections.abc.Mapping - Physical parameter values inside the declared expansion box. - - Returns - ------- - numpy.ndarray - Weight values in the mode and frequency layout. + - Returns: weight values in the mode and frequency layout. """ return _log_weight_sampler( [mode.at_parameters(values) for mode in frozen], grid, n_pol, index @@ -1836,27 +1631,20 @@ def endpoint_phi( ) entrance = sample(linear.z_span[0]) - def correction( + def _correction( z: Float[np.ndarray, " n_z"], ) -> Complex[np.ndarray, "n_z n_batch n_modes n_pol n_omega"]: """Sample the endpoint function along z. - Parameters - ---------- - z : float or numpy.ndarray - Positions within the translated span, in metres. - - Returns - ------- - numpy.ndarray - Endpoint values with a leading position axis. + - ``z``: positions within the translated span, in metres. + - Returns: endpoint values with a leading position axis. """ return np.stack([sample(float(one)) for one in z]) # Resolve the function itself before subtracting its entrance value. A relative # tolerance on the vanishing endpoint difference would chase round-off at z=0. endpoint = build( - correction, + _correction, *linear.z_span, tol=tol, mandatory_breakpoints=linear.phi_breakpoints[1:-1], @@ -1886,18 +1674,6 @@ def _add_phi_tables( narrowest width that holds every interval, and the padding is exact zeros, which a Clenshaw pass reproduces bit for bit -- but `degrees` is per interval, as the field documents, rather than the maximum everywhere. - - Parameters - ---------- - integral : PiecewiseChebyshev - Original integrated polynomial on its z intervals. - endpoint : PiecewiseChebyshev - Endpoint polynomial on the same physical span. - - Returns - ------- - PiecewiseChebyshev - Sum of both polynomials on their common refinement. """ width = max(integral.coefficients.shape[1], endpoint.coefficients.shape[1]) coefficients = np.zeros( @@ -1934,16 +1710,6 @@ def _checked_representation(representation: str) -> None: Its parameter is a plain ``str`` rather than the literal alias, because the point of the check is the production run, where no runtime type checker stands in front of it and a misspelt choice would otherwise be taken for "not the z table". - - Parameters - ---------- - representation : str - What the caller asked for. - - Raises - ------ - TranslationError - If it is not one of :data:`RepresentationChoice`. """ if representation not in ("auto", "expansion", "table"): msg = ( @@ -1969,37 +1735,9 @@ def _phi_expansion( # noqa: PLR0917 - called positionally ) -> ParameterExpansion | None: """Try to expand ``Phi`` in the scalar paths the mode set agrees on. - Parameters - ---------- - modes : sequence of Mode - The mode set, in order. - grid : solprop.grid.TimeGrid - The grid. - frame : solprop.linear.Frame - The co-moving frame. - n_pol : int - Polarisation components. - loss : solprop.linear.LossPolicy - The loss limit. - absorber_rate : numpy.ndarray or None - The spectral absorber rate. - span : float - End of the tabulated range, m. - tol : float - Target bound on the uniform relative error, already raised to the round-off - floor. - kinks : sequence of float - The paths' interior breakpoints, mandatory edges of the scalar tables. - n_max : int - Highest degree tried on a parameter. - max_intervals : int - Interval budget for the scalar tables. - - Returns - ------- - solprop.translate.expansion.ParameterExpansion or None - The expansion, or ``None`` when the mode set does not declare a usable - parameter set or a parameter does not resolve. Either way the reason is logged. + - ``span``: end of the tabulated range, m. + - Returns: the expansion, or ``None`` when the mode set does not declare a usable + parameter set or a parameter does not resolve. Either way the reason is logged. """ varying = parameter_paths(modes, 0.0, span) if varying is None: @@ -2052,21 +1790,8 @@ def _expansion_operator( ) -> LinearOperator: """Wrap a resolved expansion as section 3 of the contract. - Parameters - ---------- - expansion : solprop.translate.expansion.ParameterExpansion - The expansion of ``Phi``, with integrated weights. - operator_at : callable - ``z -> D(z)``, for the debug-level check against the quadrature. - span : float - End of the tabulated range, m. - beta0, beta1 : solprop.translate.chebyshev.PiecewiseChebyshev - The frame quantities, for the record. - - Returns - ------- - LinearOperator - With ``phi_representation`` ``"expansion"``. + - ``operator_at``: ``z -> D(z)``, for the debug-level check against the quadrature. + - ``span``: end of the tabulated range, m. """ weights = expansion.weights table = LinearOperator( @@ -2110,30 +1835,11 @@ def _z_table_operator( ) -> LinearOperator: """Build the version-2 z table, the general representation. - Parameters - ---------- - operator_at : callable - ``z -> D(z)`` in the canonical layout. - paths : sequence of solprop.profiles.ZProfile - The declared scalar paths, resolved first because they carry the structure. - span : float - End of the tabulated range, m. - tol : float - Target bound on the uniform relative error, already raised to the round-off - floor; relaxed by decades up to :data:`MAX_TOL` if the budget is exhausted. - kinks : sequence of float - The paths' interior breakpoints, mandatory edges. - n_max : int - Highest degree tried on an interval before it is bisected. - max_intervals : int - Interval budget. - beta0, beta1 : solprop.translate.chebyshev.PiecewiseChebyshev - The frame quantities, for the record. - - Returns - ------- - LinearOperator - With ``phi_representation`` ``"table"``. + - ``operator_at``: ``z -> D(z)`` in the canonical layout. + - ``span``: end of the tabulated range, m. + - ``tol``: target bound on the uniform relative error, already raised to the + round-off floor; relaxed by decades up to :data:`MAX_TOL` if the budget is + exhausted. """ samplers = [path.__call__ for path in paths] @@ -2142,18 +1848,7 @@ def sample( ) -> Complex[np.ndarray, "n_z n_batch n_modes n_pol n_omega"]: """Return the operator at every position of a Chebyshev node set. - Parameters - ---------- - z : numpy.ndarray - Positions along the waveguide, m, shape ``(n_z,)``. - *_ : object - The path values the builder resolved; the operator is evaluated from ``z`` - directly, so they are not used. - - Returns - ------- - numpy.ndarray - ``D`` at each position, shape ``(n_z, 1, n_modes, n_pol, n_omega)``. + - ``z``: positions along the waveguide, m, shape ``(n_z,)``. """ return np.stack([operator_at(float(one)) for one in z]) @@ -2230,39 +1925,15 @@ def _operator_sampler( # noqa: PLR0917 - called positionally ) -> _OperatorAt: """Return a callable giving ``D`` in the canonical layout at one position. - Parameters - ---------- - modes : sequence of Mode - The mode set, in order. - grid : solprop.grid.TimeGrid - The grid. - frame : solprop.linear.Frame - The co-moving frame. - n_pol : int - Polarisation components; ``D`` is repeated over them. - loss : solprop.linear.LossPolicy - The loss limit, applied before integration. - absorber_rate : numpy.ndarray or None - The spectral absorber rate, or ``None``. - - Returns - ------- - callable - ``z -> D`` of shape ``(1, n_modes, n_pol, n_omega)``. + - ``absorber_rate``: the spectral absorber rate, or ``None``. + - Returns: ``z -> D`` of shape ``(1, n_modes, n_pol, n_omega)``. """ def operator_at(z: float) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"]: """Return the linear operator at one position, in the canonical layout. - Parameters - ---------- - z : float - Position along the waveguide, m. - - Returns - ------- - numpy.ndarray - ``D(omega, z)`` in 1/m, shape ``(1, n_modes, n_pol, n_omega)``. + - ``z``: position along the waveguide, m. + - Returns: ``D(omega, z)`` in 1/m, shape ``(1, n_modes, n_pol, n_omega)``. """ shared = _shared_frame(modes[0], frame, z) columns = [ @@ -2293,28 +1964,10 @@ def _operator_of_parameters( # noqa: PLR0917 - called positionally frozen modes are z-constant, so the position handed to :func:`solprop.linear.dispersion_operator` is irrelevant and is zero. - Parameters - ---------- - modes : sequence of Mode - The mode set, in order; each must implement - :class:`solprop.modes.protocol.ParameterisedMode`. - grid : solprop.grid.TimeGrid - The grid. - frame : solprop.linear.Frame - The co-moving frame. Its ``beta_0`` and ``beta_1`` follow the frozen reference - mode, so the frame subtraction is a function of the same parameters and needs - no dimension of its own. - n_pol : int - Polarisation components; ``D`` is repeated over them. - loss : solprop.linear.LossPolicy - The loss limit, applied before integration. - absorber_rate : numpy.ndarray or None - The spectral absorber rate, or ``None``. - - Returns - ------- - callable - ``values -> D`` of shape ``(1, n_modes, n_pol, n_omega)``. + - ``modes``: the mode set, in order; each must implement + :class:`solprop.modes.protocol.ParameterisedMode`. + - ``absorber_rate``: the spectral absorber rate, or ``None``. + - Returns: ``values -> D`` of shape ``(1, n_modes, n_pol, n_omega)``. """ parameterised = [one for one in modes if isinstance(one, ParameterisedMode)] @@ -2323,15 +1976,7 @@ def operator_of( ) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"]: """Return the linear operator at one point of the parameter space. - Parameters - ---------- - values : collections.abc.Mapping - One value per expanded path, by name. - - Returns - ------- - numpy.ndarray - ``D(omega; values)`` in 1/m, shape ``(1, n_modes, n_pol, n_omega)``. + - Returns: ``D(omega; values)`` in 1/m, shape ``(1, n_modes, n_pol, n_omega)``. """ frozen = [one.at_parameters(values) for one in parameterised] # One frame for the whole set, from the reference mode at *these* parameter @@ -2355,18 +2000,8 @@ def _canonical( ) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"]: """Stack one operator per mode into the canonical state layout. - Parameters - ---------- - columns : sequence of numpy.ndarray - ``D`` per mode, each of shape ``(n_omega,)``. - n_pol : int - Polarisation components; ``D`` is the same for both components of one mode and - is repeated over them so a birefringent mode needs no new layout. - - Returns - ------- - numpy.ndarray - Shape ``(1, n_modes, n_pol, n_omega)``. + - ``n_pol``: polarisation components; ``D`` is the same for both components of one + mode and is repeated over them so a birefringent mode needs no new layout. """ stacked = np.stack(columns)[:, np.newaxis, :] return np.repeat(stacked, n_pol, axis=1)[np.newaxis] @@ -2391,19 +2026,7 @@ def _shared_frame(reference: Mode, frame: Frame, z: float) -> Frame: :func:`build_phi` records beside ``Phi``. Freezing them here also saves the remaining modes an ``neff`` evaluation each. - Parameters - ---------- - reference : solprop.modes.protocol.Mode - The mode the frame follows; the first of the set. - frame : solprop.linear.Frame - The frame, whose fixed values are kept as they are. - z : float - Position along the waveguide, m. - - Returns - ------- - solprop.linear.Frame - The same frame with both quantities fixed at this position. + - ``z``: position along the waveguide, m. """ if frame.beta0 is not None and frame.beta1 is not None: return frame @@ -2430,17 +2053,10 @@ def _roundoff_floor(operator_at: _OperatorAt, span: float) -> float: differ by whatever ratio the elements span, which is four orders on a wide field grid and eight when a dispersion-specified mode stands beside a capillary one. - Parameters - ---------- - operator_at : callable - ``z -> D(z)`` in the canonical layout. - span : float - End of the tabulated range, m. - - Returns - ------- - float - The relative round-off floor; zero if the operator vanishes everywhere probed. + - ``operator_at``: ``z -> D(z)`` in the canonical layout. + - ``span``: end of the tabulated range, m. + - Returns: the relative round-off floor; zero if the operator vanishes everywhere + probed. """ delta = _FLOOR_STEP_FRACTION * span worst = 0.0 @@ -2621,18 +2237,7 @@ def _check_dispersion_span(modes: Sequence[Mode], z_max: float) -> None: derivative and for its loss too, and a loss declared over half the fibre would hold its last attenuation over the rest in exactly the same silence. - Parameters - ---------- - modes : sequence of Mode - The mode set. - z_max : float - Length the run covers, m. - - Raises - ------ - TranslationError - Naming the mode, which of its paths falls short, its declared range and the - run. + - ``z_max``: length the run covers, m. """ for one in modes: for attribute, label in _SPECTRAL_PATHS: @@ -2686,17 +2291,7 @@ def parameters_own_z(modes: Sequence[Mode]) -> bool: def _interior_edges(paths: Sequence[ZProfile], span: float) -> list[float]: """Return the declared breakpoints of the paths that lie strictly inside the range. - Parameters - ---------- - paths : sequence of solprop.profiles.ZProfile - The scalar paths the modes declare. - span : float - End of the tabulated range, m. - - Returns - ------- - list of float - The interior breakpoints, sorted and deduplicated. + - ``span``: end of the tabulated range, m. """ return sorted( { @@ -2719,23 +2314,9 @@ def _frame_tables( interval width, spending dozens of intervals and still leaving an error a hundred times its tolerance at the kink itself. - Parameters - ---------- - mode : Mode - The reference mode; the frame follows it along z. - frame : solprop.linear.Frame - The frame. - span : float - End of the tabulated range, m. - tol : float - Target bound on the uniform error relative to the value scale. - kinks : sequence of float - Positions that must be interval edges. - - Returns - ------- - tuple - ``(beta0, beta1)`` as scalar Chebyshev tables, in 1/m and s/m. + - ``span``: end of the tabulated range, m. + - ``kinks``: positions that must be interval edges. + - Returns: ``(beta0, beta1)`` as scalar Chebyshev tables, in 1/m and s/m. """ quantities = [ tabulate( @@ -2918,23 +2499,10 @@ def _rate_value( ) -> np.ndarray: """Evaluate the rate table at `z`, on the side of an edge `before` asks for. - Parameters - ---------- - table : LinearOperator - An expansion or a z table. - z : float - Position in m. - before : bool - Read an edge from the interval ending there. - helpers : solprop._helpers.Helpers or None - The stepping backend's implementation for an expansion's rows, which reads the - chosen interval as a one-interval table at the same ``x``; ``None``, or a z - table, takes the shared pass. - - Returns - ------- - numpy.ndarray - The expansion's term weights, or the z table's ``D``. + - ``z``: position in m. + - ``helpers``: the stepping backend's implementation for an expansion's rows, which + reads the chosen interval as a one-interval table at the same ``x``; ``None``, or + a z table, takes the shared pass. """ rate = table.rate index = rate.interval_containing(z) @@ -2965,19 +2533,9 @@ def _separable_phase( ) -> np.ndarray: """Evaluate a phase with bounded coefficient scratch and one returned volume. - Parameters - ---------- - coefficients : solprop.translate.spatial.SeparableOperator - Shared constant-medium data. - distance : float - Integration length in m. - imaginary : bool - Return only the unitary phase when true. - - Returns - ------- - numpy.ndarray - Complex exponent or real unitary phase, in the canonical state layout. + - ``distance``: integration length in m. + - ``imaginary``: return only the unitary phase when true. + - Returns: complex exponent or real unitary phase, in the canonical state layout. """ result = np.empty( coefficients.value_shape, dtype=np.float64 if imaginary else np.complex128 @@ -3055,24 +2613,15 @@ def walk_off_buckets( axis = np.asarray(omega, dtype=np.float64)[order] sorted_active = np.asarray(active, dtype=bool)[order] - def rates(phase: np.ndarray, valid: np.ndarray) -> np.ndarray: + def _rates(phase: np.ndarray, valid: np.ndarray) -> np.ndarray: """Return the slopes of sorted phases, zero where unreadable. Centred where a bin and both its neighbours may be read, one-sided where only one neighbour may: the last propagating bin before evanescence and the active band's edges, which walk fastest, keep their rates. - Parameters - ---------- - phase : numpy.ndarray - Phases per metre on the sorted bins, rad/m, ``(n_rows, n_omega)``. - valid : numpy.ndarray - Which bins may be read. - - Returns - ------- - numpy.ndarray - ``|d phase / d omega|``, s/m, the shape of `phase`. + - ``phase``: phases per metre on the sorted bins, rad/m, ``(n_rows, n_omega)``. + - Returns: ``|d phase / d omega|``, s/m, the shape of `phase`. """ step = np.abs(np.diff(phase, axis=1)) / np.diff(axis) pair = valid[:, 1:] & valid[:, :-1] @@ -3118,7 +2667,7 @@ def rates(phase: np.ndarray, valid: np.ndarray) -> np.ndarray: root, propagating, _ = longitudinal_root(coefficients, columns) phase = (np.real(root) - coefficients.frame[None, :])[:, order] valid = propagating[:, order] & sorted_active[None, :] - each = rates(phase, valid) + each = _rates(phase, valid) fastest = max(fastest, float(np.max(each, initial=0.0))) buckets[columns][:, order] = split_rate_buckets(each) if inverse is not None: @@ -3130,7 +2679,7 @@ def rates(phase: np.ndarray, valid: np.ndarray) -> np.ndarray: n_batch, n_modes, n_pol, n_omega = rate.shape phase = -np.imag(rate.reshape(-1, n_omega))[:, order] valid = np.broadcast_to(sorted_active[None, :], phase.shape) - each = rates(phase, valid).reshape(n_batch, n_modes, n_pol, n_omega) + each = _rates(phase, valid).reshape(n_batch, n_modes, n_pol, n_omega) each = each.max(axis=(0, 2)) widest = each if widest is None else np.maximum(widest, each) assert widest is not None @@ -3149,17 +2698,9 @@ def _rate_positions( jumps at a hard kink. A coupled flow's sections are read the same way, their ends from the section each closes, beside ``D`` at the same positions. - Parameters - ---------- - table : LinearOperator - A tabulated operator. - - Returns - ------- - list of tuple - ``(z, before, section)``: the position, m, whether :func:`phi_rate` reads it - from the interval ending there, and the coupled flow's section it is read in - (``None`` without a flow, or for the section containing it). + - Returns: ``(z, before, section)``: the position, m, whether :func:`phi_rate` reads + it from the interval ending there, and the coupled flow's section it is read in + (``None`` without a flow, or for the section containing it). """ edges = np.asarray(table.phi_breakpoints, dtype=np.float64) positions: list[tuple[float, bool, int | None]] = [] @@ -3195,27 +2736,8 @@ def _channel_rate( ``G``, applied beside it. The taper's connection is antisymmetric, a rotation of the moving basis, and adds nothing to the diagonal. - Parameters - ---------- - table : LinearOperator - A tabulated operator. - z : float - Position in m. - before : bool - Read an edge of ``D`` from the interval ending there. - section : int or None - The flow's section the position is read in (:func:`flow_weights`). - - Returns - ------- - numpy.ndarray - ``D + diag(G)`` in 1/m, canonical layout. - - Raises - ------ - TranslationError - For a factored coupled flow, which no backend executes and whose generator - diagonal is not read here. + - ``z``: position in m. + - Returns: ``D + diag(G)`` in 1/m, canonical layout. """ rate = phi_rate(table, z, before=before) flow = table.coupled_flow @@ -3285,17 +2807,7 @@ def unitary_phase( def _check_inside(table: LinearOperator, z: float) -> None: """Refuse a position outside the tabulated range. - Parameters - ---------- - table : LinearOperator - The cumulative operator. - z : float - Position in m. - - Raises - ------ - TranslationError - If `z` is outside ``[z_span[0], z_span[1]]``. + - ``z``: position in m. """ low, high = table.z_span if not low <= z <= high: @@ -3309,17 +2821,8 @@ def _verify_pairs(span: float, count: int) -> tuple[tuple[float, float], ...]: Deterministic rather than random, so a debug log is reproducible: the pairs walk across the span with increasing separation. - Parameters - ---------- - span : float - End of the tabulated range, m. - count : int - Number of pairs. - - Returns - ------- - tuple - ``(z1, z2)`` pairs with ``0 <= z1 < z2 <= span``. + - ``span``: end of the tabulated range, m. + - Returns: ``(z1, z2)`` pairs with ``0 <= z1 < z2 <= span``. """ starts = np.linspace(0.0, 0.5 * span, count) ends = np.linspace(0.5 * span, span, count) diff --git a/src/solprop/translate/modal.py b/src/solprop/translate/modal.py index 86fee775..2bbec7d8 100644 --- a/src/solprop/translate/modal.py +++ b/src/solprop/translate/modal.py @@ -210,14 +210,7 @@ def __post_init__(self) -> None: self._check_kind() def _check_kind(self) -> None: - """Check the stored kind against what the transform carries. - - Raises - ------ - TranslationError - If the kind is unknown or contradicts the geometry, the tables, the node - count or the node weights. - """ + """Check the stored kind against what the transform carries.""" kind = self.kind if self.geometry is not None: if kind != self.geometry.kind: @@ -247,13 +240,7 @@ def _check_kind(self) -> None: raise TranslationError(msg) def _check_placeholders(self) -> None: - """Check the static matrices a general modal guide does not use. - - Raises - ------ - TranslationError - If anything the general route replaces carries a value. - """ + """Check the static matrices a general modal guide does not use.""" n_modes = self.n_modes expected = ((1, n_modes), (n_modes, 1), (n_modes, 1)) matrices = (self.synthesis, self.projection, self.projection_coarse) @@ -409,22 +396,7 @@ def _sampled_areas( it is where an impossible area is caught, by name, instead of reaching ``sqrt`` as a bare ``ValueError``. - Parameters - ---------- - mode : solprop.modes.protocol.Mode - The mode being tabulated. - z : numpy.ndarray - Positions in m, shape (n_z,). - - Returns - ------- - numpy.ndarray - Effective areas in m², shape (n_z,). - - Raises - ------ - ModeError - If the area is not positive and finite at one of the sampled positions. + - ``z``: positions in m, shape (n_z,). """ areas = np.array([mode.aeff(float(one)) for one in z], dtype=float) bad = ~(np.isfinite(areas) & (areas > 0.0)) @@ -450,16 +422,8 @@ def _radius_kinks(mode: Mode) -> tuple[float, ...]: :func:`solprop.translate.linop.declared_paths` puts the same paths into the cumulative operator's hard kinks, which is what the schedule lands on. - Parameters - ---------- - mode : Mode - The mode. - - Returns - ------- - tuple of float - The declared breakpoints in m, increasing and without duplicates, empty - when the mode declares no path. + - Returns: the declared breakpoints in m, increasing and without duplicates, empty + when the mode declares no path. """ values: set[float] = set() one: Mode | None = mode @@ -773,22 +737,7 @@ def general_modal_transform( def _checked_span(z_max: float, margin: float) -> float: """Return the end of the tabulated range, validating both arguments. - Parameters - ---------- - z_max : float - Length the run covers, m. - margin : float - Fraction of `z_max` to extend beyond it. - - Returns - ------- - float - ``z_max (1 + margin)``. - - Raises - ------ - TranslationError - If `z_max` is not positive and finite or `margin` is negative or not finite. + - ``z_max``: length the run covers, m. """ if not (math.isfinite(z_max) and z_max > 0.0): msg = f"z_max must be positive and finite, got {z_max!r}" diff --git a/src/solprop/translate/modal_path.py b/src/solprop/translate/modal_path.py index a3661a8b..a9a30e3e 100644 --- a/src/solprop/translate/modal_path.py +++ b/src/solprop/translate/modal_path.py @@ -272,24 +272,7 @@ def path_rule(provider: ModalProvider, rule: ModalNodeRule, z: float) -> ModalNo def _check_gauge( reference: ModalSample, sample: ModalSample, nodes: ModalNodeRule, where: str ) -> None: - """Refuse a provider whose own gauge is not continuous between two samples. - - Parameters - ---------- - reference : solprop.modes.vector.ModalSample - The neighbouring sample already accepted. - sample : solprop.modes.vector.ModalSample - The new one, on a rule of the same structure. - nodes : solprop.modes.vector.ModalNodeRule - The new sample's rule, whose weights the overlap is taken with. - where : str - What separates the two, for the message. - - Raises - ------ - solprop.exceptions.ModeError - If a channel's own overlap is not the dominant, positive, real one. - """ + """Refuse a provider whose own gauge is not continuous between two samples.""" overlap = _overlap(reference, sample, nodes) diagonal = np.diag(overlap) size = np.abs(overlap) @@ -531,10 +514,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 "ModalPathTable: a moving guide's snapshots along z", [ ("sections", self.n_intervals), @@ -555,16 +535,8 @@ def _stacked( ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """Stack the knots' E, H and generator of several snapshots, position first. - Parameters - ---------- - tables : sequence of solprop.translate.modal_tables.DenseModalTable - Snapshots on rules of one structure and one band. - - Returns - ------- - tuple of numpy.ndarray - E and H of shape ``(n_positions, n_knots, n_nodes, 3, n_modes)`` and the - generator of shape ``(n_positions, n_knots, n_modes, n_modes)``. + - Returns: e and H of shape ``(n_positions, n_knots, n_nodes, 3, n_modes)`` and the + generator of shape ``(n_positions, n_knots, n_modes, n_modes)``. """ return tuple( # type: ignore[return-value] np.array( @@ -577,17 +549,7 @@ def _stacked( def _relative(interpolated: np.ndarray, fresh: np.ndarray) -> float: """Return the relative maximum-norm departure of an interpolant from a solve. - Parameters - ---------- - interpolated : numpy.ndarray - The expansion's value. - fresh : numpy.ndarray - The fresh solve's, of the same shape and not identically zero. - - Returns - ------- - float - ``max|interpolated - fresh| / max|fresh|``, dimensionless. + - ``fresh``: the fresh solve's, of the same shape and not identically zero. """ return float(np.max(np.abs(interpolated - fresh)) / np.max(np.abs(fresh))) @@ -600,20 +562,9 @@ def _held_out( ) -> tuple[float, float]: """Measure a section's expansion against fresh solves at the held-out points. - Parameters - ---------- - section : sequence of solprop.translate.modal_tables.DenseModalTable - The section's snapshots at the Lobatto points of its degree. - fresh : sequence of solprop.translate.modal_tables.DenseModalTable - Fresh snapshots at the points a doubling adds. - lo, hi : float - The section's ends, m. - - Returns - ------- - tuple of float - The largest relative departure of E, H and the generator, and the largest - ``norm(W - I, 2)`` of the expanded basis on the held-out rule. + - ``lo``, ``hi``: the section's ends, m. + - Returns: the largest relative departure of E, H and the generator, and the largest + ``norm(W - I, 2)`` of the expanded basis on the held-out rule. """ degree = len(section) - 1 coefficients = [lobatto_coefficients(one) for one in _stacked(section)] @@ -716,18 +667,10 @@ def sample_modal_path( edges = np.asarray(z_edges, dtype=np.float64) memo: dict[float, DenseModalTable] = dict(known or {}) - def at(position: float) -> DenseModalTable: + def _at(position: float) -> DenseModalTable: """Return the snapshot at one position, solving it at most once. - Parameters - ---------- - position : float - Position in m. - - Returns - ------- - solprop.translate.modal_tables.DenseModalTable - The snapshot there. + - ``position``: position in m. """ if position not in memo: memo[position] = path_snapshot( @@ -749,8 +692,8 @@ def at(position: float) -> DenseModalTable: while True: error = departure = 0.0 for lo, hi in sections: - section = [at(float(one)) for one in lobatto_points(chosen, lo, hi)] - fresh = [at(float(one)) for one in refinement_points(chosen, lo, hi)] + section = [_at(float(one)) for one in lobatto_points(chosen, lo, hi)] + fresh = [_at(float(one)) for one in refinement_points(chosen, lo, hi)] measured_error, measured_departure = _held_out(section, fresh, lo, hi) error = max(error, measured_error) departure = max(departure, measured_departure) @@ -775,7 +718,7 @@ def at(position: float) -> DenseModalTable: ) chosen *= 2 tables = tuple( - at(float(one)) + _at(float(one)) for lo, hi in sections for one in lobatto_points(chosen, float(lo), float(hi)) ) diff --git a/src/solprop/translate/modal_tables.py b/src/solprop/translate/modal_tables.py index 9ba768a1..96b0ace8 100644 --- a/src/solprop/translate/modal_tables.py +++ b/src/solprop/translate/modal_tables.py @@ -133,22 +133,7 @@ def _overlap( left: ModalSample, right: ModalSample, nodes: ModalNodeRule ) -> Complex[np.ndarray, "n_modes n_modes"]: - """Form the physical cross-Poynting matrix on common nodes. - - Parameters - ---------- - left : ModalSample - Reference sample on common physical nodes. - right : ModalSample - Candidate sample on the same physical nodes. - nodes : ModalNodeRule - The common physical nodes and their area weights in m². - - Returns - ------- - numpy.ndarray - Cross overlaps, shape (n_modes, n_modes). - """ + """Form the physical cross-Poynting matrix on common nodes.""" e, h, f, g = left.electric, left.magnetic, right.electric, right.magnetic return ( np.einsum("qm,qn,q->mn", h[:, 1].conj(), f[:, 0], nodes.weights) @@ -477,21 +462,6 @@ def _check_transformed_passivity( original's own worst rate by more than the product's rounding is arithmetic that went wrong, and ``_channel_index_table`` would otherwise meet it as a channel with ``Im n_eff`` of the wrong sign. - - Parameters - ---------- - sample : solprop.modes.vector.ModalSample - The sample before the declared transform. - turned : solprop.modes.vector.ModalSample - The same sample after it, with the product evaluated. - basis : solprop.modes.guide.ChannelBasis - The declaration, named in the refusal. - - Raises - ------ - ModeError - If a transformed channel's rate exceeds the block's own worst by more than - the rounding of the product. """ count = len(np.diag(sample.generator)) scale = float(np.max(abs(sample.generator))) @@ -588,10 +558,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. """ reference = self.samples[0][0] _, _, modes = reference.electric.shape @@ -716,19 +683,9 @@ def __post_init__(self) -> None: def _gate_gram(self, defect: float, omega: float, z: float) -> None: """Record the largest sampled Gram defect and refuse one above the budget. - Parameters - ---------- - defect : float - Largest ``norm(W - I, 2)`` over the knots, dimensionless. - omega : float - Angular frequency in rad/s of the knot that reached it. - z : float - Position in m of that knot. - - Raises - ------ - ModeError - If the budget is not positive and finite, or the defect exceeds it. + - ``defect``: largest ``norm(W - I, 2)`` over the knots, dimensionless. + - ``omega``: angular frequency in rad/s of the knot that reached it. + - ``z``: position in m of that knot. """ if not np.isfinite(self.gram_atol) or self.gram_atol <= 0: raise ModeError("gram_atol must be positive and finite") @@ -747,26 +704,8 @@ def _interpolate( ) -> Complex[np.ndarray, "*shape"]: """Evaluate a dense field or generator component inside its knots. - Parameters - ---------- - name : str - Stored component name, one of :data:`_INTERPOLATED`. - omega : float - Positive angular frequency in rad/s. - z : float - Position in m within the declared path. - derivative : int, optional - Non-negative angular-frequency derivative order. - - Returns - ------- - numpy.ndarray - Interpolated component, with its stored shape. - - Raises - ------ - ModeError - If the requested point lies outside the knots. + - ``omega``: positive angular frequency in rad/s. + - ``z``: position in m within the declared path. """ if ( not self.omega[0] <= omega <= self.omega[-1] @@ -785,17 +724,8 @@ def _bracketing_residual( ) -> Float[np.ndarray, " n_modes"]: """Return the largest knot boundary residual around a requested point. - Parameters - ---------- - omega : float - Angular frequency in rad/s inside the knot interval. - z : float - Position in m inside the knot interval. - - Returns - ------- - numpy.ndarray - Per-channel residual in the interpolation cell, shape (n_modes,). + - ``omega``: angular frequency in rad/s inside the knot interval. + - ``z``: position in m inside the knot interval. """ # Carrying knot (0, 0)'s residual described a corner of the table, not the # point asked for. The bracketing knots are the local physical statement. @@ -1196,15 +1126,8 @@ def _channel_splitting( propagation constants themselves so that an exactly degenerate pair -- or a single channel, which has no splitting at all -- still has a positive scale. - Parameters - ---------- - diagonal : numpy.ndarray - The generator's diagonal in 1/m, shape (n_modes,). - - Returns - ------- - float - Positive scale in 1/m. + - ``diagonal``: the generator's diagonal in 1/m, shape (n_modes,). + - Returns: positive scale in 1/m. """ floor = 64 * float(np.finfo(float).eps) * float(np.max(abs(diagonal))) if len(diagonal) < 2: @@ -1225,15 +1148,8 @@ def _area_knot(omega: Float[np.ndarray, " n_omega"]) -> int: taken in the middle. For an even knot count this is the upper of the two middle knots, so that it is always a frequency the band was solved at. - Parameters - ---------- - omega : numpy.ndarray - Increasing angular frequencies in rad/s, shape (n_omega,). - - Returns - ------- - int - Index on the frequency axis. + - ``omega``: increasing angular frequencies in rad/s, shape (n_omega,). + - Returns: index on the frequency axis. """ return len(omega) // 2 @@ -1295,38 +1211,7 @@ def _channel_index_table( ) -> IndexTable: """Read one channel's dispersion out of a tracked dense table. - Parameters - ---------- - table : DenseModalTable - Tracked knots of one physical guide at one longitudinal position. - channel : int - Channel index on the mode axis. - include_area : bool - Sample :func:`channel_area` at every frequency knot. - coupled : bool, optional - Admit a generator that couples its channels and read ``i G_cc``, the - channel's **mean** propagation constant, off its diagonal. Default false, - which refuses one by name. It is not a tuning knob: a coupled basis has no - per-channel propagation constant, because its channels beat, and a run that - declares one propagates the whole block through ``linear.coupled_flow`` - instead. What the per-channel table is still - *for* on such a route is everything the flow does not do -- the surrogate - effective area, the dimension limits, the labels, and the cumulative - ``Phi`` of the **one** reference channel the block is measured against -- so - it is built, from the diagonal, by a caller that has said it knows which of - the two it is asking for. - - Returns - ------- - IndexTable - Index snapshot with the interpolant's slopes at the knots. - - Raises - ------ - ModeError - If the channel is not on the mode axis, the table spans more than one - position, the generator couples its channels and `coupled` is false, or a - channel declares gain (``Re G > 0`` in the internal convention). + - ``channel``: channel index on the mode axis. """ modes = table.samples[0][0].electric.shape[2] if not 0 <= channel < modes: @@ -1645,10 +1530,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. """ _, nodes, _, modes, _ = np.shape(self.electric) return "ModalTransformTables: the flat E/H reference of a general guide", [ @@ -1820,21 +1702,7 @@ def __post_init__(self) -> None: def _map_degree(self, intervals: int) -> int: """Return the Chebyshev degree count the two execution maps share. - Parameters - ---------- - intervals : int - Intervals the record's positions declare. - - Returns - ------- - int - The degrees along z, or zero when the record carries no map at all. - - Raises - ------ - ModeError - If only one half of the map is present, or the synthesis does not - carry one leading interval axis and at least one degree. + - Returns: the degrees along z, or zero when the record carries no map at all. """ if (self.synthesis is None) != (self.projection is None): raise ModeError( @@ -1852,14 +1720,7 @@ def _map_degree(self, intervals: int) -> int: return int(shape[1]) def _validate_arrays(self) -> None: - """Copy, check and freeze every leaf against the extents the record declares. - - Raises - ------ - ModeError - If an array has the wrong shape or dtype, holds a value it may not, or - belongs to a group that is only half supplied. - """ + """Copy, check and freeze every leaf against the extents the record declares.""" # The component count is the contract's ceiling, which lives beside the # state's polarisation ceiling in translate.problem; that module imports this # one, so the import is deferred rather than the number copied. @@ -1953,18 +1814,9 @@ def _coordinate_dual( longitudinal columns are zero. It is for amplitude recovery, not for nonlinear projection, which carries the separate ``-i omega`` factor. - Parameters - ---------- - sample : ModalSample - Internal-convention sample at one frequency and position. - weights : numpy.ndarray - Area weights in m², shape (n_nodes,). - - Returns - ------- - numpy.ndarray - Coefficients, shape (n_modes, n_nodes, 6): the first three pair with E in - sqrt(W) m/V and the last three with H in sqrt(W) m/A. + - ``sample``: Internal-convention sample at one frequency and position. + - Returns: coefficients, shape (n_modes, n_nodes, 6): the first three pair with E in + sqrt(W) m/V and the last three with H in sqrt(W) m/A. """ inverse = np.linalg.inv(sample.flux_metric) electric, magnetic = sample.electric.conj(), sample.magnetic.conj() @@ -1993,17 +1845,7 @@ def _projection_map( operations: this one contracts a polarisation in C/m^2 and has units V m/sqrt(W), so ``Q P omega`` is in sqrt(W)/m. - Parameters - ---------- - sample : ModalSample - Internal-convention sample at one frequency and position. - weights : numpy.ndarray - Area weights in m², shape (n_nodes,). - - Returns - ------- - numpy.ndarray - The projection, shape (n_modes, n_nodes, 3), in V m/sqrt(W). + - ``sample``: Internal-convention sample at one frequency and position. """ inverse = np.linalg.inv(sample.flux_metric) projected = np.einsum("mn,qcn->mqc", inverse, sample.electric.conj()) @@ -2284,10 +2126,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 "ProjectedLaunch: a declared field in a guide's channels", [ ("channels", int(self.amplitudes.shape[0])), diff --git a/src/solprop/translate/planar.py b/src/solprop/translate/planar.py index 707489d4..06c9a2b9 100644 --- a/src/solprop/translate/planar.py +++ b/src/solprop/translate/planar.py @@ -51,24 +51,9 @@ def _validate_span(n_pol: int, z_max: float, margin: float) -> float: """Validate the scalar state and a finite positive translation extent. - Parameters - ---------- - n_pol : int - State polarisation components; the planar state carries one. - z_max : float - Run length in m. - margin : float - Fractional nonnegative extension. - - Returns - ------- - float - Extended positive span in m. - - Raises - ------ - TranslationError - If the state has two components or the span is invalid. + - ``z_max``: run length in m. + - ``margin``: fractional nonnegative extension. + - Returns: extended positive span in m. """ if isinstance(n_pol, bool) or n_pol != 1: msg = ( @@ -313,19 +298,6 @@ def _check_medium(space: PlanarGrid, medium: SpatialMedium) -> None: which the medium's index cancels, but the nonlinear responses are scaled by the medium's density: a different medium would propagate correctly and respond as another gas. The modes carry the fill, so the medium must be that fill. - - Parameters - ---------- - space : solprop.modes.slab.PlanarGrid - The slab channels, which share one fill. - medium : solprop.spatial.SpatialMedium - The bulk medium the run was given. - - Raises - ------ - TranslationError - If the gas, its density (pressure and temperature), its susceptibility or - the vacuum does not match the modes'. """ mode = cast("SlabMode", space.modes[0]) position = np.array([0.0]) @@ -378,14 +350,8 @@ def square_residuals(axis: np.ndarray) -> np.ndarray: def _warn_grazing(space: PlanarGrid, grid: TimeGrid, data: SeparableOperator) -> None: """Report the first active column inside its cutoff's arithmetic uncertainty. - Parameters - ---------- - space : solprop.modes.slab.PlanarGrid - The channels and y frequencies, for the reported column. - grid : solprop.grid.TimeGrid - Frequency bins in rad/s and their active mask. - data : solprop.translate.spatial.SeparableOperator - Validated square parts and wall terms in m^-2. + - ``grid``: frequency bins in rad/s and their active mask. + - ``data``: validated square parts and wall terms in m^-2. """ assert data.kappa_sq is not None order = np.argsort(space.k_y**2, kind="stable") diff --git a/src/solprop/translate/plasma_integration.py b/src/solprop/translate/plasma_integration.py index cc5cec12..cf56bed9 100644 --- a/src/solprop/translate/plasma_integration.py +++ b/src/solprop/translate/plasma_integration.py @@ -205,22 +205,8 @@ def integration_record(rule: RunningIntegral) -> dict[str, object]: def _design(dt: float, band_edge: float) -> RunningIntegral: """Design and certify the band rule for one sample spacing and band edge. - Parameters - ---------- - dt : float - Fine sample spacing, s. - band_edge : float - Upper edge of the apodised window, rad/s. - - Returns - ------- - RunningIntegral - The band rule with the smallest admitted ``K``. - - Raises - ------ - TranslationError - If no admitted design exists. + - ``dt``: fine sample spacing, s. + - ``band_edge``: upper edge of the apodised window, rad/s. """ theta_b = band_edge * dt if not 0.0 < theta_b < math.pi: @@ -275,19 +261,8 @@ def _fit(half: int, theta_b: float) -> tuple[Float[np.ndarray, " n_taps"], float which leaves a ``K``-unknown linear least-squares problem. Its columns are scaled to unit norm before the SVD-based solve. - Parameters - ---------- - half : int - ``K``, the number of side taps. - theta_b : float - The band edge in ``omega dt``. - - Returns - ------- - taps : numpy.ndarray - ``(c_0, ..., c_K)``, with ``c_0 = 1 - 2 sum c_k``. - condition : float - The scaled design matrix's condition number; one when ``K <= 1``. + - Returns ``condition``: the scaled design matrix's condition number; one when ``K + <= 1``. """ if half == 0: return np.ones(1), 1.0 @@ -306,20 +281,7 @@ def _fit(half: int, theta_b: float) -> tuple[Float[np.ndarray, " n_taps"], float def _composite( taps: Float[np.ndarray, " n_taps"], theta: Float[np.ndarray, " n"] ) -> Float[np.ndarray, " n"]: - """Return ``H = C rho``, the filtered trapezoid's multiplier. - - Parameters - ---------- - taps : numpy.ndarray - ``(c_0, ..., c_K)``. - theta : numpy.ndarray - ``omega dt``. - - Returns - ------- - numpy.ndarray - The composite multiplier relative to the exact integral. - """ + """Return ``H = C rho``, the filtered trapezoid's multiplier.""" theta = np.asarray(theta, dtype=np.float64) response = np.full_like(theta, taps[0]) for k in range(1, taps.size): @@ -338,18 +300,6 @@ def _certified_max( rises above its sampled peak by orders of magnitude less than that, so the smaller peaks cannot decide the result and refining them would spend the search on rounding noise where ``H`` is flat. - - Parameters - ---------- - function : callable - Vectorised, from ``theta`` to a real array. - lower, upper : float - The interval. - - Returns - ------- - float - The certified maximum of the absolute value. """ theta = np.linspace(lower, upper, CERTIFICATION_SAMPLES) values = np.abs(function(theta)) diff --git a/src/solprop/translate/plasma_source.py b/src/solprop/translate/plasma_source.py index 0ba8c335..d1f74236 100644 --- a/src/solprop/translate/plasma_source.py +++ b/src/solprop/translate/plasma_source.py @@ -40,23 +40,7 @@ class _Certificate: def _certificate(grids: Grids) -> _Certificate: - """Compute one immutable grid's certificate once. - - Parameters - ---------- - grids : Grids - Fine collar and derivative, active angular frequencies and crop indices. - - Returns - ------- - _Certificate - Dimensionless full-transform residual and each taper's sample count. - - Raises - ------ - TranslationError - If an active frequency is nonpositive or the collar is identically zero. - """ + """Compute one immutable grid's certificate once.""" cached = _CERTIFICATES.get(grids) if cached is not None: return cached diff --git a/src/solprop/translate/problem.py b/src/solprop/translate/problem.py index 1071b342..8c68822e 100644 --- a/src/solprop/translate/problem.py +++ b/src/solprop/translate/problem.py @@ -545,19 +545,7 @@ def __post_init__(self) -> None: def _refuse_unread_control(controller: PIController) -> None: - """Refuse a controller setting the Adams march would ignore, by name. - - Parameters - ---------- - controller : solprop.stepper.controller.PIController - The run's controller. - - Raises - ------ - TranslationError - Naming each field set away from its default that the Adams march does not - read. - """ + """Refuse a controller setting the Adams march would ignore, by name.""" default = PIController(rtol=controller.rtol, atol=controller.atol) unread = [ one.name @@ -715,16 +703,6 @@ def _check_low_storage(self, method: LowStorageRungeKutta) -> None: The march accepts every step in place and has no estimate, so it replays a prescribed schedule and nothing else (SEP 24 section 5); `dz_max` stays a true statement about the run it records, so a longer schedule step is refused. - - Parameters - ---------- - method : solprop.stepper.low_storage.LowStorageRungeKutta - The march asked for. - - Raises - ------ - TranslationError - Naming the setting and its remedy. """ if self.schedule is None: msg = ( @@ -789,15 +767,7 @@ class _RunPolicy(NamedTuple): def _readonly(values: np.ndarray) -> np.ndarray: """Return a C-contiguous, read-only view or copy of an array. - Parameters - ---------- - values : numpy.ndarray - The array to freeze. - - Returns - ------- - numpy.ndarray - The same values, C-contiguous and not writeable. + - Returns: the same values, C-contiguous and not writeable. """ frozen = np.ascontiguousarray(values) frozen.setflags(write=False) @@ -819,15 +789,7 @@ def _hold(values: np.ndarray) -> np.ndarray: Only for an array no one else refers to: a copy, a quotient or a fresh build. - Parameters - ---------- - values : numpy.ndarray - The array. - - Returns - ------- - numpy.ndarray - The same array, read-only, which :class:`Initial` takes without a copy. + - Returns: the same array, read-only, which :class:`Initial` takes without a copy. """ values.setflags(write=False) _HELD[id(values)] = values @@ -835,35 +797,14 @@ def _hold(values: np.ndarray) -> np.ndarray: def _is_held(values: object) -> bool: - """Return whether an array is one :func:`_hold` marked. - - Parameters - ---------- - values : object - Anything. - - Returns - ------- - bool - True for the very array that was marked, while it lives. - """ + """Return whether an array is one :func:`_hold` marked.""" return _HELD.get(id(values)) is values def _held_copy(values: np.ndarray, dtype: np.dtype | type[np.generic]) -> np.ndarray: """Return a held, C-contiguous copy of an array, or the array if already held. - Parameters - ---------- - values : numpy.ndarray - The caller's array, or one already held. - dtype : numpy.dtype or type - The element type of the copy, byte order included. - - Returns - ------- - numpy.ndarray - Read-only and the problem's own, of `dtype`. + - Returns: Read-only and the problem's own, of `dtype`. """ if _is_held(values) and values.dtype == dtype: return values @@ -873,19 +814,9 @@ def _held_copy(values: np.ndarray, dtype: np.dtype | type[np.generic]) -> np.nda def _held_block(values: np.ndarray, dtype: np.dtype | type[np.generic]) -> np.ndarray: """Return a held copy of an array of `dtype`, a broadcast copying its block alone. - Parameters - ---------- - values : numpy.ndarray - The caller's array, of any numeric dtype, or one already held. - dtype : numpy.dtype or type - The element type of the copy, byte order included. - - Returns - ------- - numpy.ndarray - Read-only, of `dtype`: `values` itself when it is already held with that - dtype; for a broadcast view, a broadcast of a held copy of the block it - repeats; otherwise a held C-contiguous copy. + - Returns: Read-only, of `dtype`: `values` itself when it is already held with that + dtype; for a broadcast view, a broadcast of a held copy of the block it repeats; + otherwise a held C-contiguous copy. """ if _is_held(values) and values.dtype == dtype: return values @@ -904,17 +835,9 @@ def _held_block(values: np.ndarray, dtype: np.dtype | type[np.generic]) -> np.nd def _held_state(field0: Num[np.ndarray, "*shape"]) -> Complex[np.ndarray, "*shape"]: """Return the initial state as :class:`Initial` holds it, copying only the block. - Parameters - ---------- - field0 : numpy.ndarray - The state the caller passed, of any numeric dtype. - - Returns - ------- - numpy.ndarray - Complex128 and read-only: `field0` itself when it is already held as - complex128; for a broadcast view, a broadcast of a held copy of the block it - repeats; otherwise a held C-contiguous copy (:func:`_held_block`). + - Returns: Complex128 and read-only: `field0` itself when it is already held as + complex128; for a broadcast view, a broadcast of a held copy of the block it + repeats; otherwise a held C-contiguous copy (:func:`_held_block`). """ return _held_block(np.asarray(field0), np.complex128) @@ -1097,16 +1020,8 @@ def validate(self) -> None: def _validate(self, run: _RunPolicy | None) -> None: """Run the checks in their one order, those of the run only with a request. - Parameters - ---------- - run : _RunPolicy or None - The run's stepper section and statistics, or ``None`` for the checks the - problem makes alone. - - Raises - ------ - TranslationError - Naming the field that failed. + - ``run``: the run's stepper section and statistics, or ``None`` for the checks + the problem makes alone. """ if run is not None: self._check_integrator(run.stepper) @@ -1169,15 +1084,7 @@ def _check_route(self, run: _RunPolicy | None) -> None: Each variant checks its own fields (``validate``, also run on construction); what spans groups is checked here, one variant at a time (SEP 47 section 6.2). - Parameters - ---------- - run : _RunPolicy or None - The run's stepper section and statistics, or ``None``. - - Raises - ------ - TranslationError - Naming the route and what disagrees. + - ``run``: the run's stepper section and statistics, or ``None``. """ # Again here, as every group's own checks are: a problem may hold a route # that was changed after it was built. @@ -1215,16 +1122,6 @@ def _check_integrator(self, stepper: StepperSettings) -> None: Its half-step flows are exact exponentials of commuting diagonal values, so a coupled flow, whose flows are not, would leave an error outside the Runge-Kutta order; the TDSE response is the Adams route's alone. - - Parameters - ---------- - stepper : StepperSettings - The run's stepper section. - - Raises - ------ - TranslationError - Naming the configuration and the integrator that steps it. """ method = stepper.integrator if not isinstance(method, LowStorageRungeKutta): @@ -1252,16 +1149,8 @@ def _check_precision(self, run: _RunPolicy | None) -> None: electronic response, the far-field statistics and noise models are outside SEP 33's scope, so every backend refuses them under ``precision="single"``. - Parameters - ---------- - run : _RunPolicy or None - The run's stepper section and statistics; ``None`` leaves the statistics - and the step's exponent to :func:`validate_run`. - - Raises - ------ - TranslationError - Naming the refused route, response or statistic. + - ``run``: the run's stepper section and statistics; ``None`` leaves the + statistics and the step's exponent to :func:`validate_run`. """ if not isinstance( self.precision, Precision @@ -1323,21 +1212,6 @@ def _check_single_exponent(self, stepper: StepperSettings) -> None: kind is checked: the exponent is evaluated from the operator itself over that step at the start and the end of the run, which for a varying operator samples its range. - - Parameters - ---------- - stepper : StepperSettings - The run's stepper section. - - Raises - ------ - TranslationError - Past ``log(FLT_MAX)``, naming the exponent, the step and the remedies. - - Warns - ----- - solprop.exceptions.ExponentBudgetWarning - Between nine tenths of the limit and the limit. """ schedule = stepper.schedule step, source = ( @@ -1409,18 +1283,8 @@ def _check_single_exponent(self, stepper: StepperSettings) -> None: def _check_tdse(self, source: TDSESource, stepper: StepperSettings | None) -> None: """Refuse a manually constructed atomic seam outside the admitted route. - Parameters - ---------- - source : solprop.translate.routes.TDSESource - The modal route's TDSE extension. - stepper : StepperSettings or None - The run's stepper section; ``None`` leaves the method to - :func:`validate_run`. - - Raises - ------ - TranslationError - Naming what departs from the scalar resolved modal Adams route. + - ``stepper``: the run's stepper section; ``None`` leaves the method to + :func:`validate_run`. """ descriptor = source.descriptor if ( @@ -1507,17 +1371,9 @@ def _check_arrays(self, run: _RunPolicy | None) -> None: The initial state alone may be strided (:data:`_STRIDED_LEAVES`). - Parameters - ---------- - run : _RunPolicy or None - The run's stepper section and statistics, whose arrays are held to the - same rules under their names ``stepper`` and ``stats_spec``; ``None`` - checks the problem's alone. - - Raises - ------ - TranslationError - Naming the field that failed. + - ``run``: the run's stepper section and statistics, whose arrays are held to + the same rules under their names ``stepper`` and ``stats_spec``; ``None`` + checks the problem's alone. """ for name, values in _walk_arrays(self, run): if not values.flags.c_contiguous and name not in _STRIDED_LEAVES: @@ -1551,13 +1407,7 @@ def _check_arrays(self, run: _RunPolicy | None) -> None: raise TranslationError(msg) def _check_shapes(self) -> None: - """Check every array agrees with the canonical state shape. - - Raises - ------ - TranslationError - Naming the field that failed. - """ + """Check every array agrees with the canonical state shape.""" n_modes, n_pol, n_omega = self.shape if self.initial.field0.shape != self.shape: msg = ( @@ -1652,11 +1502,8 @@ def _check_power_weight(self) -> None: def _check_spatial(self, run: _RunPolicy | None) -> None: """Check spatial geometry and the shared separable representation agree. - Parameters - ---------- - run : _RunPolicy or None - The run's stepper section and statistics; ``None`` leaves the propagator - mode to :func:`validate_run`. + - ``run``: the run's stepper section and statistics; ``None`` leaves the + propagator mode to :func:`validate_run`. """ geometry = self.transform.geometry separable = self.linear.separable @@ -1708,16 +1555,8 @@ def _check_spatial(self, run: _RunPolicy | None) -> None: def _check_slab(self, separable: SeparableOperator) -> None: """Check the hybrid planar operator against its geometry and transform. - Parameters - ---------- - separable : solprop.translate.spatial.SeparableOperator - The shared coefficients, which must carry one wall term per channel. - - Raises - ------ - TranslationError - If the wall terms are absent, their channels are not the transform's, - or a column's ``kperp_sq`` is not its ``k_y**2`` with the channel fastest. + - ``separable``: the shared coefficients, which must carry one wall term per + channel. """ geometry = self.transform.geometry assert geometry is not None @@ -1742,11 +1581,6 @@ def _check_prefactor_constant(self) -> None: the operator: a table must be one interval of degree zero, an expansion one term, and either must hold this array, so that a backend taking the array sees what a Clenshaw pass would. - - Raises - ------ - TranslationError - If the representation is not degenerate, or does not agree with the array. """ constant = self.responses.prefactor_constant if constant is None: @@ -1798,16 +1632,6 @@ def _check_collar_stability(self, stepper: StepperSettings) -> None: already fixed, and a floor ``dz_min`` set above the limit. Past the limit the collar amplifies the field it should absorb, and the run diverges, so they are refused rather than run. - - Parameters - ---------- - stepper : StepperSettings - The run's stepper section. - - Raises - ------ - TranslationError - Naming the limit, the offending step and the scheme that lifts it. """ geometry = self.transform.geometry if geometry is not None and np.any(geometry.absorber_rate): @@ -1863,22 +1687,6 @@ def _check_exponent_budget(self, stepper: StepperSettings) -> None: forms the same inverse factor from it, so it is bounded by :func:`solprop.translate.radial.radial_rate_bounds` instead (SEP 22, Q16); the modal kinds are not checked. - - Parameters - ---------- - stepper : StepperSettings - The run's stepper section. - - Raises - ------ - TranslationError - Naming the binding term, its value, the factor and the admissible step - and rate. - - Warns - ----- - solprop.exceptions.ExponentBudgetWarning - Between nine tenths of the limit and the limit. """ operator = self.linear.separable geometry = self.transform.geometry @@ -1924,15 +1732,7 @@ def _check_exponent_budget(self, stepper: StepperSettings) -> None: def _runge_kutta_advice(stepper: StepperSettings) -> str: """Name the Runge-Kutta march where an Adams method's interval is the limit. - Parameters - ---------- - stepper : StepperSettings - The run's stepper section. - - Returns - ------- - str - A clause ending in ", " for an Adams run, otherwise empty. + - Returns: a clause ending in ", " for an Adams run, otherwise empty. """ if not isinstance(stepper.integrator, Multistep): return "" @@ -1947,16 +1747,6 @@ def _stability_radius(stepper: StepperSettings) -> float: DOPRI5's :data:`solprop.boundaries.STABILITY_RADIUS`, the Adams method's far smaller one at its highest order (SEP 16 admits Adams schedules), or RK4's 2.785 under the low-storage march (SEP 24 section 4.6). - - Parameters - ---------- - stepper : StepperSettings - The run's stepper section. - - Returns - ------- - float - The largest ``h |lambda|`` an explicitly integrated damping rate allows. """ method = stepper.integrator if method is None: @@ -1966,16 +1756,7 @@ def _stability_radius(stepper: StepperSettings) -> float: def _check_spatial_collar_stability(self, stepper: StepperSettings) -> None: """Refuse a step past the spatial collar's limit in the energy-weighted basis. - Parameters - ---------- - stepper : StepperSettings - The run's stepper section. - - Raises - ------ - TranslationError - When a replayed schedule's longest step, or the step floor, exceeds - the limit. + The step checked is a replayed schedule's longest, or else the step floor. """ geometry = self.transform.geometry if geometry is None: @@ -2029,16 +1810,6 @@ def _check_collar_crossing(self, stepper: StepperSettings) -> None: because it is bound first by damping accuracy, ``alpha0 h / 2 <= 1.3`` (:data:`solprop.spatial_absorber.DAMPING_BOUND`). An adaptive run is held to the crossing limit through ``dz_max`` instead (:func:`assemble`). - - Parameters - ---------- - stepper : StepperSettings - The run's stepper section. - - Raises - ------ - TranslationError - Naming the bound and the longest step. """ route = self.route design = route.transverse if isinstance(route, SpatialRoute) else None @@ -2064,13 +1835,7 @@ def _check_collar_crossing(self, stepper: StepperSettings) -> None: raise TranslationError(msg) def _check_carrier(self) -> None: - """Check the harmonic carrier against the Kerr term that needs it. - - Raises - ------ - TranslationError - If the carrier is present without a harmonic term or absent with one. - """ + """Check the harmonic carrier against the Kerr term that needs it.""" wanted = self.responses.harmonic_carrier if (self.grids.carrier_2w0 is not None) != wanted: msg = ( @@ -2187,13 +1952,6 @@ def _check_dispersive(self) -> None: On the general modal route each channel names a node region of the guide rather than a population of the run's material. - - Raises - ------ - TranslationError - If its arrays are not on the fine grid's spectral layout, a channel names - an unknown population or region, its carrier flag disagrees with the grid - and the sector, or a density table does not cover the run. """ leaf = self.responses.dispersive_kerr if leaf is None: @@ -2270,16 +2028,8 @@ def _check_modal_leaves(self, run: _RunPolicy | None) -> None: Every leaf is ``None`` on every route that exists today, and the whole method is then a single test of four references. - Parameters - ---------- - run : _RunPolicy or None - The run's stepper section and statistics; ``None`` leaves the run's - extent along z to :func:`validate_run`. - - Raises - ------ - TranslationError - Naming the leaf that disagrees with the state. + - ``run``: the run's stepper section and statistics; ``None`` leaves the run's + extent along z to :func:`validate_run`. """ n_modes, n_pol, _ = self.shape tables = self.transform.modal_tables @@ -2360,16 +2110,8 @@ def _check_modal_grids(self, run: _RunPolicy | None) -> None: Every leaf tabulated along z must also cover the run, as :meth:`_check_tables` requires of every other z table. - Parameters - ---------- - run : _RunPolicy or None - The run's stepper section and statistics, whose last plane the leaves must - reach; ``None`` checks the axes alone. - - Raises - ------ - TranslationError - Naming the leaf whose axis is not the state's. + - ``run``: the run's stepper section and statistics, whose last plane the leaves + must reach; ``None`` checks the axes alone. """ omega = self.grids.omega z_end = None if run is None else run.stepper.z_end @@ -2415,17 +2157,9 @@ def _check_modal_grids(self, run: _RunPolicy | None) -> None: def _check_tables(self, run: _RunPolicy | None) -> None: """Check the z tables cover the run, and the schedule contains its targets. - Parameters - ---------- - run : _RunPolicy or None - The run's stepper section and statistics; ``None`` checks the tables - against each other alone, and leaves the run's extent, its schedule, the - collar's stability and the exponent budget to :func:`validate_run`. - - Raises - ------ - TranslationError - Naming the field that failed. + - ``run``: the run's stepper section and statistics; ``None`` checks the tables + against each other alone, and leaves the run's extent, its schedule, the + collar's stability and the exponent budget to :func:`validate_run`. """ edges = self.linear.phi_breakpoints if not np.all(np.diff(edges) > 0.0): @@ -2480,17 +2214,6 @@ def _check_schedule(self, stepper: StepperSettings) -> None: is refused by name rather than integrated; adaptive stepping needs no such rule, because the driver lands on every hard kink itself. - - Parameters - ---------- - stepper : StepperSettings - The run's stepper section, which replays a schedule. - - Raises - ------ - TranslationError - If a target is not an edge of the schedule, or if a coupled flow's kink - is straddled rather than landed on. """ schedule = stepper.schedule assert schedule is not None @@ -2614,20 +2337,8 @@ def _budget_term( ) -> tuple[str, float, float | None]: """Return the exponent budget's binding term: its name, value and what remains. - Parameters - ---------- - bounds : solprop.translate.spatial.RateBounds - The operator's bounds. - span : int - Steps a gain factor spans. - attenuation : str - The name of the `propagating` term, which no setting moves. - - Returns - ------- - tuple - The term's description, its rate in m^-1, and the rest of the loss bound - beside it (``None`` for a term no setting moves). + - Returns: the term's description, its rate in m^-1, and the rest of the loss bound + beside it (``None`` for a term no setting moves). """ if span * bounds.gain > bounds.loss: return f"the medium's gain over {span} step(s)", span * bounds.gain, None @@ -2653,25 +2364,7 @@ def _budget_message( # noqa: PLR0917 - called positionally ) -> str: """Describe the factor a step forms, naming the binding term. - Parameters - ---------- - source : str - Which step: the replayed schedule's or the floor. - step : float - The step, m. - bounds : solprop.translate.spatial.RateBounds - The operator's bounds. - span : int - Steps a gain factor spans. - exponent : float - The factor's exponent. - attenuation : str - The name of the operator's own attenuation term. - - Returns - ------- - str - The opening of the message. + - ``step``: the step, m. """ name, value, _ = _budget_term(bounds, span, attenuation) return ( @@ -2685,23 +2378,7 @@ def _budget_remedy( ) -> str: """Name the admissible step, and the admissible rate when a setting binds. - Parameters - ---------- - step : float - The step, m. - bounds : solprop.translate.spatial.RateBounds - The operator's bounds. - span : int - Steps a gain factor spans. - limit : float - The exponent to stay within. - attenuation : str - The name of the operator's own attenuation term. - - Returns - ------- - str - The remedy. + - ``step``: the step, m. """ name, value, rest = _budget_term(bounds, span, attenuation) rate = max(bounds.loss, span * bounds.gain) @@ -2792,20 +2469,7 @@ def _check_operator_settings( ) -> None: """Refuse the separable-operator settings where they cannot apply. - Parameters - ---------- - spatial : bool - Whether the run has a spatial grid. - evanescent_rate : float or None - The requested evanescent cap, m^-1. - spatial_absorber : solprop.spatial_absorber.SpatialAbsorber or None - The requested transverse boundary. - - Raises - ------ - TranslationError - If either is set on a run without a spatial grid, or the rate is not - positive and finite. + - ``evanescent_rate``: the requested evanescent cap, m^-1. """ if not spatial and (evanescent_rate is not None or spatial_absorber is not None): msg = ( @@ -2828,17 +2492,8 @@ def _refuse_planes_at_jumps( SEP 34 section 4.4: a plane at a jump measures one side's forward closure, and a request that does not say which is ambiguous. - Parameters - ---------- - medium : solprop.spatial.SpatialMedium or None - The run's medium; None for a modal run, which has none. - planes : numpy.ndarray - The requested save positions in m. - - Raises - ------ - TranslationError - If a plane lies exactly on a declared jump. + - ``medium``: the run's medium; None for a modal run, which has none. + - ``planes``: the requested save positions in m. """ if medium is None: return @@ -2869,26 +2524,11 @@ def _spatial_assembly( ]: """Assemble the operator, the transform, the prefactor and the plane measure. - Parameters - ---------- - spatial_grid : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The transverse geometry; a :class:`solprop.modes.slab.PlanarGrid` is the - hybrid planar transform (slab modes in x, an FFT in y). - medium : solprop.spatial.SpatialMedium - Material and density path. - grid : solprop.grid.TimeGrid - The run's temporal grid. - frame : solprop.linear.Frame - The co-moving frame. - build : _Assembly - The assembly's knobs. - - Returns - ------- - tuple - The operator, the transform, the already translated prefactor, the run's - plane measure and its transverse boundary's design (SEP 40), ``None`` for - none. + - ``spatial_grid``: the transverse geometry; a + :class:`solprop.modes.slab.PlanarGrid` is the hybrid planar transform (slab modes + in x, an FFT in y). + - Returns: the operator, the transform, the already translated prefactor, the run's + plane measure and its transverse boundary's design (SEP 40), ``None`` for none. """ dispersion = ( SpatialDispersion(medium, grid, frame) @@ -3026,21 +2666,9 @@ def _crossing_clamped( ``exp(alpha_s h / 2)`` stays within half the precision's range, so a strong ramp on a fine grid cannot overflow it. - Parameters - ---------- - design : solprop.spatial_absorber.SpatialAbsorberDesign or None - The run's transverse boundary. - dz_max : float - The requested largest step, m. - dz_init : float - The requested first step, m. - single : bool, optional - Whether the state is binary32, whose range is smaller. - - Returns - ------- - tuple - The largest step and the first step, m. + - ``dz_max``: the requested largest step, m. + - ``dz_init``: the requested first step, m. + - Returns: the largest step and the first step, m. """ if design is None: return dz_max, dz_init @@ -3073,24 +2701,9 @@ def _split_crossing( then by the boundary monitor from the state at every check. The in-stage scheme, and a run without a temporal collar, have no such limit. - Parameters - ---------- - compiled : solprop.translate.responses.CompiledResponses - The compiled responses, with the temporal collar and its scheme. - linear : LinearOperator - The linear operator, whose group delays walk. - grid : solprop.grid.TimeGrid - The grid, with the collar and the active band. - launch : numpy.ndarray - The launched state, shape ``(n_modes, n_pol, n_omega)``. - columns : numpy.ndarray or None - The modes' quadrature weights, as the boundary monitor weighs them, or - ``None`` for unit weights. - - Returns - ------- - solprop.boundaries.SplitCrossing or None - The limit, or ``None`` when none applies. + - ``columns``: the modes' quadrature weights, as the boundary monitor weighs them, + or ``None`` for unit weights. + - Returns: the limit, or ``None`` when none applies. """ if compiled.absorber_scheme != "split_step" or compiled.temporal_absorber is None: return None @@ -3112,22 +2725,11 @@ def _split_clamped( ) -> tuple[float, float]: """Return an adaptive run's step bounds held to the launch's crossing limit. - Parameters - ---------- - crossing : solprop.boundaries.SplitCrossing or None - The limit (:func:`_split_crossing`). - schedule : solprop.stepper.schedule.FixedSchedule or None - The schedule to replay, which keeps its bounds, or ``None``. - dz_max : float - The requested largest step, m. - dz_init : float - The requested first step, m. - - Returns - ------- - tuple - The largest step and the first step, m. Lowering ``dz_max`` is logged at - ``INFO``, naming the limit and what sets it. + - ``schedule``: the schedule to replay, which keeps its bounds, or ``None``. + - ``dz_max``: the requested largest step, m. + - ``dz_init``: the requested first step, m. + - Returns: the largest step and the first step, m. Lowering ``dz_max`` is logged at + ``INFO``, naming the limit and what sets it. """ if crossing is None or schedule is not None: return dz_max, dz_init @@ -3151,17 +2753,7 @@ def _refuse_split_crossing( ) -> None: """Refuse a fixed schedule whose longest step exceeds the launch's crossing limit. - Parameters - ---------- - crossing : solprop.boundaries.SplitCrossing or None - The limit (:func:`_split_crossing`). - schedule : solprop.stepper.schedule.FixedSchedule or None - The schedule to replay, or ``None`` for an adaptive run. - - Raises - ------ - TranslationError - Naming the limit and the collar's width. + - ``schedule``: the schedule to replay, or ``None`` for an adaptive run. """ if crossing is None or schedule is None: return @@ -3189,19 +2781,7 @@ def _transverse_design( A material update (:class:`TranslationReuse`) keeps the design its reused transform and expansions were built with, as it keeps the transform itself. - Parameters - ---------- - spatial_grid : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The transverse geometry. - dispersion : solprop.spatial.SpatialDispersion - The medium and the temporal grid. - build : _Assembly - The assembly's knobs, with the specification and the noise-free launch. - - Returns - ------- - solprop.spatial_absorber.SpatialAbsorberDesign or None - The design, or ``None`` when the run asked for no transverse boundary. + - Returns: the design, or ``None`` when the run asked for no transverse boundary. """ if build.reuse is not None: return build.reuse.transverse @@ -3242,29 +2822,8 @@ def _general_assembly( :func:`solprop.translate.coupled.constant_schur_flow` itself: there is no wall vector, no validity ratio and no Marcatili mode in it. - Parameters - ---------- - route : solprop.translate.general.GeneralModalRoute - The guide's leaves and its Gram certificate. - grid : solprop.grid.TimeGrid - The run's field grid. - modes : sequence of Mode - The guide's channels in state order. - frame : solprop.linear.Frame - The one shared co-moving frame. - build : _Assembly - The assembly's knobs, including the legacy ones it refuses. - - Returns - ------- - tuple - The operator carrying the identity metric and the unit power weight, the - transform carrying the tables, and the prefactor row. - - Raises - ------ - TranslationError - Naming what cannot be combined with a general modal route. + - Returns: the operator carrying the identity metric and the unit power weight, the + transform carrying the tables, and the prefactor row. """ _check_general(route, grid, build) coupled = route.coupled_generator @@ -3378,30 +2937,9 @@ def _general_flow( the method. A guide that varies along z needs the transform maps to move with it and is built by :func:`_moving_flow` instead. - Parameters - ---------- - route : solprop.translate.general.GeneralModalRoute - The route, whose gauge identity the flow carries so that a resume comparing - the flow compares the basis it was built in. - coupled : numpy.ndarray - The block relative to the reference channel in 1/m, shape - ``(n_modes, n_modes, n_omega)``. - grid : solprop.grid.TimeGrid - The run's grid, whose coarse axis the leaf is tabulated on. - build : _Assembly - The assembly's knobs, for ``dz_max`` and the run's length. - - Returns - ------- - solprop.translate.coupled.CoupledFlowTables - The validated leaf, one ``"constant_schur"`` section. - - Raises - ------ - TranslationError - If the run declares the ``'modified'`` noise model, if the block's exponent - over ``dz_max`` exceeds :data:`MAX_GENERAL_FLOW_EXPONENT`, or on anything - ``constant_schur_flow`` refuses. + - ``coupled``: the block relative to the reference channel in 1/m, shape ``(n_modes, + n_modes, n_omega)``. + - ``grid``: the run's grid, whose coarse axis the leaf is tabulated on. """ if build.noise_model == "modified": msg = ( @@ -3452,23 +2990,7 @@ def _landed_kinks( ) -> Float[np.ndarray, " n_kinks"]: """Return the operator's hard kinks, refusing a response kink they do not hold. - Parameters - ---------- - build : _Assembly - The assembly's knobs, carrying the response kinks. - linear : LinearOperator - The route's operator, whose hard kinks are what every step lands on. - - Returns - ------- - numpy.ndarray - ``linear.hard_kinks``, in m, shape ``(n_kinks,)``. - - Raises - ------ - TranslationError - Naming the path and the position, if a response kink strictly inside the run - is not a hard kink: only the uncoupled modal route adds them to ``Phi``. + - Returns: ``linear.hard_kinks``, in m, shape ``(n_kinks,)``. """ straddled = unlanded_kinks( build.kinks, @@ -3513,60 +3035,28 @@ class _PathCarrier: @property def z_constant(self) -> bool: - """Return False, so that the declared kinks reach the operator's tables. - - Returns - ------- - bool - Always ``False``. - """ + """Return False, so that the declared kinks reach the operator's tables.""" return False @property def scale_invariant(self) -> bool: - """Return False: a solid guide's modes have no scaling law. - - Returns - ------- - bool - Always ``False``. - """ + """Return False: a solid guide's modes have no scaling law.""" return False @property def channel_label(self) -> ChannelLabel: - """Return the reference channel's label. - - Returns - ------- - solprop.modes.protocol.ChannelLabel - The entrance channel's own. - """ + """Return the reference channel's label.""" return self.entrance.channel_label @property def azimuthal_order(self) -> int: - """Return the reference channel's azimuthal order. - - Returns - ------- - int - The entrance channel's own. - """ + """Return the reference channel's azimuthal order.""" return self.entrance.azimuthal_order def principal_component(self, z: float = 0.0) -> PolarisationComponent | None: """Return the reference channel's principal component at the entrance. - Parameters - ---------- - z : float, optional - Position in m; ignored. - - Returns - ------- - str or None - The entrance channel's own. + - ``z``: position in m; ignored. """ del z return self.entrance.principal_component() @@ -3576,17 +3066,8 @@ def neff( ) -> Complex[np.ndarray, " n_omega"]: """Return the entrance index at every position. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s. - z : float, optional - Position in m; ignored, the carrier being the entrance's. - - Returns - ------- - numpy.ndarray - The entrance ``n_eff``, complex. + - ``omega``: angular frequencies in rad/s. + - ``z``: position in m; ignored, the carrier being the entrance's. """ del z return self.entrance.neff(omega) @@ -3596,17 +3077,8 @@ def neff_minus_one( ) -> Complex[np.ndarray, " n_omega"]: """Return the entrance ``n_eff - 1`` at every position. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s. - z : float, optional - Position in m; ignored. - - Returns - ------- - numpy.ndarray - The entrance ``n_eff - 1``, complex. + - ``omega``: angular frequencies in rad/s. + - ``z``: position in m; ignored. """ del z return self.entrance.neff_minus_one(omega) @@ -3616,17 +3088,9 @@ def dneff_domega( ) -> Complex[np.ndarray, " n_omega"]: """Return the entrance index derivative at every position. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s. - z : float, optional - Position in m; ignored. - - Returns - ------- - numpy.ndarray - ``d n_eff / d omega`` in s/rad, complex. + - ``omega``: angular frequencies in rad/s. + - ``z``: position in m; ignored. + - Returns: ``d n_eff / d omega`` in s/rad, complex. """ del z return self.entrance.dneff_domega(omega) @@ -3634,15 +3098,7 @@ def dneff_domega( def aeff(self, z: float = 0.0) -> float: """Return the entrance surrogate area. - Parameters - ---------- - z : float, optional - Position in m; ignored. - - Returns - ------- - float - Area in m². + - ``z``: position in m; ignored. """ del z return self.entrance.aeff() @@ -3652,15 +3108,7 @@ def dimlimits( ) -> tuple[CrossSection, tuple[float, ...], tuple[float, ...]]: """Return the entrance domain. - Parameters - ---------- - z : float, optional - Position in m; ignored. - - Returns - ------- - tuple - The entrance channel's own. + - ``z``: position in m; ignored. """ del z return self.entrance.dimlimits() @@ -3670,17 +3118,8 @@ def field( ) -> Float[np.ndarray, "n_points 2"]: """Refuse, as the provider channel it carries does. - Parameters - ---------- - points : numpy.ndarray - Cross-section positions in m. - z : float, optional - Position in m. - - Returns - ------- - numpy.ndarray - Never returns for a provider channel. + - ``points``: Cross-section positions in m. + - ``z``: position in m. """ return self.entrance.field(points, z) @@ -3688,31 +3127,12 @@ def field( def parameter_paths(self) -> Mapping[str, ZProfile]: """Return the one declared path: the position, kinked where the maps are. - Returns - ------- - collections.abc.Mapping - ``{"position": path}``. + - Returns: ``{"position": path}``. """ return MappingProxyType({"position": self.path}) def at_parameters(self, values: Mapping[str, float]) -> Mode: - """Return the entrance channel, whatever the position. - - Parameters - ---------- - values : collections.abc.Mapping - The position, by name. - - Returns - ------- - solprop.modes.protocol.Mode - The entrance channel. - - Raises - ------ - TranslationError - If a name other than ``"position"`` is given. - """ + """Return the entrance channel, whatever the position.""" if set(values) - {"position"}: msg = f"a moving guide's carrier declares one path, got {sorted(values)}" raise TranslationError(msg) @@ -3720,20 +3140,7 @@ def at_parameters(self, values: Mapping[str, float]) -> Mode: def _carrier(mode: Mode, route: GeneralModalRoute) -> _PathCarrier: - """Return the carrier of a moving guide's ``Phi`` for its reference channel. - - Parameters - ---------- - mode : solprop.modes.protocol.Mode - The reference channel, a moving provider channel. - route : solprop.translate.general.GeneralModalRoute - The moving route, whose guide identity names the path's sections. - - Returns - ------- - _PathCarrier - The carrier. - """ + """Return the carrier of a moving guide's ``Phi`` for its reference channel.""" entrance = getattr(mode, "entrance", None) base = cast("Mode", entrance() if callable(entrance) else mode) edges = route.tables.z_edges @@ -3753,15 +3160,7 @@ def _carrier(mode: Mode, route: GeneralModalRoute) -> _PathCarrier: def _position(z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: """Return the position itself, the carrier's declared path. - Parameters - ---------- - z : numpy.ndarray - Positions in m. - - Returns - ------- - numpy.ndarray - The same positions, float64. + - ``z``: positions in m. """ return np.asarray(z, dtype=float) @@ -3779,26 +3178,6 @@ def _moving_flow( the interaction picture's source carries, as for a varying capillary. The flow's sections are the maps' here, which is natural and not required: the stage bundle carries the two weight rows separately (kernel schema 14). - - Parameters - ---------- - route : solprop.translate.general.GeneralModalRoute - The moving route. - grid : solprop.grid.TimeGrid - The run's grid. - build : _Assembly - The assembly's knobs, for ``dz_max`` and the noise refusal. - - Returns - ------- - solprop.translate.coupled.CoupledFlowTables - The validated leaf, every section ``"frozen_residual"``. - - Raises - ------ - TranslationError - If the run declares the ``'modified'`` noise model, or on anything the flow - refuses. """ if build.noise_model == "modified": msg = ( @@ -3831,27 +3210,12 @@ def _modal_linear( ) -> LinearOperator: """Return the linear operator of a mode-set route, with or without a wall block. - Parameters - ---------- - wall : solprop.modes.capillary.CoherentWall or None - The coherent wall declaration, or ``None``. Without one, a multimode - capillary taper carries the taper coupling when ``build.taper_coupling`` - says so, and every other set -- or that one with the coupling declined -- - takes the uncoupled diagonal route. - modes : sequence of Mode - The mode set in state order. - grid : solprop.grid.TimeGrid - The run's grid. - frame : solprop.linear.Frame - The one shared co-moving frame. - build : _Assembly - The assembly's knobs, including ``dz_max`` and the prequalified reuse. - - Returns - ------- - LinearOperator - Section 3 of the contract, carrying ``coupled_flow`` exactly when a wall was - declared or the taper coupling applies (:func:`_taper_assembly`). + - ``wall``: the coherent wall declaration, or ``None``. Without one, a multimode + capillary taper carries the taper coupling when ``build.taper_coupling`` says so, + and every other set -- or that one with the coupling declined -- takes the + uncoupled diagonal route. + - Returns: section 3 of the contract, carrying ``coupled_flow`` exactly when a wall + was declared or the taper coupling applies (:func:`_taper_assembly`). """ if wall is not None: # Every channel's frequency-only terms are sampled once for the whole wall @@ -3915,31 +3279,8 @@ def _wall_assembly( built from the real mode set, so a saved amplitude still carries each channel's own ``sqrt(Re n_eff)``. - Parameters - ---------- - wall : solprop.modes.capillary.CoherentWall - The declaration, carrying the two limits the block is admitted under. - modes : sequence of Mode - The contiguous ``HE_11`` to ``HE_1N`` block in state order. - grid : solprop.grid.TimeGrid - The run's grid; supplies the frequency axis and the active band. - frame : solprop.linear.Frame - The one shared co-moving frame. - build : _Assembly - The assembly's knobs, including the ``dz_max`` the conditioning bound is - declared over. - - Returns - ------- - LinearOperator - The mode-independent carrier, the real mode set's power weight and the - coupled flow leaf. - - Raises - ------ - TranslationError - If the run is not one this block is derived for, if the carrier is not the - exact constant representation, or if the total generator is not passive. + - ``modes``: the contiguous ``HE_11`` to ``HE_1N`` block in state order. + - ``grid``: the run's grid; supplies the frequency axis and the active band. """ block = tuple(modes) _check_wall_route(build) @@ -4015,18 +3356,6 @@ def _check_wall_method(wall: CoherentWall, first: MarcatiliMode) -> None: than dropped. CF4 is admitted beside the vector block of ``impedance="te_tm"`` too: it integrates the stored expansion of whichever block the route built and reads nothing else of it. - - Parameters - ---------- - wall : solprop.modes.capillary.CoherentWall - The declaration. - first : solprop.modes.capillary.MarcatiliMode - The block's first channel, whose profiles are every channel's. - - Raises - ------ - TranslationError - If CF4 is declared on a constant guide. """ if wall.method != "cf4": return @@ -4068,31 +3397,6 @@ def _vector_wall_assembly( the vector connection, which is block diagonal -- each family's own ``C_mn`` of its Bessel order, and no term between the families -- as derived and as measured (:func:`solprop.modes.capillary.wall_connection`). - - Parameters - ---------- - wall : solprop.modes.capillary.CoherentWall - The declaration, carrying ``impedance="te_tm"`` and the two limits. - block : tuple of Mode - One azimuthal order of the capillary, in state order. - grid : solprop.grid.TimeGrid - The run's grid. - frame : solprop.linear.Frame - The one shared co-moving frame. - build : _Assembly - The assembly's knobs. - - Returns - ------- - LinearOperator - The mode-independent carrier, the real mode set's power weight and the - constant-Schur coupled flow of the vector block, or the varying route's flow. - - Raises - ------ - TranslationError - If the total generator is not passive, or on everything the varying route - refuses. """ first = cast("MarcatiliMode", block[0]) if not first.z_constant: @@ -4192,33 +3496,8 @@ def _varying_wall_assembly( still the same equation, because the residual is defined against whichever block the step's anchor names, so the edges need not be landed on exactly. - Parameters - ---------- - wall : solprop.modes.capillary.CoherentWall - The declaration, carrying the two limits and the section count. - block : tuple of Mode - The contiguous ``HE_11`` to ``HE_1N`` set in state order, or one azimuthal - order of the capillary for ``wall.impedance == "te_tm"``. - grid : solprop.grid.TimeGrid - The run's grid. - frame : solprop.linear.Frame - The one shared co-moving frame. - build : _Assembly - The assembly's knobs. - - Returns - ------- - LinearOperator - The mode-independent carrier, the real mode set's power weight and the - frozen-residual coupled flow -- or, for ``wall.method == "cf4"``, the CF4 flow - of the same expansion (:func:`solprop.translate.coupled.cf4_flow`). - - Raises - ------ - TranslationError - If the carrier is not mode independent, if the total generator grows, or on - everything :func:`solprop.translate.coupled.frozen_residual_flow` or - :func:`solprop.translate.coupled.cf4_flow` refuses. + - ``block``: the contiguous ``HE_11`` to ``HE_1N`` set in state order, or one + azimuthal order of the capillary for ``wall.impedance == "te_tm"``. """ first = cast("MarcatiliMode", block[0]) carrier = build_phi( @@ -4347,32 +3626,8 @@ def _taper_assembly( frozen per section of the carrier's own table with the variation and the connection in the explicit residual (:func:`frozen_residual_flow`). - Parameters - ---------- - block : tuple of MarcatiliMode - The contiguous ``HE_11`` to ``HE_1N`` set on a varying radius, from - :func:`solprop.modes.capillary.taper_coupling_block`. - grid : solprop.grid.TimeGrid - The run's grid. - frame : solprop.linear.Frame - The one shared co-moving frame. - build : _Assembly - The assembly's knobs. - - Returns - ------- - LinearOperator - The mode-independent carrier, the real mode set's power weight and the - frozen-residual coupled flow. - - Raises - ------ - TranslationError - If the run carries a noise model or a second field component, if the total - generator grows, or on everything - :func:`solprop.translate.coupled.frozen_residual_flow` refuses. - solprop.exceptions.ModeError - If the channels do not declare one guide with analytic profile derivatives. + - ``block``: the contiguous ``HE_11`` to ``HE_1N`` set on a varying radius, from + :func:`solprop.modes.capillary.taper_coupling_block`. """ _check_taper_route(build, grid) # The block's own door, before the generator differentiates the profiles: it @@ -4455,18 +3710,7 @@ def _taper_assembly( def _check_taper_route(build: _Assembly, grid: TimeGrid) -> None: """Refuse, by name, a run the taper coupling cannot be carried in. - Parameters - ---------- - build : _Assembly - The assembly's knobs. - grid : solprop.grid.TimeGrid - The run's grid, which must be a field grid. - - Raises - ------ - TranslationError - For an envelope grid, a second field component or the ``'modified'`` noise - model, naming the opt-out. + - ``grid``: the run's grid, which must be a field grid. """ if grid.kind != "field": msg = ( @@ -4508,19 +3752,9 @@ def _taper_generator_at( rest of each channel's normalisation rate (:func:`_channel_normalisation_at`). The difference is formed from ``n_eff - 1`` without the round trip through unity. - Parameters - ---------- - block : tuple of MarcatiliMode - The contiguous ``HE_11`` to ``HE_1N`` set. - grid : solprop.grid.TimeGrid - The run's grid; the generator is exactly zero off its active band. - z : float - Position in m. - - Returns - ------- - numpy.ndarray - The generator in 1/m, complex, shape ``(n_modes, n_modes, n_omega)``. + - ``block``: the contiguous ``HE_11`` to ``HE_1N`` set. + - ``z``: position in m. + - Returns: the generator in 1/m, complex, shape ``(n_modes, n_modes, n_omega)``. """ band = grid.omega[grid.active_mask] reference = np.asarray(block[0].neff_minus_one(band, z)) @@ -4544,17 +3778,8 @@ def _section_edges( kink of the radius and the density and never spans one; `CoherentWall.sections` is the refinement inside each of those, in equal parts. - Parameters - ---------- - breakpoints : numpy.ndarray - The carrier's interval edges in m, strictly increasing. - wall : solprop.modes.capillary.CoherentWall - The declaration, carrying the section count. - - Returns - ------- - numpy.ndarray - The edges in m, strictly increasing, with the same two ends. + - ``breakpoints``: the carrier's interval edges in m, strictly increasing. + - Returns: the edges in m, strictly increasing, with the same two ends. """ if wall.sections == 1: return np.array(breakpoints, dtype=float) @@ -4570,17 +3795,8 @@ def _padded_degrees( ) -> np.ndarray: """Return a coefficient block lengthened along its degree axis with exact zeros. - Parameters - ---------- - coefficients : numpy.ndarray - The block, shape ``(n_intervals, n_deg, ...)``. - degrees : int - The degree axis to reach, at least the block's own. - - Returns - ------- - numpy.ndarray - The block, with the same values and a degree axis of `degrees`. + - ``degrees``: the degree axis to reach, at least the block's own. + - Returns: the block, with the same values and a degree axis of `degrees`. """ pad = [(0, 0)] * coefficients.ndim pad[1] = (0, degrees - coefficients.shape[1]) @@ -4596,25 +3812,7 @@ def _wall_block_at( ) -> WallOperator | VectorWallOperator: """Return the wall block the declared impedance names, at one position. - Parameters - ---------- - block : tuple of Mode - The channels in state order. - grid : solprop.grid.TimeGrid - The run's grid. - z : float - Position in m. - validity_limit : float - Largest admitted validity ratio, as :class:`CoherentWall` declares it. - impedance : str - ``"mean"`` builds :func:`solprop.modes.capillary.wall_operator`, ``"te_tm"`` - :func:`solprop.modes.capillary.vector_wall_operator`. - - Returns - ------- - WallOperator or VectorWallOperator - The block, built on the grid's active band with its validity ratios taken - over the requested band (``grid.inband_mask``). + - ``z``: position in m. """ if impedance == "mean": return wall_operator( @@ -4650,24 +3848,11 @@ def _wall_generator_at( the two. Both the block and the reference channel are read **at this position**, which is the whole difference from the constant section. - Parameters - ---------- - block : tuple of Mode - The contiguous ``HE_11`` to ``HE_1N`` set. - grid : solprop.grid.TimeGrid - The run's grid. - z : float - Position in m. - sampled : list - Collects the limits of every position sampled, for the admission that follows. - impedance : str, optional - Which block to sample, ``"mean"`` or ``"te_tm"``. - - Returns - ------- - numpy.ndarray - The generator in 1/m, complex, shape ``(n_modes, n_modes, n_omega)``, exactly - zero off the active band. + - ``block``: the contiguous ``HE_11`` to ``HE_1N`` set. + - ``z``: position in m. + - ``impedance``: which block to sample, ``"mean"`` or ``"te_tm"``. + - Returns: the generator in 1/m, complex, shape ``(n_modes, n_modes, n_omega)``, + exactly zero off the active band. """ with warnings.catch_warnings(): warnings.simplefilter("ignore", GrazingIncidenceWarning) @@ -4700,19 +3885,9 @@ def _channel_normalisation_at( its variation, and CF4 integrates it with the rest. The slope is the analytic :meth:`solprop.modes.capillary.MarcatiliMode.dneff_dz`, never a difference. - Parameters - ---------- - block : tuple of Mode - The contiguous ``HE_11`` to ``HE_1N`` set. - grid : solprop.grid.TimeGrid - The run's grid; the rate is exactly zero off its active band. - z : float - Position in m. - - Returns - ------- - numpy.ndarray - The rate in 1/m, shape ``(n_modes, n_omega)``. + - ``block``: the contiguous ``HE_11`` to ``HE_1N`` set. + - ``z``: position in m. + - Returns: the rate in 1/m, shape ``(n_modes, n_omega)``. """ band = grid.omega[grid.active_mask] logarithmic = np.stack( @@ -4751,21 +3926,10 @@ def _wall_shifted_generator( the absolute rounding of ``k`` (about 1e-9 1/m) instead of that of the refractivities. - Parameters - ---------- - generator : numpy.ndarray - The optics-convention block in 1/m, shape ``(n_modes, n_modes, n_omega)``. - first : solprop.modes.capillary.MarcatiliMode - The channel the cumulative carrier was built from. - grid : solprop.grid.TimeGrid - The run's grid. - z : float, optional - Position in m both are read at. - - Returns - ------- - numpy.ndarray - The internal-convention generator in 1/m, same shape. + - ``generator``: the optics-convention block in 1/m, shape ``(n_modes, n_modes, + n_omega)``. + - ``z``: position in m both are read at. + - Returns: the internal-convention generator in 1/m, same shape. """ band = grid.omega[grid.active_mask] refractivity = np.asarray(first.core_index_minus_one(band, z)) - np.asarray( @@ -4798,21 +3962,10 @@ def _wall_connection_at( the same way: each channel's weight is its own, whichever family it is in, and the terms between the families are zero before the weights and after them. - Parameters - ---------- - block : tuple of Mode - The contiguous ``HE_11`` to ``HE_1N`` set, or one azimuthal order. - grid : solprop.grid.TimeGrid - The run's grid. - z : float - Position in m. - impedance : str, optional - Which block the connection is of, ``"mean"`` or ``"te_tm"``. - - Returns - ------- - numpy.ndarray - ``C(z)`` in 1/m, complex, shape ``(n_modes, n_modes, n_omega)``. + - ``block``: the contiguous ``HE_11`` to ``HE_1N`` set, or one azimuthal order. + - ``z``: position in m. + - ``impedance``: which block the connection is of, ``"mean"`` or ``"te_tm"``. + - Returns: ``C(z)`` in 1/m, complex, shape ``(n_modes, n_modes, n_omega)``. """ return _true_power_connection( wall_connection(block, z, impedance=impedance), block, grid, z @@ -4831,21 +3984,9 @@ def _true_power_connection( ``sqrt(w_n / w_m) C_mn``, exactly zero off the active band (:func:`_wall_connection_at` gives the reason). - Parameters - ---------- - link : numpy.ndarray - ``C`` in 1/m, real, shape ``(n_modes, n_modes)``. - block : tuple of Mode - The channels in state order. - grid : solprop.grid.TimeGrid - The run's grid. - z : float - Position in m. - - Returns - ------- - numpy.ndarray - The term in 1/m, complex, shape ``(n_modes, n_modes, n_omega)``. + - ``link``: ``C`` in 1/m, real, shape ``(n_modes, n_modes)``. + - ``z``: position in m. + - Returns: the term in 1/m, complex, shape ``(n_modes, n_modes, n_omega)``. """ band = grid.omega[grid.active_mask] weight = np.stack([np.real(np.asarray(mode.neff(band, z))) for mode in block]) @@ -4880,32 +4021,16 @@ def _check_wall_total_varying( # noqa: PLR0917 - called positionally passivity is checked at every sampled position by :func:`solprop.modes.capillary.wall_operator`. - Parameters - ---------- - coefficients : numpy.ndarray - The stored generator's Chebyshev coefficients in 1/m. - linear : solprop.translate.linop.LinearOperator - The carrier the flow is multiplied by. - edges : numpy.ndarray - The section edges in m. - block : tuple of Mode - The mode set. ``Phi`` was built from its lowest channel and the flow's diagonal - completes every other channel's normalisation, so the allowance is each - channel's own. - grid : solprop.grid.TimeGrid - The run's grid, which supplies the band the allowance is taken over. - tail : float, optional - The expansion's relative Chebyshev tail, dimensionless. The interpolant's - section mean departs from the function's by about the size of its last - coefficients, so twice the tail times the block's scale joins the rounding - budget: the normalisation term is compared through the interpolant on one side - and exactly on the other. - - Raises - ------ - TranslationError - If any channel grows on average over any section by more than its own - normalisation term accounts for. + - ``coefficients``: the stored generator's Chebyshev coefficients in 1/m. + - ``edges``: the section edges in m. + - ``block``: the mode set. ``Phi`` was built from its lowest channel and the flow's + diagonal completes every other channel's normalisation, so the allowance is each + channel's own. + - ``tail``: the expansion's relative Chebyshev tail, dimensionless. The + interpolant's section mean departs from the function's by about the size of its + last coefficients, so twice the tail times the block's scale joins the rounding + budget: the normalisation term is compared through the interpolant on one side and + exactly on the other. """ count = len(block) degrees = np.arange(coefficients.shape[1]) @@ -4941,15 +4066,7 @@ def _check_wall_total_varying( # noqa: PLR0917 - called positionally def _validated(problem: "PropagationProblem", request: "RunRequest") -> None: - """Validate an assembled problem and run every diagnostic that only warns. - - Parameters - ---------- - problem : PropagationProblem - The assembled contract. - request : solprop.translate.run_request.RunRequest - The request of a fresh run of it. - """ + """Validate an assembled problem and run every diagnostic that only warns.""" validate_run(problem, request) validate_coupled_flow(problem, request.stepper) _admit_dense(problem, request.stepper) @@ -4972,19 +4089,10 @@ def _normalisation_rate( three orders above this -- and the allowance is each channel's term's own exact mean over the section, ``|ln(w_n(hi)/w_n(lo))|/(2 (hi - lo))``. - Parameters - ---------- - block : tuple of Mode - The channels, in state order. - grid : solprop.grid.TimeGrid - The run's grid; the allowance is zero off its active band, where the block is. - lo, hi : float - The section's ends in m. - - Returns - ------- - numpy.ndarray - The non-negative allowance in 1/m, shape ``(n_modes, n_omega)``. + - ``grid``: the run's grid; the allowance is zero off its active band, where the + block is. + - ``lo``, ``hi``: the section's ends in m. + - Returns: the non-negative allowance in 1/m, shape ``(n_modes, n_omega)``. """ band = grid.omega[grid.active_mask] rate = np.zeros((len(block), grid.n_omega)) @@ -5058,15 +4166,8 @@ def validate_coupled_flow( def _declared_steps(settings: StepperSettings) -> dict[str, float]: """Return the steps a run declares, by name: the two limits and a replay's largest. - Parameters - ---------- - settings : StepperSettings - The run's stepper section. - - Returns - ------- - dict - ``dz_init``, ``dz_max`` and, for a fixed schedule, its largest step, in m. + - Returns: ``dz_init``, ``dz_max`` and, for a fixed schedule, its largest step, in + m. """ declared = { "dz_init": float(settings.dz_init), @@ -5084,23 +4185,7 @@ def _warn_advisory_step( settings: StepperSettings, scored: Bool[np.ndarray, " n_omega"], ) -> None: - """Warn when a declared step exceeds the explicit residual's advisory limit. - - Parameters - ---------- - flow : solprop.translate.coupled.CoupledFlowTables - The leaf. - settings : StepperSettings - The run's stepper section. - scored : numpy.ndarray of bool - The error norm's bins, on which alone the residual is applied. - - Warns - ----- - solprop.exceptions.SolpropWarning - Naming the step, the term, the section, the frequency, ``K``, the - conditioning factor and the limit. - """ + """Warn when a declared step exceeds the explicit residual's advisory limit.""" limits = advisory_step(flow, scored) section, bin_index = np.unravel_index(int(np.argmin(limits)), limits.shape) smallest = float(limits[section, bin_index]) @@ -5164,19 +4249,8 @@ def _warn_cf4_defect( estimator**, not a bound, and the warning says so; nothing here changes the caller's tolerances or schedule. - Parameters - ---------- - flow : solprop.translate.coupled.CoupledFlowTables - The leaf, carrying at least one ``"cf4"`` section. - settings : StepperSettings - The run's stepper section. - kinks : numpy.ndarray - The run's hard kinks in m, at which every step ends. - - Warns - ----- - solprop.exceptions.SolpropWarning - Naming the step, the section, the frequency, the estimate and the tolerance. + - ``flow``: the leaf, carrying at least one ``"cf4"`` section. + - ``kinks``: the run's hard kinks in m, at which every step ends. """ declared = _declared_steps(settings) name = max(declared, key=lambda key: declared[key]) @@ -5211,16 +4285,6 @@ def _check_wall_route(build: _Assembly) -> None: :func:`solprop.modes.capillary.wall_operator`, and an empty active band by that same function, which names the mask it was given and is the layer that evaluates over it. - - Parameters - ---------- - build : _Assembly - The assembly's knobs. - - Raises - ------ - TranslationError - Naming what the block cannot carry. """ if build.n_pol != 1: msg = ( @@ -5258,22 +4322,6 @@ def _refuse_a_wall_off_the_capillary_route( with ``linear.coupled_flow`` absent, no ``modal.flow`` key on its record and no word said. The other two doors are the mode set's (:func:`solprop.modes.capillary.wall_operator`) and the backend's. - - Parameters - ---------- - wall : solprop.modes.capillary.CoherentWall or None - What the run declared. - general : bool - Whether the run takes the general modal route. - spatial : bool - Whether the run carries a spatial geometry. - taper_coupling : bool, optional - Whether the run keeps the taper coupling, which a wall cannot decline. - - Raises - ------ - TranslationError - Naming the route that cannot carry the block, or the declined coupling. """ if wall is not None and not taper_coupling: msg = ( @@ -5309,19 +4357,7 @@ def _check_wall_total( easy mistake of writing ``-i B`` where ``-i conj(B)`` was meant -- turns the diagonal into gain and is caught. - Parameters - ---------- - generator : numpy.ndarray - The stored internal-convention generator in 1/m. - linear : LinearOperator - The carrier, whose constant term is what the flow is multiplied by. - count : int - Channel count, used in the refusal. - - Raises - ------ - TranslationError - If any channel grows at any frequency. + - ``generator``: the stored internal-convention generator in 1/m. """ assert linear.phi_constant is not None total = np.real(linear.phi_constant[0, :, 0, :]) + np.real( @@ -5350,21 +4386,9 @@ def _check_general( ) -> None: """Refuse every legacy knob and every grid a general modal route cannot take. - Parameters - ---------- - route : solprop.translate.general.GeneralModalRoute - The guide's leaves. - grid : solprop.grid.TimeGrid - The run's grid. - refusals : _Assembly - The assembly's knobs; each legacy one is refused by name when it is set, - and the declared responses must be empty, a general guide's belonging to - its material regions. - - Raises - ------ - TranslationError - Naming what cannot be combined with a general modal route. + - ``refusals``: the assembly's knobs; each legacy one is refused by name when it is + set, and the declared responses must be empty, a general guide's belonging to its + material regions. """ named = ( ("quadrature", refusals.quadrature), @@ -5439,19 +4463,9 @@ def _general_prefactor( active band as every other route's prefactor is, so no unnormalised polarisation survives there for the stages or the error estimate to see. - Parameters - ---------- - grid : solprop.grid.TimeGrid - The run's grid, which supplies the crop scale and the active band. - linear : solprop.translate.linop.LinearOperator - The assembled operator, whose z span every other table must share. - n_pol : int - State polarisations, one on this route. - - Returns - ------- - solprop.translate.chebyshev.PiecewiseChebyshev - One interval of degree zero with value shape ``(n_modes, n_pol, n_omega)``. + - ``linear``: the assembled operator, whose z span every other table must share. + - Returns: one interval of degree zero with value shape ``(n_modes, n_pol, + n_omega)``. """ n_modes = linear.value_shape[1] values = np.zeros((1, 1, n_modes, n_pol, grid.n_omega), dtype=np.complex128) @@ -5467,17 +4481,7 @@ def _general_prefactor( def _check_scalar_domain( table: PiecewiseChebyshev, name: str, *, upper: float = math.inf ) -> None: - """Check a scalar polynomial at endpoints and every real stationary point. - - Parameters - ---------- - table : PiecewiseChebyshev - Scalar profile table checked over every polynomial interval. - name : str - Table name used in a refusal diagnostic. - upper : float - Inclusive upper domain bound, in the table value units; infinity by default. - """ + """Check a scalar polynomial at endpoints and every real stationary point.""" if table.value_shape != (): raise TranslationError(f"{name} must be a scalar table") for row, degree in zip(table.coefficients, table.degrees, strict=True): @@ -5498,30 +4502,14 @@ def _walk_arrays( ) -> list[tuple[str, np.ndarray]]: """Collect every NumPy array in the problem tree, with its dotted name. - Parameters - ---------- - problem : PropagationProblem - The problem to walk. - run : _RunPolicy or None, optional - The run's stepper section and statistics, walked after the initial state - under the names ``stepper`` and ``stats_spec`` they have always had. - - Returns - ------- - list of tuple - ``(name, array)`` pairs, in field order. + - Returns: ``(name, array)`` pairs, in field order. """ found: list[tuple[str, np.ndarray]] = [] def visit(prefix: str, node: object) -> None: """Add every array reachable from one node. - Parameters - ---------- - prefix : str - Dotted name of the node. - node : object - A dataclass, an array, or anything else. + - ``node``: a dataclass, an array, or anything else. """ if isinstance(node, np.ndarray): found.append((prefix, node)) @@ -5554,15 +4542,7 @@ def visit(prefix: str, node: object) -> None: def _walk_tables(problem: PropagationProblem) -> list[tuple[str, PiecewiseChebyshev]]: """Collect every Chebyshev table along z, with its dotted name. - Parameters - ---------- - problem : PropagationProblem - The problem to walk. - - Returns - ------- - list of tuple - ``(name, table)`` pairs. + - Returns: ``(name, table)`` pairs. """ candidates = [ ("linear.beta0", problem.linear.beta0), @@ -5630,27 +4610,9 @@ def _state( ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: """Bring an initial field to the canonical single-trajectory layout. - Parameters - ---------- - field0 : numpy.ndarray - A ``Pulse.build`` result of shape ``(n_modes, n_pol, n_omega)``. The shape is - checked here rather than by an annotation, so that a wrong rank is reported by - name in production too. A group of trajectories is a sequence of problems - (SEP 47 section 6.7), never a stacked field. - n_omega : int - Required number of spectral bins from the grid. - - Returns - ------- - numpy.ndarray - The state, complex128, the problem's own: copied once here as :class:`Initial` - holds it, so that it is not copied again there, and taken as it is when the - problem already holds it. - - Raises - ------ - TranslationError - If the array does not have three axes or the grid extent differs. + - Returns: the state, complex128, the problem's own: copied once here as + :class:`Initial` holds it, so that it is not copied again there, and taken as it + is when the problem already holds it. """ values = np.asarray(field0) if values.ndim != 3: @@ -5671,24 +4633,10 @@ def _material_mixture( ) -> Mixture | None: """Resolve the material shared by the linear and response paths. - Parameters - ---------- - modes : sequence of Mode - Ordered mode set, empty for spatial geometry. - medium : SpatialMedium or None - Spatial material, or None for modal geometry. - density : ZProfile, float or None - Optional explicit response density in m^-3. - - Returns - ------- - Mixture or None - The declared mixture, or None for the legacy single material. - - Raises - ------ - TranslationError - If an explicit density conflicts with a mixture or a gas medium. + - ``modes``: ordered mode set, empty for spatial geometry. + - ``medium``: spatial material, or None for modal geometry. + - ``density``: optional explicit response density in m^-3. + - Returns: the declared mixture, or None for the legacy single material. """ if medium is not None and isinstance(medium.material, Gas) and density is not None: # The linear operator takes the medium's own density path; an explicit one @@ -5758,18 +4706,9 @@ def _check_tdse_assembly_route( ) -> None: """Refuse paths whose optical ownership is outside the atomic contract. - Parameters - ---------- - descriptor : TDSEDescriptor or None - Atomic declaration, or None for an ordinary response. - modal : bool - Whether the geometry is the uncoupled modal route. - scheduled : bool - Whether a fixed step schedule was supplied. - absorber_scheme : str - Temporal collar application policy. - density : ZProfile, float or None - Requested density override in m^-3, refused for an atomic declaration. + - ``descriptor``: atomic declaration, or None for an ordinary response. + - ``density``: requested density override in m^-3, refused for an atomic + declaration. """ if descriptor is None: return @@ -5790,18 +4729,6 @@ def _admit_dense(problem: "PropagationProblem", stepper: StepperSettings) -> Non Adams interpolant alike. The per-step temporal collar changes the state after the step, so an interpolated plane would be uncollared; a coupled flow's action and the ``modified`` noise's draw are outside the interpolant. - - Parameters - ---------- - problem : PropagationProblem - The assembled problem. - stepper : StepperSettings - The run's stepper section. - - Raises - ------ - TranslationError - Naming the combination refused. """ if not stepper.interpolate_planes: return @@ -5839,23 +4766,7 @@ def _stepper_settings( an overshoot, so both keep the version-16 policy: the interval cap and no start-up rule. - Parameters - ---------- - linear : LinearOperator - The problem's linear operator, whose coupled flow decides the policy. - controller : solprop.stepper.controller.PIController - The controller, whose reject-free switch decides the policy too. - interval_cap : bool or None - The interval cap asked for; ``None`` takes the route's. - auto_init : bool - Whether the start-up rule was asked for. - **fields - The other :class:`StepperSettings` fields. - - Returns - ------- - StepperSettings - The settings. + - ``interval_cap``: the interval cap asked for; ``None`` takes the route's. """ restrained = linear.coupled_flow is not None or controller.reject_free return StepperSettings( @@ -5874,15 +4785,7 @@ def _printed_down(value: float) -> str: to nearest would give ``5.239e-03``, a step the check still refuses. The decimal expansion of the double is exact, so the floor is too. - Parameters - ---------- - value : float - The limit, positive and finite. - - Returns - ------- - str - Four significant digits, as ``f"{value:.3e}"`` writes them, never above `value`. + - ``value``: the limit, positive and finite. """ exact = Decimal(value) exponent = exact.adjusted() @@ -5901,20 +4804,7 @@ def _history_replay_advice(printed: str, *, split_step: bool, integrator: bool) Capping the discovery run with ``dz_max`` under the limit always replays; the split temporal collar lifts the temporal collar's limit alone. - Parameters - ---------- - printed : str - The limit as the refusal prints it, rounded down, m. - split_step : bool - Whether ``absorber_scheme="split_step"`` lifts the limit, as it does the - temporal collar's. - integrator : bool - Whether the limit is a stability limit, which depends on the integrator. - - Returns - ------- - str - Sentences, starting with a space, naming the remedies. + - ``printed``: the limit as the refusal prints it, rounded down, m. """ split = ( 'either give absorber_scheme="split_step" to both the discovery run and ' @@ -5942,18 +4832,6 @@ def _check_harmonic_frame(harmonic: bool, compiled: CompiledResponses) -> None: :func:`solprop.translate.responses.declares_harmonic_carrier`; the operator it writes is right only if the compiled pass agrees on whether it carries the harmonic (:attr:`solprop.translate.responses.CompiledResponses.harmonic_carrier`). - - Parameters - ---------- - harmonic : bool - What the declared responses said. - compiled : solprop.translate.responses.CompiledResponses - The compiled responses. - - Raises - ------ - TranslationError - If the two disagree, an internal inconsistency that no input should reach. """ if compiled.harmonic_carrier != harmonic: msg = ( @@ -5975,18 +4853,7 @@ def _refuse_carrier_removing_frame(frame: Frame | None, harmonic: bool) -> None: spurious mismatch (:class:`solprop.linear.Frame`, :func:`solprop.linear.frame_origin`). - Parameters - ---------- - frame : solprop.linear.Frame or None - The frame the caller gave, or ``None`` for the co-moving one. - harmonic : bool - Whether the declared responses compile the harmonic carrier - (:func:`solprop.translate.responses.declares_harmonic_carrier`). - - Raises - ------ - TranslationError - Naming ``subtract_beta0``, if `frame` removes ``beta_0`` beside the harmonic. + - ``frame``: the frame the caller gave, or ``None`` for the co-moving one. """ if frame is not None and frame.subtract_beta0 and harmonic: msg = ( @@ -6539,27 +5406,8 @@ def _route( ) -> Route: """Return the route variant an assembled transform and its leaves describe. - Parameters - ---------- - transform : solprop.translate.modal.Transform - The assembled transform: a spatial geometry makes a spatial route, general - modal tables a general one, and anything else a modal one. - tdse : solprop.translate.routes.TDSESource or None - The TDSE extension, admitted on the modal route alone. - flux : solprop.translate.spatial.FluxPlan or None - The plane measure, which a spatial route requires and no other carries. - transverse : solprop.spatial_absorber.SpatialAbsorberDesign or None - The transverse boundary, a spatial route's alone. - - Returns - ------- - solprop.translate.routes.Route - The variant. - - Raises - ------ - TranslationError - If a leaf is given to a route that does not carry it. + - ``transform``: the assembled transform: a spatial geometry makes a spatial route, + general modal tables a general one, and anything else a modal one. """ geometry = transform.geometry if (geometry is None) != (flux is None): @@ -6692,22 +5540,10 @@ def _power_initial( ``neff`` call at the last few bits, and the two conventions disagreeing at the entrance was measured at 3e-15 of the launch. - Parameters - ---------- - initial : Initial - Fresh true-power launch and noise in canonical shape. - linear : solprop.translate.linop.LinearOperator - Assembled linear group; its modal power-weight row is the entrance weight, and - its absence marks spatial geometry, which carries no modal conversion. - index : float or None - Constant index override, or None for the physical effective index. - reused : Initial or None - Previously scaled launch retained only when its arrays match. - - Returns - ------- - Initial - Internal vacuum-normalised launch and noise. + - ``initial``: fresh true-power launch and noise in canonical shape. + - ``index``: constant index override, or None for the physical effective index. + - ``reused``: previously scaled launch retained only when its arrays match. + - Returns: internal vacuum-normalised launch and noise. """ if linear.power_weight_representation is not None and index != 1.0: root_weight = np.sqrt(power_weight_row(linear, initial.z)) @@ -6741,17 +5577,8 @@ def _density( ) -> ZProfile | float: """Choose the density path a compiled response multiplies its coefficient by. - Parameters - ---------- - modes : sequence of Mode - The mode set; a gas-filled capillary declares its own ``density`` path. - density : solprop.profiles.ZProfile or float or None - The caller's choice, or ``None`` to take the mode's. - - Returns - ------- - solprop.profiles.ZProfile or float - The density, or 1.0 when neither the caller nor the mode states one. + - ``density``: the caller's choice, or ``None`` to take the mode's. + - Returns: the density, or 1.0 when neither the caller nor the mode states one. """ if density is not None: return density @@ -6766,26 +5593,11 @@ def _components( ) -> tuple[str, ...]: """Resolve and validate the transverse components a transform resolves. - Parameters - ---------- - components : sequence of str or None - What the caller asked for, or ``None`` to resolve the mode set's components. - quadrature : solprop.modes.quadrature.TransverseQuadrature or None - The fixed rule, or ``None`` for the mode-averaged case, which has no components - to resolve. - modes : sequence of Mode - Modes whose continuous profiles declare the nonzero components. - - Returns - ------- - tuple of str - The resolved components. - - Raises - ------ - TranslationError - If components are given without a rule, or are not a non-empty subset of - ``("x", "y")``. + - ``components``: what the caller asked for, or ``None`` to resolve the mode set's + components. + - ``quadrature``: the fixed rule, or ``None`` for the mode-averaged case, which has + no components to resolve. + - ``modes``: modes whose continuous profiles declare the nonzero components. """ if components is None: if quadrature is None: @@ -6828,36 +5640,10 @@ def _transform( ) -> Transform: """Build the mode-averaged or the multimode transform, as the problem asks. - Parameters - ---------- - modes : sequence of Mode - The mode set. - grid : solprop.grid.TimeGrid - The grid. - quadrature : solprop.modes.quadrature.TransverseQuadrature or None - The fixed rule, or ``None`` for the mode-averaged case. - z_max : float - Length the run covers, m. - n_pol : int - Polarisation components of the state, 1 or 2. - components : sequence of str or None - The transverse components the rule resolves, or ``None`` for the union of - the modes' nonzero components at the entrance (:func:`_components`). - margin : float - Fraction of `z_max` the tables extend beyond the end. - tol : float - Target bound on the uniform error of the tables. - - Returns - ------- - Transform - Section 4 of the kernel contract. - - Raises - ------ - TranslationError - If a mode set is given without a rule, if the components are invalid, or if the - transform and the state would both carry a polarisation axis. + - ``quadrature``: the fixed rule, or ``None`` for the mode-averaged case. + - ``z_max``: length the run covers, m. + - ``components``: the transverse components the rule resolves, or ``None`` for the + union of the modes' nonzero components at the entrance (:func:`_components`). """ # A trait, not a class: any future non-degenerate partner pair (a tabulated # pair, Zeisberger) declares it and is refused here without naming a class. diff --git a/src/solprop/translate/radial.py b/src/solprop/translate/radial.py index 485ab1ee..529a7d6a 100644 --- a/src/solprop/translate/radial.py +++ b/src/solprop/translate/radial.py @@ -174,20 +174,10 @@ def radial_operators( grid = dispersion.grid rate = np.zeros(grid.n_omega) if absorber is None else absorber.spectral_rate - def material(model: SpatialDispersion, z: float = 0.0) -> SeparableOperator: + def _material(model: SpatialDispersion, z: float = 0.0) -> SeparableOperator: """Assemble the radial coefficients of one material sample. - Parameters - ---------- - model : solprop.spatial.SpatialDispersion - Bulk material and frame at the sampled density. - z : float, optional - Position in m at which to evaluate the material. - - Returns - ------- - solprop.translate.spatial.SeparableOperator - Shared material vectors over the radial columns. + - ``z``: position in m at which to evaluate the material. """ square, frame, numerator, clamped = model.shared_values(z) return SeparableOperator( @@ -206,24 +196,15 @@ def material(model: SpatialDispersion, z: float = 0.0) -> SeparableOperator: kperp_rate=transverse_rate, ) - def sampled( + def _sampled( model: SpatialDispersion, z: float = 0.0 ) -> tuple[np.ndarray, np.ndarray]: """Materialise radial coefficients with the complete seam shape. - Parameters - ---------- - model : solprop.spatial.SpatialDispersion - Bulk material and frame at the sampled density. - z : float, optional - Position in m at which to evaluate the material. - - Returns - ------- - tuple of numpy.ndarray - Linear rate and cropped nonlinear prefactor, shared by polarisation. + - ``z``: position in m at which to evaluate the material. + - Returns: linear rate and cropped nonlinear prefactor, shared by polarisation. """ - return separable_values(material(model, z)) + return separable_values(_material(model, z)) paths = { name: path @@ -248,24 +229,17 @@ def sampled( kinks=kinks, ) - def at_parameters(values: Mapping[str, float]) -> tuple[np.ndarray, np.ndarray]: + def _at_parameters(values: Mapping[str, float]) -> tuple[np.ndarray, np.ndarray]: """Evaluate a frozen density sample for the parameter expansion. - Parameters - ---------- - values : collections.abc.Mapping - Density sample in m^-3 under the name ``density``. - - Returns - ------- - tuple of numpy.ndarray - Linear rate and nonlinear prefactor at that density. + - ``values``: density sample in m^-3 under the name ``density``. + - Returns: linear rate and nonlinear prefactor at that density. """ medium = dispersion.medium.at_parameters(values) - return sampled(replace(dispersion, medium=medium)) + return _sampled(replace(dispersion, medium=medium)) if not paths and reuse_linear is None and reuse_prefactor is None: - operator, prefactor = sampled(dispersion) + operator, prefactor = _sampled(dispersion) coefficients = np.ascontiguousarray(prefactor[None, None]) edges = np.array([0.0, span]) degrees = np.zeros(1, dtype=np.intp) @@ -277,10 +251,10 @@ def at_parameters(values: Mapping[str, float]) -> tuple[np.ndarray, np.ndarray]: PiecewiseChebyshev(coefficients, edges, degrees, kinks), ) - working = _linear_roundoff_target(dispersion, space, sampled, span, tol) + working = _linear_roundoff_target(dispersion, space, _sampled, span, tol) adiabatic = _AdiabaticAmplitude( dispersion, - lambda model, z: longitudinal_root(material(model, z))[:2], + lambda model, z: longitudinal_root(_material(model, z))[:2], paths, kinks, span, @@ -289,7 +263,7 @@ def at_parameters(values: Mapping[str, float]) -> tuple[np.ndarray, np.ndarray]: if len(paths) > MAX_PARAMETERS: linear_table = build( lambda positions: np.stack( - [sampled(dispersion, float(z))[0] for z in positions] + [_sampled(dispersion, float(z))[0] for z in positions] ), 0.0, span, @@ -298,7 +272,7 @@ def at_parameters(values: Mapping[str, float]) -> tuple[np.ndarray, np.ndarray]: ) prefactor_table = build( lambda positions: np.stack( - [sampled(dispersion, float(z))[1] for z in positions] + [_sampled(dispersion, float(z))[1] for z in positions] ), 0.0, span, @@ -357,7 +331,7 @@ def at_parameters(values: Mapping[str, float]) -> tuple[np.ndarray, np.ndarray]: ) if linear is None: linear = build_expansion( - lambda values: at_parameters(values)[0], + lambda values: _at_parameters(values)[0], paths, 0.0, span, @@ -367,7 +341,7 @@ def at_parameters(values: Mapping[str, float]) -> tuple[np.ndarray, np.ndarray]: ) if prefactor is None: prefactor = build_expansion( - lambda values: at_parameters(values)[1], + lambda values: _at_parameters(values)[1], paths, 0.0, span, @@ -492,27 +466,11 @@ class _AdiabaticAmplitude: entrance root, which vanishes there and is an endpoint quantity, so it is never obtained by differentiating a table (:func:`solprop.translate.linop.endpoint_phi`). - Parameters - ---------- - dispersion : solprop.spatial.SpatialDispersion - Material along the run path. - roots : callable - ``(model, z) -> (k_z, propagating)``, each ``(n_r, n_omega)``, from the one - root evaluation the operator uses - (:func:`solprop.translate.spatial.longitudinal_root`). - paths : collections.abc.Mapping - The medium's varying parameter paths, by name. - kinks : numpy.ndarray - Breakpoints of those paths, m. - span : float - End of the tabulated range, m. - n_pol : int - State polarisation components, 1 or 2. - - Raises - ------ - TranslationError - If a column's propagating status changes along the sampled path. + - ``roots``: ``(model, z) -> (k_z, propagating)``, each ``(n_r, n_omega)``, from the + one root evaluation the operator uses + (:func:`solprop.translate.spatial.longitudinal_root`). + - ``kinks``: breakpoints of those paths, m. + - ``span``: end of the tabulated range, m. """ def __init__( # noqa: PLR0917 - called positionally @@ -538,22 +496,9 @@ def __init__( # noqa: PLR0917 - called positionally def _admitted(self, model: SpatialDispersion, z: float) -> np.ndarray: """Return ``Re k_z`` where the term applies, refusing a cutoff crossing. - Parameters - ---------- - model : solprop.spatial.SpatialDispersion - Material sample. - z : float - Position in m at which to evaluate it. - - Returns - ------- - numpy.ndarray - ``Re k_z`` in m^-1, shape ``(n_r, n_omega)``; one where the term is zero. - - Raises - ------ - TranslationError - If the propagating set differs from the entrance's. + - ``z``: position in m at which to evaluate it. + - Returns: ``Re k_z`` in m^-1, shape ``(n_r, n_omega)``; one where the term is + zero. """ root, propagating = self._roots(model, z) changed = (propagating & self._dispersion.grid.active_mask[None, :]) != ( @@ -575,17 +520,7 @@ def _log_ratio( ) -> Complex[np.ndarray, "1 n_r n_pol n_omega"]: """Return ``-1/2 ln(Re k_z / Re k_z(0))`` in the canonical layout. - Parameters - ---------- - model : solprop.spatial.SpatialDispersion - Material sample. - z : float - Position in m at which to evaluate it. - - Returns - ------- - numpy.ndarray - Dimensionless, complex with zero imaginary part. + - ``z``: position in m at which to evaluate it. """ wave = self._admitted(model, z) reference = self._reference @@ -605,15 +540,8 @@ def _log_ratio( def sample(self, z: float) -> Complex[np.ndarray, "1 n_r n_pol n_omega"]: """Evaluate the term's endpoint function along the run path. - Parameters - ---------- - z : float - Position in m. - - Returns - ------- - numpy.ndarray - Dimensionless endpoint function, canonical layout. + - ``z``: position in m. + - Returns: dimensionless endpoint function, canonical layout. """ return self._log_ratio(self._dispersion, z) @@ -622,15 +550,7 @@ def value_at( ) -> Complex[np.ndarray, "1 n_r n_pol n_omega"]: """Evaluate the endpoint function at one point of the parameter box. - Parameters - ---------- - values : collections.abc.Mapping - Parameter values by name, inside the expansion's box. - - Returns - ------- - numpy.ndarray - Dimensionless endpoint function, canonical layout. + - Returns: dimensionless endpoint function, canonical layout. """ medium = self._dispersion.medium.at_parameters(values) return self._log_ratio(replace(self._dispersion, medium=medium), 0.0) @@ -638,16 +558,8 @@ def value_at( def add(self, linear: LinearOperator) -> LinearOperator: """Join the term to a z table or an expansion of ``Phi``. - Parameters - ---------- - linear : solprop.translate.linop.LinearOperator - The cumulative dispersion operator. - - Returns - ------- - solprop.translate.linop.LinearOperator - The operator with the endpoint term; unchanged when no column propagates - or an expansion already carries it (batch reuse). + - Returns: the operator with the endpoint term; unchanged when no column + propagates or an expansion already carries it (batch reuse). """ if not self._mask.any(): return linear @@ -669,23 +581,9 @@ def _linear_roundoff_target( ) -> float: """Budget sqrt conditioning and frame subtraction before expansion sampling. - Parameters - ---------- - dispersion : solprop.spatial.SpatialDispersion - Material and temporal frame. - space : solprop.grid.RadialGrid - Transverse frequencies for the conditioning calculation. - sampled : callable - Return the shaped operator and prefactor for a model and position. - span : float - Extended propagation interval in m. - requested : float - Requested dimensionless expansion tolerance, positive. - - Returns - ------- - float - Relative expansion target including the derived arithmetic floor. + - ``sampled``: return the shaped operator and prefactor for a model and position. + - ``span``: extended propagation interval in m. + - ``requested``: requested dimensionless expansion tolerance, positive. """ # For s=sqrt(k^2-kperp^2), a relative perturbation in k^2 gives absolute # error |k^2|*epsilon/(2|s|). The following gamma(64) envelope covers both diff --git a/src/solprop/translate/raman.py b/src/solprop/translate/raman.py index 53ff7a30..92178c0a 100644 --- a/src/solprop/translate/raman.py +++ b/src/solprop/translate/raman.py @@ -245,24 +245,8 @@ def _damped( ) -> Float[np.ndarray, " n_to_doubled"]: """Apply each term's dephasing to its undamped kernel and sum. - Parameters - ---------- - response : RamanResponse - The response the kernels came from; supplies the dephasings and the fraction. - kernels : numpy.ndarray - The undamped, tapered rows, shape ``(n_terms, 2 n_to)``. - grid : TimeGrid - The grid supplying the fine time axis and the temporal collar. - density : float - Number density in m^-3, non-negative. - check : bool - Whether to check each damped term against the window it has to fit in, and - note in the debug log the ones that outlive it. - - Returns - ------- - numpy.ndarray - The damped kernel, shape ``(2 n_to,)``. + - ``grid``: the grid supplying the fine time axis and the temporal collar. + - ``density``: number density in m^-3, non-negative. """ n_to = grid.n_to delays = np.arange(n_to) * grid.dt_fine @@ -285,15 +269,7 @@ def _damped( def _flat_samples(grid: TimeGrid) -> int: """Return the number of kernel samples the taper leaves alone. - Parameters - ---------- - grid : TimeGrid - The grid supplying the fine time axis and the temporal collar. - - Returns - ------- - int - The index at which the taper starts to fall. + - ``grid``: the grid supplying the fine time axis and the temporal collar. """ span = (grid.n_to - 1) * grid.dt_fine return int( @@ -326,20 +302,7 @@ def _check_window( # noqa: PLR0917 - called positionally since the taper has already begun; that makes the test conservative, and the taper is one at the sample the tail starts from. - Parameters - ---------- - response : RamanResponse - The response, for the number of terms in the message. - model : RamanModel - The term itself, for its dephasing time. - tail : numpy.ndarray - The damped kernel over the tapered region, shape ``(n_tail,)``. - damped : numpy.ndarray - The damped kernel over the whole response span, shape ``(n_to,)``. - density : float - Number density in m^-3, non-negative. - row : int - Index of the term, for the message. + - ``density``: number density in m^-3, non-negative. """ peak = float(np.max(np.abs(damped))) if peak == 0.0 or tail.size == 0: @@ -683,17 +646,8 @@ def rate( ) -> Float[np.ndarray, " n_z"]: """Return the capped decay rate at each position. - Parameters - ---------- - z : numpy.ndarray - Positions in m, shape ``(n_z,)``. - dephasing : Dephasing - The term's dephasing, bound at definition time. - - Returns - ------- - numpy.ndarray - ``min(1/tau2(rho(z)), cap)`` in s^-1, shape ``(n_z,)``. + - ``z``: positions in m, shape ``(n_z,)``. + - Returns: ``min(1/tau2(rho(z)), cap)`` in s^-1, shape ``(n_z,)``. """ values = density(np.asarray(z, dtype=float)) return np.array( diff --git a/src/solprop/translate/responses.py b/src/solprop/translate/responses.py index f403ed3b..18b9ecf1 100644 --- a/src/solprop/translate/responses.py +++ b/src/solprop/translate/responses.py @@ -902,15 +902,8 @@ def _preionisation_profile(value: float | ZProfile) -> ZProfile: :class:`Constant`, so a constant path compiles to the number's table to the last bit whatever breakpoints it carries. - Parameters - ---------- - value : float or ZProfile - The initial ionised fraction, dimensionless, or a path of it along z in m. - - Returns - ------- - ZProfile - The path to tabulate. + - ``value``: the initial ionised fraction, dimensionless, or a path of it along z in + m. """ if isinstance(value, ZProfile) and value.is_constant: start = value.z_span[0] @@ -1332,10 +1325,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. """ title = "RegionalResponseTables: the compiled response of each material region" return title, [ @@ -1373,13 +1363,7 @@ def __post_init__(self) -> None: self._validate_tables() def _validate_tensors(self) -> None: - """Check the two tensors against the response and component counts. - - Raises - ------ - TranslationError - Naming the field that failed. - """ + """Check the two tensors against the response and component counts.""" # The contract's component ceiling lives beside the state's polarisation # ceiling, in translate.problem, which imports this module; the import is # deferred rather than the number copied, so there is one of it. @@ -1426,13 +1410,7 @@ def _validate_tensors(self) -> None: object.__setattr__(self, "delayed_tensor", delayed) def _validate_tables(self) -> None: - """Check the susceptibility, the kernels and the temporal weight. - - Raises - ------ - TranslationError - Naming the field that failed. - """ + """Check the susceptibility, the kernels and the temporal weight.""" responses = self.n_responses terms = int(self.delayed_tensor.shape[1]) chi3 = np.array(self.chi3, dtype=np.float64, order="C", copy=True) @@ -1745,13 +1723,7 @@ class _DelayedTerms: @property def n_terms(self) -> int: - """Return how many delayed channels each response carries. - - Returns - ------- - int - Zero, or the two channels of the silica tensor model. - """ + """Return how many delayed channels each response carries.""" return int(self.tensors.shape[1]) @@ -1764,21 +1736,12 @@ def _delayed_terms( **linear** convolution and the response's tail cannot wrap onto the head of the drive, exactly as :mod:`solprop.translate.raman` stores the scalar one. - Parameters - ---------- - declared : sequence - One :class:`solprop.nonlinear.raman.SilicaTensorResponse` or ``None`` per - response, in the record's own order. - n_to : int - Fine time samples of the run's grid, positive. - dt_fine : float - Fine sample spacing in s, positive. - - Returns - ------- - _DelayedTerms - The tensors, the kernels and the support counts, with an empty term axis - when no region declares a delayed response. + - ``declared``: one :class:`solprop.nonlinear.raman.SilicaTensorResponse` or + ``None`` per response, in the record's own order. + - ``n_to``: fine time samples of the run's grid, positive. + - ``dt_fine``: fine sample spacing in s, positive. + - Returns: the tensors, the kernels and the support counts, with an empty term axis + when no region declares a delayed response. """ responses = len(declared) present = [one for one in declared if one is not None] @@ -2064,11 +2027,6 @@ def _check_prefactor_representation(self) -> None: A kind that is neither is caught by these two checks together and named by their message, so it needs no membership test of its own -- which could not be reached anyway from a constructor whose annotation is a literal. - - Raises - ------ - TranslationError - If the wrong member is present for the kind named. """ if (self.separable is not None) != ( self.prefactor_representation == "separable" @@ -2140,17 +2098,7 @@ def prefactor_span(self) -> tuple[float, float]: def _prefactor_kind(constant: bool, expanded: bool) -> PhiRepresentation: """Return the representation kind the three prefactor members spell out. - Parameters - ---------- - constant : bool - Whether every mode reports ``z_constant``, so the array itself is carried. - expanded : bool - Whether an expansion in the scalar parameters was built. - - Returns - ------- - str - ``"constant"``, ``"expansion"`` or ``"table"``. + - Returns: ``"constant"``, ``"expansion"`` or ``"table"``. """ if constant: return "constant" @@ -2242,41 +2190,14 @@ def _prefactor_at( # noqa: PLR0917 - called positionally ``mode_average_prefactor(mode, grid, 0.0) * crop_scale``, which is what ``prefactor_constant`` and every backend's fast path expect. - Parameters - ---------- - modes : sequence of Mode - The mode set, in order. - grid : solprop.grid.TimeGrid - The grid. - n_pol : int - Polarisation components; the prefactor is the same for both components of one - mode and is stored for each. - mode_averaged : bool - Whether the effective-area conversion is folded in (the one-node case). - shock : bool - Keep the ``omega`` dependence of the numerator. - index : float or None - Replace the effective index by this constant; the GNLSE passes 1.0. - - Returns - ------- - callable - ``z -> p`` of shape ``(n_modes, n_pol, n_omega)``. + - Returns: ``z -> p`` of shape ``(n_modes, n_pol, n_omega)``. """ areas = [sqrtw_to_vm(one.aeff(0.0)) if mode_averaged else 1.0 for one in modes] def prefactor_at(z: float) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: """Return the prefactor at one position, in the canonical layout. - Parameters - ---------- - z : float - Position along the waveguide, m. - - Returns - ------- - numpy.ndarray - The prefactor, shape ``(n_modes, n_pol, n_omega)``. + - ``z``: position along the waveguide, m. """ columns = [ uppe_prefactor(one, grid, z, shock=shock, index=index) @@ -2311,35 +2232,9 @@ def _prefactor_expansion( # noqa: PLR0917 - called positionally representation, with plain products of the basis polynomials as its weights rather than their integrals: the prefactor is a value at ``z``, not a cumulative one. - Parameters - ---------- - modes : sequence of Mode - The mode set; its declared paths are the dimensions. - grid : solprop.grid.TimeGrid - The grid. - n_pol : int - Polarisation components; the prefactor is stored for each. - mode_averaged : bool - Whether the effective-area conversion is folded in, at the reference position. - shock : bool - Keep the ``omega`` dependence of the numerator. - index : float or None - Replace the effective index by this constant; the GNLSE passes 1.0. - span : float - End of the tabulated range, m. - edges : array_like - Positions that must be interval edges of the scalar tables. - tol : float - Target bound on the uniform error relative to the value scale. - n_max : int - Highest Chebyshev degree tried on a parameter. - max_intervals : int - Interval budget for the scalar tables. - - Returns - ------- - solprop.translate.expansion.ParameterExpansion or None - The expansion, or ``None`` when the mode set does not allow one. + - ``span``: end of the tabulated range, m. + - ``edges``: positions that must be interval edges of the scalar tables. + - Returns: the expansion, or ``None`` when the mode set does not allow one. """ varying = parameter_paths(modes, 0.0, span) if varying is None: @@ -2350,18 +2245,7 @@ def _prefactor_expansion( # noqa: PLR0917 - called positionally def prefactor_of( values: Mapping[str, float], ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: - """Return the prefactor at one point of the parameter space. - - Parameters - ---------- - values : collections.abc.Mapping - One value per expanded path, by name. - - Returns - ------- - numpy.ndarray - Shape ``(n_modes, n_pol, n_omega)``. - """ + """Return the prefactor at one point of the parameter space.""" columns = [ uppe_prefactor( one.at_parameters(values), grid, 0.0, shock=shock, index=index @@ -2410,28 +2294,13 @@ def _prefactor_table( round-off of 8e-16 against the operator's 1e-11, and the build converges at the tolerance it is asked for. - Parameters - ---------- - prefactor_at : callable - ``z -> p`` in the canonical layout, from :func:`_prefactor_at`. - modes : sequence of Mode - The mode set; its ``core_radius`` and ``density`` paths carry the structure. - span : float - End of the tabulated range, m: the same ``z_max (1 + margin)`` as ``Phi``. - edges : array_like - Positions that must be interval edges. :func:`compile_responses` is given the - cumulative operator's hard kinks, so the two tables split on the same edges. - tol : float - Target bound on the uniform error relative to the value scale. - n_max : int - Highest Chebyshev degree tried on an interval before it is bisected. - max_intervals : int - Interval budget; exceeding it warns and leaves the table unresolved. - - Returns - ------- - solprop.translate.chebyshev.PiecewiseChebyshev - The prefactor over ``[0, span]``, with read-only arrays. + - ``prefactor_at``: ``z -> p`` in the canonical layout, from :func:`_prefactor_at`. + - ``span``: end of the tabulated range, m: the same ``z_max (1 + margin)`` as + ``Phi``. + - ``edges``: positions that must be interval edges. :func:`compile_responses` is + given the cumulative operator's hard kinks, so the two tables split on the same + edges. + - Returns: the prefactor over ``[0, span]``, with read-only arrays. """ def sample( @@ -2439,18 +2308,7 @@ def sample( ) -> Complex[np.ndarray, "n_z n_modes n_pol n_omega"]: """Return the prefactor at every position of a Chebyshev node set. - Parameters - ---------- - z : numpy.ndarray - Positions along the waveguide, m, shape ``(n_z,)``. - *_ : object - The path values the builder resolved; the prefactor is evaluated from ``z`` - directly, so they are not used. - - Returns - ------- - numpy.ndarray - Shape ``(n_z, n_modes, n_pol, n_omega)``. + - ``z``: positions along the waveguide, m, shape ``(n_z,)``. """ return np.stack([prefactor_at(float(one)) for one in z]) @@ -2489,12 +2347,7 @@ def _log_prefactor(compiled: "CompiledResponses", z_max: float) -> None: report is free: it is the largest departure from the entrance value over the run, which is exactly what freezing the prefactor at ``z = 0`` used to discard. - Parameters - ---------- - compiled : CompiledResponses - Section 5, already assembled. - z_max : float - Length the run covers, m. + - ``z_max``: length the run covers, m. """ if compiled.separable is not None: logger.debug( @@ -2547,16 +2400,9 @@ def _translated_prefactor( ) -> _PrefactorRepresentation | None: """Classify a caller's own normalisation without rebuilding it as a modal one. - Parameters - ---------- - supplied : object or None - A translated table, parameter expansion or separable coefficient record. - None requests the ordinary modal normalisation. - - Returns - ------- - _PrefactorRepresentation or None - The supplied representation, or None for a modal problem. + - ``supplied``: a translated table, parameter expansion or separable coefficient + record. None requests the ordinary modal normalisation. + - Returns: the supplied representation, or None for a modal problem. """ if supplied is None: return None @@ -2600,19 +2446,8 @@ def _material_profiles( ) -> _MaterialProfiles: """Resolve component profiles while preserving the original single path. - Parameters - ---------- - modes : Sequence of Mode or None - Mode set carrying the material declaration. - mixture : Mixture or None - Ordered component populations; None retains the legacy material path. - profile : ZProfile - Total density path in m^-3, or the legacy nonlinear scale. - - Returns - ------- - _MaterialProfiles - Species metadata with each component density and the total path. + - ``mixture``: ordered component populations; None retains the legacy material path. + - ``profile``: total density path in m^-3, or the legacy nonlinear scale. """ if mixture is None and modes: declared = getattr(modes[0], "mixture", None) @@ -2638,20 +2473,7 @@ def _response_label( | RamanResponse, material: _MaterialProfiles, ) -> str: - """Require an unambiguous population for every declared response. - - Parameters - ---------- - response : object - Declared response whose optional species field binds a component label. - material : _MaterialProfiles - Ordered labels, species names and density paths. - - Returns - ------- - str - Validated label in the material declaration. - """ + """Require an unambiguous population for every declared response.""" label = response.species if label is None: if len(material.labels) == 1: @@ -2689,21 +2511,8 @@ def _compile_kerr_components( ) -> KerrCoefficients | None: """Fold species amplitudes once, retaining the one-component multiplication. - Parameters - ---------- - responses : tuple - Declared responses of one kind in user order. - material : _MaterialProfiles - Ordered labels, species names and density paths. - densities : Mapping of str to PiecewiseChebyshev - Density tables in m^-3, keyed by component label. - build : _ResponseBuild - Grid, span in m, interpolation tolerance and ionisation field range. - - Returns - ------- - KerrCoefficients or None - One cubic form with its own composite density table. + - ``densities``: density tables in m^-3, keyed by component label. + - ``build``: grid, span in m, interpolation tolerance and ionisation field range. """ if not responses: return None @@ -2745,28 +2554,11 @@ def _compile_dispersive_components( general modal guide's gas regions arrive compiled, as `regional`, and are taken as they are. - Parameters - ---------- - responses : tuple of DispersiveKerrResponse - Declared responses in user order. - material : _MaterialProfiles - Ordered labels, species names and density paths. - densities : Mapping of str to PiecewiseChebyshev - Density tables in m^-3, keyed by component label. - build : _ResponseBuild - Grid, span in m, interpolation tolerance and ionisation field range. - regional : DispersiveKerrTerms or None, optional - A general modal guide's compiled gas channels, or ``None``. - - Returns - ------- - DispersiveKerrTerms or None - The compiled leaf, channels in species order, or ``None`` with none declared. - - Raises - ------ - TranslationError - If a dispersive response is declared beside a guide's own channels. + - ``densities``: density tables in m^-3, keyed by component label. + - ``build``: grid, span in m, interpolation tolerance and ionisation field range. + - ``regional``: a general modal guide's compiled gas channels, or ``None``. + - Returns: the compiled leaf, channels in species order, or ``None`` with none + declared. """ if regional is not None: if responses: @@ -2838,21 +2630,8 @@ def _compile_plasma_channels( ) -> tuple[PlasmaChannel, ...]: """Bind each exposure scan to its own density and response constants. - Parameters - ---------- - responses : tuple - Declared responses of one kind in user order. - material : _MaterialProfiles - Ordered labels, species names and density paths. - densities : Mapping of str to PiecewiseChebyshev - Density tables in m^-3, keyed by component label. - build : _ResponseBuild - Grid, span in m, interpolation tolerance and ionisation field range. - - Returns - ------- - tuple of PlasmaChannel - Independent channels in mixture order. + - ``densities``: density tables in m^-3, keyed by component label. + - ``build``: grid, span in m, interpolation tolerance and ionisation field range. """ channels = [] for response in responses: @@ -2885,23 +2664,6 @@ def _plasma_integral( The channels share the two current integrals, so they cannot honour different rules: every plasma response of one problem must ask for the same one. - - Parameters - ---------- - responses : tuple of PlasmaResponse - The field plasma responses, in user order. - grid : solprop.grid.TimeGrid - The field grid the band rule is designed for. - - Returns - ------- - RunningIntegral - The designed rule, or the trapezoid without plasma. - - Raises - ------ - TranslationError - If the responses ask for different rules, or no band design serves the grid. """ if not responses: return RunningIntegral.trapezoid() @@ -2928,23 +2690,8 @@ def _compile_envelope_channels( ) -> EnvelopePlasmaAssembly | None: """Compile labelled envelope populations and their separate spectral assembly. - Parameters - ---------- - responses : tuple of EnvelopePlasmaResponse - Declared envelope responses. - material : _MaterialProfiles - Component identities and paths. - densities : Mapping - Compiled component number densities in m^-3. - build : _ResponseBuild - Grid, z span, profile tolerance and intended amplitude domain. - shock : bool - Frequency-dependent or frozen-carrier policy. - - Returns - ------- - EnvelopePlasmaAssembly or None - Numeric channels in material order, or None when not declared. + - ``densities``: compiled component number densities in m^-3. + - Returns: numeric channels in material order, or None when not declared. """ if not responses: return None @@ -2999,29 +2746,9 @@ def _raman_density( labels that form it both drives and damps it: still one decay-rate path per term and one amplitude table per leaf, as the kernel contract has them. - Parameters - ---------- - response : RamanResponse - The declared response. - label : str - The leaf's label. - material : _MaterialProfiles - Ordered labels and their density paths. - densities : Mapping of str to PiecewiseChebyshev - Density tables in m^-3, keyed by label. - build : _ResponseBuild - Span and tolerance of the tables. - - Returns - ------- - tuple - The density path in m^-3 the decay rates are composed with, and the - amplitude density table in m^-3. - - Raises - ------ - TranslationError - If a composition names a label the material does not have. + - ``densities``: density tables in m^-3, keyed by label. + - Returns: the density path in m^-3 the decay rates are composed with, and the + amplitude density table in m^-3. """ labels = response.labels if isinstance(response, OptmatRamanResponse) else () if len(labels) < 2: @@ -3048,25 +2775,9 @@ def _compile_raman_components( # noqa: PLR0917 - called positionally ) -> tuple[RamanCoefficients, ...]: """Compile component amplitudes and linewidths under the total-density rule. - Parameters - ---------- - responses : tuple - Declared responses of one kind in user order. - kerr : KerrResponse or DispersiveKerrResponse or None - Electronic response specifying the field-grid harmonic form. - material : _MaterialProfiles - Ordered labels, species names and density paths. - densities : Mapping of str to PiecewiseChebyshev - Density tables in m^-3, keyed by component label. - build : _ResponseBuild - Grid, span in m, interpolation tolerance and ionisation field range. - n_pol : int - Number of effective polarisation components, one or two. - - Returns - ------- - tuple of RamanCoefficients - Component kernels, amplitude densities and total-density decay tables. + - ``densities``: density tables in m^-3, keyed by component label. + - ``build``: grid, span in m, interpolation tolerance and ionisation field range. + - Returns: component kernels, amplitude densities and total-density decay tables. """ compiled = [] for response in responses: @@ -3446,13 +3157,7 @@ def _check_population_declarations( envelope: tuple[EnvelopePlasmaResponse, ...], kerr: tuple[KerrResponse, ...], ) -> None: - """Reject contradictory source declarations before expensive compilation. - - Parameters - ---------- - envelope, kerr : tuple - Declared responses of each kind. - """ + """Reject contradictory source declarations before expensive compilation.""" if envelope and any(response.thg for response in kerr): raise TranslationError("envelope plasma cannot be combined with envelope THG") @@ -3464,24 +3169,8 @@ def _population_tables( ) -> tuple[RateTable, ...]: """Retain the exact rate table owned by each active population channel. - Parameters - ---------- - plasma : tuple of PlasmaChannel - Compiled field populations. - envelope : EnvelopePlasmaAssembly or None - Compiled envelope populations and source assembly. - field_range : tuple of float or None - Intended table amplitude domain in V/m. - - Returns - ------- - tuple of RateTable - Tables in channel order; empty without a plasma population. - - Raises - ------ - TranslationError - If a field range is given without a plasma population to use it. + - ``field_range``: intended table amplitude domain in V/m. + - Returns: tables in channel order; empty without a plasma population. """ if envelope is not None: return tuple(channel.rate for channel in envelope.channels) @@ -3511,24 +3200,9 @@ def _declared( that declares it is undefined on this grid is refused with that reason -- the plasma model is written for a resolved carrier and has no envelope form. - Parameters - ---------- - responses : sequence - The declared responses. - grid : solprop.grid.TimeGrid - The grid, whose ``kind`` every response must support. - - Returns - ------- - tuple - The Kerr, plasma, Raman, envelope-plasma and dispersive Kerr responses, each - empty when not declared. - - Raises - ------ - TranslationError - Naming a response that cannot be compiled, one declared twice, two electronic - responses on one population, or one that is not defined on this grid. + - ``grid``: the grid, whose ``kind`` every response must support. + - Returns: the Kerr, plasma, Raman, envelope-plasma and dispersive Kerr responses, + each empty when not declared. """ found: dict[tuple[type, str | None], object] = {} for response in responses: @@ -3614,17 +3288,8 @@ def _declared( def _tabulate_rate(rate: IonisationRate, field_range: tuple[float, float]) -> RateTable: """Build a frozen spline table of an ionisation rate over a field range. - Parameters - ---------- - rate : solprop.nonlinear.ionisation.IonisationRate - The rate to tabulate. - field_range : tuple of float - Lower and upper field amplitude in V/m the table must cover. - - Returns - ------- - solprop.translate.ionisation.RateTable - The converged table, with read-only arrays. + - ``field_range``: lower and upper field amplitude in V/m the table must cover. + - Returns: the converged table, with read-only arrays. """ return _frozen_table(build_rate_table(rate, field_range)) @@ -3635,16 +3300,6 @@ def _frozen_table(table: RateTable) -> RateTable: The builder memoises its tables and hands out the originals, so a table that crosses the seam is a frozen copy: the contract promises read-only arrays and ``validate`` checks it. - - Parameters - ---------- - table : solprop.translate.ionisation.RateTable - The table to freeze. - - Returns - ------- - solprop.translate.ionisation.RateTable - The same values, not writeable. """ if ( not table.log_rate.flags.writeable @@ -3663,15 +3318,7 @@ def _frozen_table(table: RateTable) -> RateTable: def _readonly(values: np.ndarray) -> np.ndarray: """Return a C-contiguous, read-only copy of an array. - Parameters - ---------- - values : numpy.ndarray - The array to freeze. - - Returns - ------- - numpy.ndarray - The same values, C-contiguous and not writeable. + - Returns: the same values, C-contiguous and not writeable. """ frozen = np.ascontiguousarray(values) frozen.setflags(write=False) @@ -3688,24 +3335,7 @@ def _response_order( ) -> tuple[ResponseStage, ...]: """Return the fused-pass sequence the declared content implies. - Parameters - ---------- - has_kerr : bool - Whether a Kerr response was declared. - has_ionisation : bool - Whether an ionisation table was built. - has_plasma : bool - Whether a plasma response was compiled. - has_raman : bool - Whether a Raman response was compiled. - has_dispersive : bool, optional - Whether a dispersive Kerr response was compiled; its stage follows the - constant one's. - - Returns - ------- - tuple of ResponseStage - The stages in execution order, always ending in ``ACCUMULATE``. + - Returns: the stages in execution order, always ending in ``ACCUMULATE``. """ stages: list[ResponseStage] = [] if has_ionisation: diff --git a/src/solprop/translate/spatial.py b/src/solprop/translate/spatial.py index e6db5f26..6f29fffa 100644 --- a/src/solprop/translate/spatial.py +++ b/src/solprop/translate/spatial.py @@ -454,12 +454,9 @@ def __post_init__(self) -> None: self._adopt_user_rate() def _adopt_user_rate(self) -> None: - """Own the user's share of the spectral rate, zeros when absent, and check it. + """Own the user's share of the spectral rate, zeros when absent. - Raises - ------ - TranslationError - If it is not a finite share of the spectral rate on every bin. + Refuse one of another shape or outside ``[0, spectral_rate]`` on any bin. """ rate = ( np.zeros(self.spectral_rate.size) @@ -480,13 +477,7 @@ def _adopt_user_rate(self) -> None: object.__setattr__(self, "user_rate", rate) def _adopt_kperp_rate(self) -> None: - """Own the transverse rate, zeros when absent, and validate it. - - Raises - ------ - TranslationError - If it is not one finite nonnegative rate per column. - """ + """Own the transverse rate, zeros when absent, and validate it.""" rate = ( np.zeros(self.kperp_sq.size) if self.kperp_rate is None @@ -645,17 +636,7 @@ def rate_bounds(coefficients: SeparableOperator) -> RateBounds: def _frozen_array(values: np.ndarray, dtype: type) -> np.ndarray: """Own a finite, contiguous array without freezing a caller's writable buffer. - Parameters - ---------- - values : numpy.ndarray - Numerical coefficients or geometry values to copy. - dtype : type - Required seam dtype: float64, complex128 or bool. - - Returns - ------- - numpy.ndarray - Finite, contiguous, read-only copy with the input shape. + - Returns: finite, contiguous, read-only copy with the input shape. """ result = np.array(values, dtype=dtype, order="C", copy=True) if not np.all(np.isfinite(result)): @@ -719,17 +700,8 @@ def _kperp_rate( ) -> Float[np.ndarray, " n_columns"]: """Return the transverse rate of the selected columns. - Parameters - ---------- - coefficients : SeparableOperator - The operator. - columns : slice or None - Transverse columns; None selects all. - - Returns - ------- - numpy.ndarray - The power rate in m^-1, one per selected column. + - ``columns``: transverse columns; None selects all. + - Returns: the power rate in m^-1, one per selected column. """ rate = coefficients.kperp_rate assert rate is not None # adopted as zeros in __post_init__ @@ -952,21 +924,8 @@ def _far_field_columns( ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: """Return the Cartesian columns a far-field statistic reads, or refuse the geometry. - Parameters - ---------- - geometry : SpatialGeometry, solprop.grid.FreeGrid, solprop.grid.RadialGrid or None - The run's geometry record, or the grid of a saved plane. - - Returns - ------- - tuple of numpy.ndarray - The stored axes ``k_x`` and ``k_y`` in rad/m, the spectral weights and the - on-axis weights. - - Raises - ------ - OutputError - For a radial or planar-hybrid geometry, or none at all. + - Returns: the stored axes ``k_x`` and ``k_y`` in rad/m, the spectral weights and + the on-axis weights. """ if isinstance(geometry, FreeGrid): return geometry.k_x, geometry.k_y, geometry.spectral_weights, geometry.on_axis @@ -1089,19 +1048,7 @@ def aperture_tables( def _refuse_off_line(apertures: ApertureSet) -> None: - """Refuse the outlines a line uniform in y gives no meaning. - - Parameters - ---------- - apertures : solprop.stats.ApertureSet - The set. - - Raises - ------ - OutputError - For a half-plane bounded in y, which holds every column or none, or an - ellipse, which is an edge in y as well as in x. - """ + """Refuse the outlines a line uniform in y gives no meaning.""" for row, shape in enumerate(apertures.row_shapes): if shape == "ellipse" or shape.startswith("half_plane_y"): msg = ( @@ -1114,20 +1061,7 @@ def _refuse_off_line(apertures: ApertureSet) -> None: def _outlines(apertures: ApertureSet, *, line: bool) -> list[ApertureOutline]: - """Return each row's outline on a plane or a line. - - Parameters - ---------- - apertures : solprop.stats.ApertureSet - The set. - line : bool - Whether the columns are a line's. - - Returns - ------- - list of solprop.spatial.ApertureOutline - One per aperture. - """ + """Return each row's outline on a plane or a line.""" count = apertures.n_apertures diameters = np.asarray(apertures.diameters) edges = np.zeros(count) if apertures.edges is None else np.asarray(apertures.edges) diff --git a/src/solprop/translate/stage.py b/src/solprop/translate/stage.py index bd286bed..b0e4c5c3 100644 --- a/src/solprop/translate/stage.py +++ b/src/solprop/translate/stage.py @@ -410,18 +410,7 @@ def terms(problem: PropagationProblem) -> StageTerms: def _phi_terms( linear: LinearOperator, ) -> Complex[np.ndarray, "n_phi_terms n_batch n_modes n_pol n_omega"] | None: - """Return the terms of ``Phi``, or ``None`` for the z table. - - Parameters - ---------- - linear : solprop.translate.linop.LinearOperator - Section 3 of the contract. - - Returns - ------- - numpy.ndarray or None - Shape ``(n_phi_terms, 1, n_modes, n_pol, n_omega)``. - """ + """Return the terms of ``Phi``, or ``None`` for the z table.""" if linear.phi_constant is not None: return linear.phi_constant[np.newaxis] if linear.phi_expansion is not None: @@ -432,18 +421,7 @@ def _phi_terms( def _prefactor_terms( responses: CompiledResponses, ) -> Complex[np.ndarray, "n_pre_terms n_modes n_pol n_omega"] | None: - """Return the terms of the prefactor, or ``None`` for the z table. - - Parameters - ---------- - responses : solprop.translate.responses.CompiledResponses - Section 5 of the contract. - - Returns - ------- - numpy.ndarray or None - Shape ``(n_pre_terms, n_modes, n_pol, n_omega)``. - """ + """Return the terms of the prefactor, or ``None`` for the z table.""" if responses.prefactor_constant is not None: return responses.prefactor_constant[np.newaxis] if responses.prefactor_expansion is not None: @@ -502,22 +480,11 @@ def scalars( kerr = responses.kerr_coefficients flow = linear.coupled_flow - def read(table: PiecewiseChebyshev) -> float: - """Return a scalar table's value at the stage, by the backend's pass. - - Parameters - ---------- - table : solprop.translate.chebyshev.PiecewiseChebyshev - A scalar table. - - Returns - ------- - float - Its value at `z1`. - """ + def _read(table: PiecewiseChebyshev) -> float: + """Return a scalar table's value at the stage, by the backend's pass.""" return float(table(z1, helpers=helpers)) - density = read(responses.density) + density = _read(responses.density) return StageScalars( z0=z0, z1=z1, @@ -525,23 +492,23 @@ def read(table: PiecewiseChebyshev) -> float: phi_at=phi_at, prefactor=_prefactor_weights(responses, z1, helpers), density=density, - taper_scale=1.0 if taper is None else read(taper), + taper_scale=1.0 if taper is None else _read(taper), raman_rates=tuple( - read(table) for term in responses.raman_terms for table in term.decay_rates + _read(table) for term in responses.raman_terms for table in term.decay_rates ), kerr_density=0.0 if kerr is None else density if kerr.density is None - else read(kerr.density), + else _read(kerr.density), plasma_densities=tuple( - read(channel.density) for channel in responses.population_channels + _read(channel.density) for channel in responses.population_channels ), plasma_preionisation=tuple( - read(channel.preionisation) for channel in responses.population_channels + _read(channel.preionisation) for channel in responses.population_channels ), raman_densities=tuple( - density if term.density is None else read(term.density) + density if term.density is None else _read(term.density) for term in responses.raman_terms ), flow_section=-1 if flow is None else flow_section(flow, z0), @@ -551,7 +518,7 @@ def read(table: PiecewiseChebyshev) -> float: transform_weights=transform_weights(problem.transform.modal_tables, z1), dispersive_kerr_densities=() if responses.dispersive_kerr is None - else tuple(read(table) for table in responses.dispersive_kerr.densities), + else tuple(_read(table) for table in responses.dispersive_kerr.densities), ) @@ -641,19 +608,8 @@ def scalars_at( zs = np.array(where, dtype=np.float64) read: dict[int, list[float]] = {} - def column(table: PiecewiseChebyshev) -> list[float]: - """Return a scalar table's values at every position, reading it once. - - Parameters - ---------- - table : solprop.translate.chebyshev.PiecewiseChebyshev - A scalar table. - - Returns - ------- - list of float - Its values at the step's positions, in order. - """ + def _column(table: PiecewiseChebyshev) -> list[float]: + """Return a scalar table's values at every position, reading it once.""" held = read.get(id(table)) if held is None: held = read[id(table)] = table.at_positions(zs, helpers=helpers).tolist() @@ -661,11 +617,11 @@ def column(table: PiecewiseChebyshev) -> list[float]: phi = _phi_rows(linear, z0, where, zs, at, helpers) prefactor = _prefactor_rows(responses, where, zs, helpers) - density = column(responses.density) + density = _column(responses.density) taper = problem.transform.taper_scale - tapers = [1.0] * count if taper is None else column(taper) + tapers = [1.0] * count if taper is None else _column(taper) rates = [ - column(table) for term in responses.raman_terms for table in term.decay_rates + _column(table) for term in responses.raman_terms for table in term.decay_rates ] kerr = responses.kerr_coefficients kerrs = ( @@ -673,19 +629,19 @@ def column(table: PiecewiseChebyshev) -> list[float]: if kerr is None else density if kerr.density is None - else column(kerr.density) + else _column(kerr.density) ) channels = responses.population_channels - plasma = [column(channel.density) for channel in channels] - preionised = [column(channel.preionisation) for channel in channels] + plasma = [_column(channel.density) for channel in channels] + preionised = [_column(channel.preionisation) for channel in channels] raman = [ - density if term.density is None else column(term.density) + density if term.density is None else _column(term.density) for term in responses.raman_terms ] dispersive = ( [] if responses.dispersive_kerr is None - else [column(table) for table in responses.dispersive_kerr.densities] + else [_column(table) for table in responses.dispersive_kerr.densities] ) flow = linear.coupled_flow section = -1 if flow is None else flow_section(flow, z0) @@ -727,25 +683,10 @@ def _phi_rows( # noqa: PLR0917 - called positionally ]: """Return :func:`_phi_weights` at each of several positions, reading ``Phi`` once. - Parameters - ---------- - linear : solprop.translate.linop.LinearOperator - Section 3 of the contract. - z0 : float - The step's anchor, m. - where : list of float - The positions, m. - zs : numpy.ndarray - The same positions as float64, shape ``(n_points,)``. - at : StepAnchor or None - The anchor's weights, hoisted, or ``None`` to evaluate them here. - helpers : solprop._helpers.Helpers - The implementation of the Clenshaw pass. - - Returns - ------- - list of tuple - Per position, ``(weights of Phi(z1) - Phi(z0), weights of Phi(z1))``. + - ``z0``: the step's anchor, m. + - ``where``: the positions, m. + - ``at``: the anchor's weights, hoisted, or ``None`` to evaluate them here. + - Returns: per position, ``(weights of Phi(z1) - Phi(z0), weights of Phi(z1))``. """ if linear.phi_constant is not None or linear.separable is not None: # The increment is the distance itself, formed per position exactly as @@ -772,21 +713,8 @@ def _prefactor_rows( ) -> list[Float[np.ndarray, " n_pre_terms"] | None]: """Return :func:`_prefactor_weights` at each of several positions. - Parameters - ---------- - responses : solprop.translate.responses.CompiledResponses - Section 5 of the contract. - where : list of float - The positions, m. - zs : numpy.ndarray - The same positions as float64, shape ``(n_points,)``. - helpers : solprop._helpers.Helpers - The implementation of the Clenshaw pass. - - Returns - ------- - list - Per position, the weights, or ``None`` for the z table. + - ``where``: the positions, m. + - Returns: per position, the weights, or ``None`` for the z table. """ if responses.prefactor_constant is not None or responses.separable is not None: return [_prefactor_weights(responses, z1, helpers) for z1 in where] @@ -812,22 +740,10 @@ def _phi_weights( tabulated range, so the value at `z1` -- which is what the noise's unitary phase needs -- is the weights at `z1` against the same terms. - Parameters - ---------- - linear : solprop.translate.linop.LinearOperator - Section 3 of the contract. - z0, z1 : float - The step's anchor and the stage's position, m. - at : StepAnchor or None - The anchor's weights, hoisted, or ``None`` to evaluate them here. - helpers : solprop._helpers.Helpers - The implementation of the Clenshaw pass. - - Returns - ------- - tuple - ``(weights of Phi(z1) - Phi(z0), weights of Phi(z1))``, each shape - ``(n_phi_terms,)``, or ``(None, None)`` for the z table. + - ``z0``, ``z1``: the step's anchor and the stage's position, m. + - ``at``: the anchor's weights, hoisted, or ``None`` to evaluate them here. + - Returns: ``(weights of Phi(z1) - Phi(z0), weights of Phi(z1))``, each shape + ``(n_phi_terms,)``, or ``(None, None)`` for the z table. """ if linear.phi_constant is not None or linear.separable is not None: # Phi(z) = D z for a constant operator, so the increment's weight is the @@ -850,20 +766,7 @@ def _prefactor_weights( ) -> Float[np.ndarray, " n_pre_terms"] | None: """Return the prefactor's weights at a position, or ``None`` for the z table. - Parameters - ---------- - responses : solprop.translate.responses.CompiledResponses - Section 5 of the contract. - z : float - Position, m. - helpers : solprop._helpers.Helpers - The implementation of the Clenshaw pass. - - Returns - ------- - numpy.ndarray or None - Shape ``(n_pre_terms,)``. Exactly ``[1.0]`` for a constant, so that combining - returns the stored array's own values. + - ``z``: position, m. """ if responses.prefactor_constant is not None or responses.separable is not None: low, high = responses.prefactor_span diff --git a/src/solprop/translate/tdse.py b/src/solprop/translate/tdse.py index 20c9b7d1..da368b49 100644 --- a/src/solprop/translate/tdse.py +++ b/src/solprop/translate/tdse.py @@ -289,18 +289,7 @@ def resolve_atomic_window( def _basis_from_json(values: Mapping[str, object]) -> TDSEBasis | HaccBasis: - """Rebuild either basis type from its primitive record. - - Parameters - ---------- - values : mapping - A basis record; a haCC record carries ``kind="hacc_helium"``. - - Returns - ------- - TDSEBasis or HaccBasis - The revalidated record. - """ + """Rebuild either basis type from its primitive record.""" basis = dict(values) if basis.pop("kind", None) == "hacc_helium": for name in ("radial_edges", "radial_orders"): @@ -312,19 +301,7 @@ def _basis_from_json(values: Mapping[str, object]) -> TDSEBasis | HaccBasis: def _response_from_json(encoded: str) -> TDSEGasResponse: - """Revalidate persisted primitive science at the single dynamic JSON boundary. - - Parameters - ---------- - encoded : str - Descriptor JSON containing a primitive response record in SI units. - - Returns - ------- - TDSEGasResponse - Reconstructed frozen specification with all scientific validation applied. - The consistency policy is never persisted, so it takes its default. - """ + """Revalidate persisted primitive science at the single dynamic JSON boundary.""" record = json.loads(encoded)["response"] calibration = dict(record["calibration"]) for name in ("wavelength_range", "limitations"): @@ -356,15 +333,7 @@ def _response_from_json(encoded: str) -> TDSEGasResponse: def _response_record(response: TDSEGasResponse) -> dict[str, object]: """Return a response's primitive descriptor record. - Parameters - ---------- - response : TDSEGasResponse - Validated scientific specification. - - Returns - ------- - dict - The response record; the consistency policy never enters it. + - Returns: the response record; the consistency policy never enters it. """ record = asdict(response) del record["consistency"] @@ -374,13 +343,7 @@ def _response_record(response: TDSEGasResponse) -> dict[str, object]: def _validate_schema(encoded: str) -> None: - """Require the one descriptor schema, its complete response record and lattice. - - Parameters - ---------- - encoded : str - Descriptor JSON with SI temporal-collar breakpoints. - """ + """Require the one descriptor schema, its complete response record and lattice.""" configuration = json.loads(encoded) response = configuration["response"] if configuration.get("schema") != DESCRIPTOR_SCHEMA: @@ -397,15 +360,7 @@ def _validate_schema(encoded: str) -> None: def _validated_collar(configuration: Mapping[str, Any]) -> Collar: """Return a descriptor's recorded temporal collar after checking its order. - Parameters - ---------- - configuration : mapping - Parsed descriptor JSON. - - Returns - ------- - Collar - The fine-lattice collar in s. + - Returns: the fine-lattice collar in s. """ collar = Collar(**configuration["lattice"]["time_collar"]) if not collar.zero_low < collar.one_low <= collar.one_high < collar.zero_high: @@ -416,18 +371,9 @@ def _validated_collar(configuration: Mapping[str, Any]) -> Collar: def _setup(samples: int, spacing: float) -> dict[str, object]: """Return the admitted SAE provider ground-state setup for one atomic lattice. - Parameters - ---------- - samples : int - Atomic samples: the full lattice, or the atomic window's. - spacing : float - Response spacing in s. - - Returns - ------- - dict - Setup policy. The growing-mode window is the atomic run duration in s, - so the provider checks mode growth over exactly the propagated time. + - ``spacing``: response spacing in s. + - Returns: setup policy. The growing-mode window is the atomic run duration in s, so + the provider checks mode growth over exactly the propagated time. """ return { "initial_l": 0, @@ -442,12 +388,9 @@ def _setup(samples: int, spacing: float) -> dict[str, object]: def _hacc_setup() -> dict[str, object]: """Return the pinned haCC provider setup record (SEP 20, 6.2). - Returns - ------- - dict - Every row restarts from the field-free ground root at each evaluation; a - growing root below the cut is refused at preparation; the gross-failure - norm-growth limit is pinned. + - Returns: every row restarts from the field-free ground root at each evaluation; a + growing root below the cut is refused at preparation; the gross-failure + norm-growth limit is pinned. """ return { "initial": "ground_root", @@ -461,15 +404,7 @@ def _hacc_setup() -> dict[str, object]: def _window_from_json(encoded: str) -> AtomicWindowRecord | None: """Parse a descriptor's resolved atomic window once per configuration. - Parameters - ---------- - encoded : str - Validated descriptor JSON. - - Returns - ------- - AtomicWindowRecord or None - The resolved window, or None for the full lattice. + - Returns: the resolved window, or None for the full lattice. """ record = json.loads(encoded)["lattice"].get("atomic_window") if record is None: @@ -817,19 +752,8 @@ def _resolved_window( ) -> AtomicWindowRecord | None: """Resolve a response's declared window on its lattice, if it has one. - Parameters - ---------- - response : TDSEGasResponse - Validated specification. - samples : int - Fine lattice samples. - spacing : float - Fine spacing in s. - - Returns - ------- - AtomicWindowRecord or None - The resolved window, or None for the full lattice. + - ``spacing``: fine spacing in s. + - Returns: the resolved window, or None for the full lattice. """ window = response.atomic_window if window is None or isinstance(window, str): @@ -866,18 +790,8 @@ class _SetupScalars: def _setup_fields(model: TDSEModel, setup: Mapping[str, object]) -> _SetupScalars: """Map a setup record onto the descriptor's scalar provider-setup fields. - Parameters - ---------- - model : {'sae', 'hacc'} - Atomic model family. - setup : mapping - The configuration's setup record. - - Returns - ------- - _SetupScalars - The SAE record itself, or the haCC growing policy with the SAE-only fields - at their inert defaults (no growing-mode window). + - Returns: the SAE record itself, or the haCC growing policy with the SAE-only + fields at their inert defaults (no growing-mode window). """ if model == "sae": return _SetupScalars(**cast("dict[str, Any]", setup)) @@ -886,33 +800,14 @@ def _setup_fields(model: TDSEModel, setup: Mapping[str, object]) -> _SetupScalar @functools.cache def _cached_response(encoded: str) -> TDSEGasResponse: - """Rebuild a descriptor's response once per configuration. - - Parameters - ---------- - encoded : str - Validated descriptor JSON. - - Returns - ------- - TDSEGasResponse - The immutable reconstructed response. - """ + """Rebuild a descriptor's response once per configuration.""" return _response_from_json(encoded) def _breakpoints_from_json(encoded: str) -> tuple[float, float, float, float]: """Return the breakpoints of a descriptor's source gate. - Parameters - ---------- - encoded : str - Validated descriptor JSON. - - Returns - ------- - tuple of float - The window's gate when one is set, else the recorded fine collar, in s. + - Returns: the window's gate when one is set, else the recorded fine collar, in s. """ window = _window_from_json(encoded) if window is not None: @@ -927,18 +822,8 @@ def _gate_derivative_from_json( ) -> Float[np.ndarray, " n_to"]: """Evaluate a descriptor's source-gate derivative once per configuration. - Parameters - ---------- - encoded : str - Validated descriptor JSON. - error : bool, optional - Return the derivative's rounding bound instead of the derivative. - - Returns - ------- - numpy.ndarray - Read-only ``g'``, or its bound, in 1/s on the full lattice: of the window - gate, else of the recorded collar. + - Returns: Read-only ``g'``, or its bound, in 1/s on the full lattice: of the window + gate, else of the recorded collar. """ lattice = json.loads(encoded)["lattice"] times = lattice_times(lattice["samples"], lattice["spacing"]) @@ -952,15 +837,7 @@ def _gate_derivative_from_json( def _gate_from_json(encoded: str) -> Float[np.ndarray, " n_to"] | None: """Evaluate a descriptor's window gate once per configuration. - Parameters - ---------- - encoded : str - Validated descriptor JSON. - - Returns - ------- - numpy.ndarray or None - Read-only gate on the full lattice, or None without a window. + - Returns: Read-only gate on the full lattice, or None without a window. """ window = _window_from_json(encoded) if window is None: @@ -1003,41 +880,14 @@ def waveform_identity(fields: Float[np.ndarray, "n_nodes n_to"]) -> str: def _metadata_json(value: str, name: str, expected: type) -> None: - """Require finite JSON containers for provider-neutral immutable diagnostics. - - Parameters - ---------- - value : str - Serialized candidate metadata. - name : str - Field name used in diagnostics. - expected : type - Required outer container type, either dict or list. - """ + """Require finite JSON containers for provider-neutral immutable diagnostics.""" def reject_constant(constant: str) -> None: - """Reject a nonstandard JSON constant. - - Parameters - ---------- - constant : str - Parsed nonfinite token. - """ + """Reject a nonstandard JSON constant.""" raise BackendError(f"{name} contains nonfinite JSON constant {constant}") def finite_float(number: str) -> float: - """Parse one finite JSON floating-point token. - - Parameters - ---------- - number : str - Decimal representation of a JSON number. - - Returns - ------- - float - Finite binary64 value. - """ + """Parse one finite JSON floating-point token.""" result = float(number) if not math.isfinite(result): raise BackendError(f"{name} contains a nonfinite number") @@ -1303,15 +1153,7 @@ def reserve( def _per_evaluation(self, calls: int | None) -> int: """Return the provider calls of one evaluation. - Parameters - ---------- - calls : int or None - Explicit positive count, or None for ``calls_per_evaluation``. - - Returns - ------- - int - Positive number of provider calls. + - ``calls``: explicit positive count, or None for ``calls_per_evaluation``. """ if calls is None: return self.calls_per_evaluation diff --git a/src/solprop/translate/tdse_source.py b/src/solprop/translate/tdse_source.py index 8a3c30a8..d223e177 100644 --- a/src/solprop/translate/tdse_source.py +++ b/src/solprop/translate/tdse_source.py @@ -76,18 +76,7 @@ def _helium(mode: MarcatiliMode) -> Species: - """Resolve the same helium material that supplies the mode's dispersion. - - Parameters - ---------- - mode : MarcatiliMode - Declared scalar capillary mode and its material. - - Returns - ------- - Species - Admitted optmat helium species with no initial ionisation. - """ + """Resolve the same helium material that supplies the mode's dispersion.""" if mode.mixture is not None: if len(mode.mixture.components) != 1: raise TranslationError( @@ -294,20 +283,7 @@ def steady_current_certificate( def _steady_current_certificate( descriptor: TDSEDescriptor, omega: bytes, active: bytes ) -> float: - """Evaluate one configuration's certificate once. - - Parameters - ---------- - descriptor : TDSEDescriptor - Hashable gated descriptor. - omega, active : bytes - Raw float64 frequencies and boolean mask, the cache key of the band. - - Returns - ------- - float - The certificate. - """ + """Evaluate one configuration's certificate once.""" frequencies = np.frombuffer(omega, dtype=np.float64) mask = np.frombuffer(active, dtype=np.bool_) lattice = json.loads(descriptor.configuration_json)["lattice"] diff --git a/src/solprop/translate/transverse.py b/src/solprop/translate/transverse.py index 97e5de42..fe14475a 100644 --- a/src/solprop/translate/transverse.py +++ b/src/solprop/translate/transverse.py @@ -73,16 +73,8 @@ class _Axis: def _axes(space: RadialGrid | FreeGrid | PlanarGrid) -> tuple[_Axis, ...]: """Return the axes whose faces bound `space`, with each column's wavenumber. - Parameters - ---------- - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The transverse grid. - - Returns - ------- - tuple of _Axis - One radially; x and y on a Cartesian grid, x alone on a line; y on a planar - grid, whose columns are ``p + n_p * iy``. + - Returns: one radially; x and y on a Cartesian grid, x alone on a line; y on a + planar grid, whose columns are ``p + n_p * iy``. """ if isinstance(space, RadialGrid): return (_Axis("r", np.asarray(space.k_r), space.radius, radial=True),) @@ -95,18 +87,7 @@ def _axes(space: RadialGrid | FreeGrid | PlanarGrid) -> tuple[_Axis, ...]: def _nodes_window(space: RadialGrid | FreeGrid | PlanarGrid) -> np.ndarray: - """Return the grid's window on the nodes the collar's rate is applied at. - - Parameters - ---------- - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The transverse grid. - - Returns - ------- - numpy.ndarray - One inside the requested size, falling to zero at the outer size. - """ + """Return the grid's window on the nodes the collar's rate is applied at.""" if isinstance(space, PlanarGrid): return np.repeat(space.window_y, int(space.x.size)) return np.asarray(space.window) @@ -173,30 +154,12 @@ def _column_power( ) -> Float[np.ndarray, " n_columns"]: """Return each spectral column's launch energy over the requested band. - Parameters - ---------- - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The transverse grid, for its spectral weights. - field0 : numpy.ndarray - The noise-free launch, canonical layout. - mask : numpy.ndarray - The requested band's bins. - - Returns - ------- - numpy.ndarray - Energy per column in the field's own units. + - ``field0``: the noise-free launch, canonical layout. """ density = np.empty(field0.shape[1]) def columns(rows: slice) -> None: - """Sum one block of columns; each column's own sum is unchanged by blocking. - - Parameters - ---------- - rows : slice - The block of columns. - """ + """Sum one block of columns; each column's own sum is unchanged by blocking.""" density[rows] = np.sum(np.abs(field0[:, rows][..., mask]) ** 2, axis=(0, 2, 3)) # A large launch's single pass took 2 s of translation (solprop issue #347). @@ -209,18 +172,11 @@ def _ramp_profile( ) -> Float[np.ndarray, " n_columns"]: """Return the raised-cosine ramp from `start` to `top` on the columns. - Parameters - ---------- - columns : numpy.ndarray - Each column's ``|k|``, rad/m. - start, top : float - Where the ramp leaves zero and reaches one, rad/m; ``start < top``, since - every axis holds at least two samples and so a nonzero largest ``|k|``. - - Returns - ------- - numpy.ndarray - The profile, in [0, 1]. + - ``columns``: each column's ``|k|``, rad/m. + - ``start``, ``top``: where the ramp leaves zero and reaches one, rad/m; ``start < + top``, since every axis holds at least two samples and so a nonzero largest + ``|k|``. + - Returns: the profile, in [0, 1]. """ inside = np.clip((np.abs(columns) - start) / (top - start), 0.0, 1.0) return 0.5 * (1.0 - np.cos(np.pi * inside)) @@ -244,31 +200,11 @@ def _intermediate_return( # noqa: PLR0917 - the design and its weights exact symbol per frequency, weighted by the launch's spectrum. The band is taken in sixteen contiguous groups of bins, each at its power-weighted wavenumber. - Parameters - ---------- - axes : tuple of _Axis - The grid's axes. - kappa_p : list of numpy.ndarray - The protected band's edge per axis. - widths : tuple of float - The collar's width per axis, m. - starts : list of float - The ramp's start per axis, rad/m. - alpha0 : float - The collar's peak rate, 1/m. - field0 : numpy.ndarray - The noise-free launch. - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The grid, for its spectral weights. - grid : solprop.grid.TimeGrid - The temporal grid. - k : numpy.ndarray - The band's wavenumbers, the entrance's first. - - Returns - ------- - float - A share of the launch energy. + - ``axes``: the grid's axes. + - ``kappa_p``: the protected band's edge per axis. + - ``widths``: the collar's width per axis, m. + - ``starts``: the ramp's start per axis, rad/m. + - ``alpha0``: the collar's peak rate, 1/m. """ band = np.nonzero(grid.inband_mask & grid.active_mask)[0] power = np.asarray(space.spectral_weights)[:, None] * np.sum( @@ -508,20 +444,7 @@ def resolve_transverse( # noqa: PLR0912, PLR0915 - one resolution, in order def _trial_grid( space: RadialGrid | FreeGrid | PlanarGrid, fraction: float ) -> RadialGrid | FreeGrid | PlanarGrid: - """Return `space` with another collar fraction at the same spacing. - - Parameters - ---------- - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The design's grid. - fraction : float - The trial fraction. - - Returns - ------- - solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The trial grid. - """ + """Return `space` with another collar fraction at the same spacing.""" scale = (1.0 + fraction) / (1.0 + float(space.collar_fraction)) if isinstance(space, RadialGrid): return RadialGrid(space.radius, max(2, round(space.n_r * scale)), fraction) @@ -545,22 +468,7 @@ def _relaunched( trial: RadialGrid | FreeGrid | PlanarGrid, field0: Complex[np.ndarray, "n_batch n_modes n_pol n_omega"], ) -> Complex[np.ndarray, "1 n_trial n_pol n_omega"]: - """Return the launch on a trial grid: its field, zero beyond the design's grid. - - Parameters - ---------- - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The design's grid. - trial : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The trial grid, the same spacing. - field0 : numpy.ndarray - The launch's spectral columns on `space`. - - Returns - ------- - numpy.ndarray - Its spectral columns on `trial`. - """ + """Return the launch on a trial grid: its field, zero beyond the design's grid.""" first = np.asarray(field0[:1], dtype=np.complex128) _, n_cols, n_pol, n_omega = first.shape flat = first[0].reshape(n_cols, n_pol * n_omega) From ffbfe0c6429a38a15c80487b040803bf0656b206 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:13:03 +0000 Subject: [PATCH 07/50] Slim private docstrings in solprop.driver Mechanical: uv run python tools/docstring_diet.py src/solprop/driver --write --check --report --rename-closures Co-Authored-By: Claude Opus 5.5 --- src/solprop/driver/_launch.py | 167 +- src/solprop/driver/_scan_batch.py | 157 +- src/solprop/driver/_scan_model.py | 22 +- src/solprop/driver/_scan_run.py | 437 +-- src/solprop/driver/batch.py | 76 +- src/solprop/driver/estimate.py | 48 +- src/solprop/driver/executors/_queue_state.py | 95 +- src/solprop/driver/executors/pool.py | 295 +- src/solprop/driver/executors/queue.py | 152 +- src/solprop/driver/executors/slurm.py | 18 +- src/solprop/driver/material_output.py | 294 +- src/solprop/driver/mixtures.py | 25 +- src/solprop/driver/plane_view.py | 13 +- src/solprop/driver/run.py | 318 +- src/solprop/driver/scaffold.py | 15 +- src/solprop/driver/service.py | 258 +- src/solprop/driver/simulation.py | 3412 +++--------------- src/solprop/driver/summary.py | 304 +- src/solprop/driver/tdse.py | 797 +--- src/solprop/driver/tdse_consistency.py | 188 +- 20 files changed, 992 insertions(+), 6099 deletions(-) diff --git a/src/solprop/driver/_launch.py b/src/solprop/driver/_launch.py index b7c6fc65..99148663 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 46fa34ae..7a816f95 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 dff5d4f9..46004ad7 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}") diff --git a/src/solprop/driver/_scan_run.py b/src/solprop/driver/_scan_run.py index 650a3db3..973f3e79 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 0a1de779..a19b3bd9 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 ad060f1e..a2f8dee7 100644 --- a/src/solprop/driver/estimate.py +++ b/src/solprop/driver/estimate.py @@ -110,19 +110,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 +122,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 +327,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 9d711bed..e6cb2cbd 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,15 +67,7 @@ def read_json(path: Path) -> dict: def process_stamp(pid: int) -> str | None: """Read process creation identity where the operating system exposes it. - Parameters - ---------- - pid : int - Positive operating-system process identifier. - - Returns - ------- - str or None - Process creation identity, or None if unavailable. + - Returns: process creation identity, or None if unavailable. """ proc = Path(f"/proc/{pid}/stat") if proc.exists(): @@ -128,10 +84,7 @@ def process_stamp(pid: int) -> str | None: 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, @@ -144,17 +97,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(): @@ -174,32 +118,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 2fa32a58..d1642f0f 100644 --- a/src/solprop/driver/executors/pool.py +++ b/src/solprop/driver/executors/pool.py @@ -192,18 +192,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 +202,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 +211,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 +223,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 +234,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 +245,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 +252,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 +333,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 +351,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 +367,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 +379,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 +405,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 +484,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 +500,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 +539,8 @@ 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. + - Returns: worker process and controller pipe. """ context = mp.get_context("spawn") parent, child = context.Pipe() @@ -707,18 +570,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 +586,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 +595,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 +716,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 +749,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 +764,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 +781,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 +816,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 +839,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 658ddc8b..296f67aa 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 10d45ffc..99f62ea2 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 8c5e0c67..7a579e74 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 1720d3e9..6d7a36d2 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 817fc222..cfb50483 100644 --- a/src/solprop/driver/plane_view.py +++ b/src/solprop/driver/plane_view.py @@ -116,18 +116,7 @@ def quantities(self) -> dict[str, Quantity]: 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 7a0bfbb6..d7cc9eef 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, @@ -577,19 +555,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 +585,12 @@ 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. + - ``boundary_check``: the thresholds the window's edges are watched with, or + ``None``. """ start_plane, z_start = start summary = run_summary( @@ -784,14 +736,7 @@ 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) @@ -831,22 +776,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 +807,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 +826,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 +880,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 @@ -1054,19 +953,7 @@ class _FailedObservation: def _method_history( request: RunRequest, ) -> 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. - """ + """Return which kind of method history a run's march keeps.""" integrator = request.integration.method if isinstance(integrator, Multistep): return "adams" @@ -1113,20 +1000,7 @@ 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. """ def __init__( @@ -1156,47 +1030,26 @@ 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. """ if required and self._kept_z == z: return @@ -1275,10 +1128,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 +1158,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 +1250,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 +1301,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 +1331,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 +1364,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 c0f0ab98..2602be67 100644 --- a/src/solprop/driver/scaffold.py +++ b/src/solprop/driver/scaffold.py @@ -101,20 +101,13 @@ def gnlse_source( to_field = sqrtw_to_vm(problem.aeff) n_t, n_to = grid.n_t, grid.n_to - def nonlinear( + def _nonlinear( state: Complex[np.ndarray, " n_omega"], ) -> Complex[np.ndarray, " n_omega"]: """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. + - 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 @@ -122,7 +115,7 @@ def nonlinear( transformed = fourier.forward(polarisation[0], kind="envelope") return prefactor * fourier.crop(transformed, n_t, kind="envelope") - return nonlinear + return _nonlinear def propagate_fixed_rk4( diff --git a/src/solprop/driver/service.py b/src/solprop/driver/service.py index d4456faa..97df3789 100644 --- a/src/solprop/driver/service.py +++ b/src/solprop/driver/service.py @@ -164,15 +164,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)) @@ -193,17 +185,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} @@ -220,12 +202,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: @@ -239,13 +216,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 @@ -254,15 +225,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: @@ -281,18 +244,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" @@ -309,15 +264,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: @@ -339,12 +286,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: @@ -364,16 +305,7 @@ 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. """ self._chunks.append((history, rows)) if report and history is not None: @@ -388,20 +320,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 = [] @@ -416,22 +342,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, @@ -459,26 +374,10 @@ 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). + - ``attempts``: its history and rows. + - ``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 @@ -495,14 +394,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``. + - ``stored``: its field exported into the plane store's stored form, or ``None``. """ if stored is None: output.save(plane) @@ -598,19 +490,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 @@ -787,12 +673,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: @@ -829,31 +710,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 @@ -921,36 +779,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 @@ -1067,16 +901,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 @@ -1092,16 +918,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 2f72f4e7..145d839a 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", [ @@ -1088,11 +1043,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 = ( @@ -1197,20 +1147,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 = ( @@ -1247,15 +1183,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 @@ -1266,25 +1194,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 @@ -1293,23 +1209,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") @@ -1323,21 +1223,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 @@ -1354,16 +1239,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, @@ -1400,27 +1275,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 = ( @@ -1449,18 +1303,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 = ( @@ -1709,17 +1551,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" @@ -1740,29 +1573,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`). @@ -1801,26 +1617,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), @@ -1867,34 +1667,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) @@ -1912,20 +1689,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 @@ -1961,18 +1729,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: @@ -2008,15 +1764,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 @@ -2024,15 +1772,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)) @@ -2040,17 +1780,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 @@ -2314,24 +2046,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) @@ -2427,14 +2144,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) @@ -2465,10 +2175,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 = ( @@ -2611,11 +2318,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 @@ -2896,26 +2598,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" @@ -2936,24 +2622,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 @@ -2989,17 +2658,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) @@ -3015,17 +2675,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") @@ -3058,18 +2709,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 " @@ -3079,18 +2719,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 " @@ -3247,14 +2876,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 @@ -3267,11 +2889,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) @@ -3279,18 +2896,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( { @@ -3445,10 +3051,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( @@ -3513,14 +3116,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 @@ -3555,22 +3151,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) @@ -3716,31 +3297,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): @@ -3955,20 +3515,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 @@ -4010,12 +3558,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"): @@ -4041,18 +3584,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": @@ -4094,18 +3626,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 @@ -4122,17 +3642,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 @@ -4156,15 +3668,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: @@ -4181,11 +3685,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 @@ -4219,28 +3718,10 @@ 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. """ if state is None: return Start.entrance( @@ -4304,28 +3785,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: @@ -4352,15 +3816,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} @@ -4393,20 +3850,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") @@ -4823,46 +4267,16 @@ 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. + - Returns: cached translation and the ordinary resumable execution interface. """ chosen_frame = frame if chosen_frame is None: @@ -6363,86 +5777,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( @@ -6701,307 +6052,185 @@ 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`. + - ``shape``: Transform-limited shape of the input. + - ``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( @@ -7488,10 +6717,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)), @@ -7643,18 +6869,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, @@ -7680,17 +6895,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) @@ -7710,24 +6914,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), @@ -7766,19 +6952,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 @@ -7817,19 +6991,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, marked as held - 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, marked + as held so that nothing copies it; `initial` itself otherwise. The caller's own + flags are not touched. """ if ( isinstance(initial, np.ndarray) @@ -7846,16 +7010,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``. """ placeholder = np.broadcast_to( np.zeros(1, dtype=np.complex128), problem.initial.field0.shape @@ -7868,16 +7022,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 +7147,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 +7170,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 +7207,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 +7228,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 +7250,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 +7306,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 +7340,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 +7377,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 +7452,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 +7493,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 +7531,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 +7568,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 +7591,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 +7627,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 +7683,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 +7703,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 +7718,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 +7750,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 +7796,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 +7818,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 +7836,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 +7852,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 +7889,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 +7900,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 +7941,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 +7957,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 +7979,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 +7991,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 +8004,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 +8016,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 +8046,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) @@ -9288,16 +8065,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 @@ -9313,27 +8080,7 @@ 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. + - Returns: the handler, and the state to resume from or ``None``. """ if resume and metadata.configuration.get("_benchmark_plasma_source"): raise DriverError("gated plasma benchmark output cannot be resumed") @@ -9352,19 +8099,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``. - """ + """Refuse a ``resume`` value that is not one of its three.""" if resume is True or resume is False or resume == "interpolated": return msg = f"resume must be False, True or 'interpolated', got {resume!r}" @@ -9372,24 +8107,7 @@ 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``. - """ + """Return whether a file has anything left to compute, by position.""" return ( state.checkpoint is not None or next_plane < request.observation.save_planes.size @@ -9402,20 +8120,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 @@ -9436,29 +8140,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 @@ -9475,28 +8157,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": @@ -9540,44 +8205,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( @@ -9646,26 +8285,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: @@ -9708,22 +8329,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]) @@ -9754,36 +8360,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 = [ @@ -9852,24 +8434,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: @@ -9889,43 +8456,23 @@ 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. + - ``route``: the interface they are for. + - 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 @@ -9940,30 +8487,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: @@ -10008,25 +8536,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: @@ -10052,26 +8562,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: @@ -10126,23 +8617,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( @@ -10157,16 +8634,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": @@ -10183,22 +8650,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 @@ -10221,27 +8673,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 @@ -10271,21 +8705,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 @@ -10299,21 +8719,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) @@ -10321,22 +8727,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), @@ -10357,17 +8748,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: @@ -10397,19 +8781,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 @@ -10521,33 +8893,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: @@ -10600,36 +8949,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( @@ -10711,25 +9032,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): @@ -10761,24 +9066,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 @@ -10799,20 +9088,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 @@ -10830,20 +9110,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" @@ -10890,19 +9157,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 @@ -10931,29 +9185,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: @@ -11008,21 +9243,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 = ( @@ -11070,44 +9294,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: @@ -11151,18 +9348,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: @@ -11201,31 +9386,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( @@ -11253,21 +9416,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) @@ -11282,18 +9432,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" @@ -11350,41 +9488,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: @@ -11457,24 +9568,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 @@ -11484,19 +9580,8 @@ def _entry_overrides[P, R]( # noqa: PLR0917 - called positionally 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( @@ -11506,13 +9591,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", @@ -11538,23 +9617,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 @@ -11656,17 +9721,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 @@ -11679,19 +9734,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 @@ -11724,11 +9767,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") @@ -11747,25 +9785,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: @@ -11811,26 +9831,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( @@ -11896,23 +9900,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: @@ -11948,18 +9937,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) @@ -11981,25 +9959,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 = ( @@ -12046,23 +10005,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 @@ -12080,23 +10024,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 = ( @@ -12124,15 +10051,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 @@ -12154,23 +10073,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] @@ -12221,13 +10129,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) @@ -12241,20 +10143,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): @@ -12279,26 +10171,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 = [ @@ -12383,18 +10257,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 @@ -12428,18 +10290,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( @@ -12458,19 +10308,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. @@ -12492,17 +10332,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]) @@ -12510,15 +10341,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) @@ -12535,27 +10359,8 @@ 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. """ field, circular = peak bound = list(bind_reference_field(responses, field, field_range)) @@ -12572,18 +10377,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): @@ -12605,22 +10398,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 @@ -12644,20 +10424,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 = ( @@ -12676,18 +10443,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 @@ -12708,21 +10463,8 @@ 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. - - 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 @@ -12749,15 +10491,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 @@ -12772,18 +10506,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 @@ -12808,19 +10530,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): @@ -12833,18 +10545,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( @@ -12878,18 +10579,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] @@ -12925,25 +10616,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 = ( @@ -12978,30 +10651,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: @@ -13059,18 +10715,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 @@ -13083,22 +10727,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 7a7c2faf..f72f2bef 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 1f978307..0a029c8e 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,10 @@ 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. + - Returns: every ``HaccBasis`` field but ``source`` and ``preset``. """ if isinstance(atom, hacc.Argon): raise MaterialError( @@ -373,15 +321,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 +333,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,10 +624,7 @@ 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 @@ -728,24 +653,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 +689,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 +700,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 +713,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 +729,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 +751,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 +766,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 +778,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 +794,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] @@ -1079,20 +912,6 @@ class _HaccModel: The model is entered into the caller's stack first, so it closes after every 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. """ def __init__( @@ -1147,18 +966,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 +995,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 +1007,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 +1042,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 +1069,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 +1105,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 +1184,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 +1207,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 +1282,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 +1345,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 +1355,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 +1373,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 +1398,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 +1414,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 +1422,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 +1431,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 +1444,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 +1467,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 +1480,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 +1489,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 +1518,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 +1667,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 +1711,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 +1729,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 +1759,9 @@ 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. + - ``index``: the chunk that raised, or None. + - ``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 +1789,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 +1819,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 +1842,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 +1879,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 +1917,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 +1959,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 +2018,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 +2053,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 +2100,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 +2125,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 +2168,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 +2224,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 +2316,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 +2365,8 @@ 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. + - ``evaluation``: the archive's evaluation, or None. + - Returns: the write failure, if any. """ archive = context.archive if archive is None or evaluation is None: @@ -2927,18 +2389,8 @@ 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. + - ``evaluation``: the archive's evaluation, or None. + - ``primary``: the error raised, which gains a note if recording fails. """ archive = context.archive if archive is None or evaluation is None: @@ -2955,14 +2407,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 +2416,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 +2435,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) diff --git a/src/solprop/driver/tdse_consistency.py b/src/solprop/driver/tdse_consistency.py index b6f0c3c1..1549fc77 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" @@ -694,20 +606,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 +729,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 +935,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 +955,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__} From a077ef5a2a3fe073d6bdd901c7bd69910db18ade Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:13:03 +0000 Subject: [PATCH 08/50] Shorten two driver summaries by hand, as the slimming left them Co-Authored-By: Claude Opus 5.5 --- src/solprop/driver/run.py | 16 +++------------- src/solprop/driver/tdse.py | 12 ++---------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/src/solprop/driver/run.py b/src/solprop/driver/run.py index d7cc9eef..846cc56b 100644 --- a/src/solprop/driver/run.py +++ b/src/solprop/driver/run.py @@ -743,20 +743,10 @@ def _retain( def _check_retainable(output: Output, retention: FieldRetention) -> None: - """Refuse a field-free rule for an output that cannot hold one, before opening it. + """Refuse a field-free rule for an output that cannot hold one. - Parameters - ---------- - output : solprop.output.schema.Output - The run's handler. - retention : solprop.output.schema.FieldRetention - The run's resolved rule. - - 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 = ( diff --git a/src/solprop/driver/tdse.py b/src/solprop/driver/tdse.py index 0a029c8e..e39db0b0 100644 --- a/src/solprop/driver/tdse.py +++ b/src/solprop/driver/tdse.py @@ -630,17 +630,9 @@ def rows(self) -> int: 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 From 03f6ad98a7363e3a838ae1b174efcdb50a75fbc3 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:13:07 +0000 Subject: [PATCH 09/50] Slim private docstrings in solprop's top-level modules Mechanical: python tools/docstring_diet.py src/solprop/*.py --write --check --report --rename-closures The three summaries the tool skipped (D200) are shortened by hand: log.LogReporter._say, pulses._analytic, transverse_monitor._insensitive_reason. Co-Authored-By: Claude Opus 5.5 --- src/solprop/_display.py | 189 +------ src/solprop/_helpers.py | 47 +- src/solprop/_host.py | 98 +--- src/solprop/_interrupts.py | 87 +--- src/solprop/_process.py | 259 +--------- src/solprop/adiabatic.py | 142 +---- src/solprop/boundaries.py | 105 +--- src/solprop/capabilities.py | 68 +-- src/solprop/cli.py | 103 +--- src/solprop/fourier.py | 89 +--- src/solprop/gasflow.py | 521 +++---------------- src/solprop/grid.py | 429 +++------------ src/solprop/hankel.py | 13 +- src/solprop/linear.py | 18 +- src/solprop/log.py | 142 +---- src/solprop/monitor.py | 196 +------ src/solprop/noise.py | 44 +- src/solprop/optics.py | 288 ++--------- src/solprop/polarisation.py | 30 +- src/solprop/profiles.py | 657 ++++------------------- src/solprop/pulses.py | 755 ++++----------------------- src/solprop/scan.py | 23 +- src/solprop/spatial.py | 499 +++--------------- src/solprop/spatial_absorber.py | 16 +- src/solprop/stats.py | 424 ++------------- src/solprop/tools.py | 831 +++++------------------------- src/solprop/transverse_monitor.py | 612 +++------------------- 27 files changed, 931 insertions(+), 5754 deletions(-) diff --git a/src/solprop/_display.py b/src/solprop/_display.py index 277d29fa..38ad878c 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): @@ -192,50 +133,15 @@ 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' - """ + """Return the end of a long text, which for a path is the part that matters.""" return text if len(text) <= width else "..." + text[-(width - 3) :] 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 +150,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 +172,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 +207,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 a68e4298..b946e6ca 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 334b9bd5..3e325187 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 3d810bfd..b35faf78 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/_process.py b/src/solprop/_process.py index 9c9dde99..8c1a80ec 100644 --- a/src/solprop/_process.py +++ b/src/solprop/_process.py @@ -59,23 +59,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} @@ -95,17 +79,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 {})}) @@ -130,13 +104,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" @@ -180,13 +148,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: @@ -218,20 +180,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) @@ -264,17 +212,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: @@ -316,13 +253,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)) @@ -334,11 +265,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", ()) @@ -360,16 +286,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() @@ -439,16 +356,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" @@ -478,51 +386,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: @@ -553,18 +430,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) @@ -573,17 +441,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: @@ -603,15 +462,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 @@ -653,18 +504,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) @@ -676,15 +517,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 @@ -709,11 +542,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 @@ -727,20 +557,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: @@ -766,19 +583,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). @@ -845,18 +653,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}" diff --git a/src/solprop/adiabatic.py b/src/solprop/adiabatic.py index 1651e4ff..ecab27cd 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/boundaries.py b/src/solprop/boundaries.py index 4199d0c6..0d778ae8 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) @@ -394,17 +388,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 +518,7 @@ 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. - - 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 +538,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 +581,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 +1026,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 +1039,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 +1392,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/capabilities.py b/src/solprop/capabilities.py index 92fb9eae..0b835f83 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 89516d83..286332f7 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,7 @@ 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. """ if not received: return 0 @@ -199,22 +163,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 +177,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 +194,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 +247,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/fourier.py b/src/solprop/fourier.py index b763266e..26af58ff 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 d246ad2c..c4e0fe11 100644 --- a/src/solprop/gasflow.py +++ b/src/solprop/gasflow.py @@ -237,23 +237,7 @@ 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. - - 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 +250,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 +302,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 +330,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 +339,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 +347,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 +357,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 +379,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 +405,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 +417,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 +425,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 +441,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 +454,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 +464,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 +530,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 +648,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 +717,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 +834,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 @@ -1323,10 +1099,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 +1132,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 +1156,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 +1179,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 +1240,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 +1278,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 +1311,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 +1348,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 +1382,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 +1648,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 +1690,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 13e1f37b..cadf7abe 100644 --- a/src/solprop/grid.py +++ b/src/solprop/grid.py @@ -146,19 +146,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) @@ -333,17 +322,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 = ( @@ -368,22 +347,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 @@ -578,10 +544,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")), @@ -598,20 +561,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) @@ -629,27 +579,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) @@ -760,22 +695,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 @@ -887,10 +811,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", [ ( @@ -1026,10 +947,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)", [ ( @@ -1179,18 +1097,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: @@ -1206,24 +1113,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) @@ -1236,16 +1126,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. @@ -1267,24 +1149,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)) @@ -1292,25 +1157,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}" @@ -1330,18 +1178,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 @@ -1369,23 +1207,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 @@ -1760,22 +1586,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 @@ -1803,10 +1621,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 @@ -1839,20 +1654,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 = ( @@ -1879,25 +1682,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}" @@ -1939,19 +1727,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)) @@ -1961,18 +1738,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( @@ -2097,27 +1864,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 @@ -2144,13 +1902,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}" @@ -2284,20 +2036,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 @@ -2305,7 +2049,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) @@ -2338,12 +2082,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() @@ -2366,18 +2104,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 @@ -2431,20 +2158,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) @@ -2454,23 +2168,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 dad647ee..91196074 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 c1ec5dc1..6510836b 100644 --- a/src/solprop/linear.py +++ b/src/solprop/linear.py @@ -464,23 +464,7 @@ 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,)``. - - 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 ec5f5ef5..809a31bf 100644 --- a/src/solprop/log.py +++ b/src/solprop/log.py @@ -1081,32 +1081,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 +1102,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 +1124,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 +1135,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 +1157,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 +1222,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 +1234,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 +1250,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 +1261,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 @@ -1561,10 +1483,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 +1501,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 +1509,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 +1528,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/monitor.py b/src/solprop/monitor.py index f0f6c927..a75f3c13 100644 --- a/src/solprop/monitor.py +++ b/src/solprop/monitor.py @@ -148,21 +148,7 @@ def _guard_message( One text for every owner of a monitor's decisions: :meth:`BoundaryMonitor._judge` and a backend whose session judges natively (SEP 47 section 6.4). - Parameters - ---------- - check : BoundaryCheck - The thresholds. - worst : float - The amplitude of the worse guard occupancy. - z : float or None - Where, m, or ``None`` for the launch. - stop : bool - Whether it stops the run, rather than warns. - - Returns - ------- - str - The message. + - ``z``: where, m, or ``None`` for the launch. """ if z is None: where = "at the launch" @@ -188,20 +174,7 @@ def _guard_message( def _removal_message(check: BoundaryCheck, removed: float) -> str: - """Return the message of the end of a run's removal warning (D5). - - Parameters - ---------- - check : BoundaryCheck - The thresholds. - removed : float - The removed fraction of the launch energy. - - Returns - ------- - str - The message. - """ + """Return the message of the end of a run's removal warning (D5).""" return ( f"the temporal collar removed {removed:.2e} of the launch energy, " f"whose square root is above the {check.budget:.0e} budget: " @@ -210,15 +183,7 @@ def _removal_message(check: BoundaryCheck, removed: float) -> str: def _log_finish(removed: float, low: float, high: float) -> None: - """Log the removed fraction and the guards' peaks at the end of a run (D5). - - Parameters - ---------- - removed : float - The removed fraction of the launch energy. - low, high : float - The guards' largest occupancies. - """ + """Log the removed fraction and the guards' peaks at the end of a run (D5).""" logger.info( "the temporal collar removed %.3e of the launch energy; the guards peaked " "at %.2e (low) and %.2e (high)", @@ -234,16 +199,6 @@ def _boundary_warning(message: str) -> BoundaryWarning: One construction for every owner of a monitor's decisions: the monitor here and a backend whose session judges natively (SEP 47 section 6.4), so that both issue the same warning. - - Parameters - ---------- - message : str - Its text. - - Returns - ------- - solprop.exceptions.BoundaryWarning - The warning, concerning ``boundary_check``. """ return BoundaryWarning(message, parameter="boundary_check") @@ -254,23 +209,6 @@ def _count(value: float, name: str) -> int: Python's ``int`` of the recorded number, which truncates; a count that is not finite, or truncates below zero, is refused, by the monitor here and by a native session's alike (SEP 47 section 5.5). - - Parameters - ---------- - value : float - The recorded number. - name : str - Its field, for the message. - - Returns - ------- - int - The count. - - Raises - ------ - BoundaryError - When `value` is not finite or is negative. """ if not math.isfinite(value) or math.trunc(value) < 0: msg = f"a resumed boundary record holds an invalid count: {name} = {value!r}" @@ -281,22 +219,7 @@ def _count(value: float, name: str) -> int: def _number( record: Mapping[str, object], name: str, default: float = math.nan ) -> float: - """Return a record's number, or `default` when it is absent or not a number. - - Parameters - ---------- - record : collections.abc.Mapping - A monitor record read back from a run's file. - name : str - The field. - default : float, optional - What an absent or unmeasured field stands for. - - Returns - ------- - float - The value. - """ + """Return a record's number, or `default` when it is absent or not a number.""" value = record.get(name) if isinstance(value, bool) or not isinstance(value, int | float): return default @@ -703,18 +626,8 @@ def split_sums( def _judged(energies: BoundaryEnergies) -> tuple[float, Float[np.ndarray, " 4"]]: """Return a reading's energy and weighted energies, or nothing for no light. - Parameters - ---------- - energies : BoundaryEnergies - A backend's reading of one state. - - Returns - ------- - total : float - The state's energy in the proxy's units; zero for a state with none. - regions : numpy.ndarray - The energies in the low guard, the high guard and the collar, and the - collar's removal rate, energy per metre. + - Returns ``regions``: the energies in the low guard, the high guard and the + collar, and the collar's removal rate, energy per metre. """ total = float(energies.total) if total <= 0.0 or not math.isfinite(total): @@ -728,16 +641,6 @@ def _above(self, regions: Float[np.ndarray, " 4"]) -> Float[np.ndarray, " 4"]: The removal rate subtracts the floor's expectation and keeps its sign: its integral along z averages the fluctuation out, which clipping would rectify into a spurious removal. - - Parameters - ---------- - regions : numpy.ndarray - A state's four weighted energies (:meth:`_regions`). - - Returns - ------- - numpy.ndarray - The same four, above the floor. """ above = regions - self._floor_regions * np.array( [FLOOR_MARGIN, FLOOR_MARGIN, FLOOR_MARGIN, 1.0] @@ -996,18 +899,7 @@ def step( self._judge(shares, z) def _read_cap(self, energies: BoundaryEnergies) -> None: - """Read the split collar's crossing limit from a reading's occupancy. - - Parameters - ---------- - energies : BoundaryEnergies - The backend's reading. - - Raises - ------ - BackendError - When the run has a crossing limit and the reading carries no occupancy. - """ + """Read the split collar's crossing limit from a reading's occupancy.""" if self.crossing is None: return if energies.occupancy is None: @@ -1041,23 +933,6 @@ def _found(self, energies: BoundaryEnergies) -> BoundaryEnergies: the deep collar, the guards with it, by ``exp(-alpha h)``, which would hide the light that reached the edge by a factor set by the step. Otherwise the state's own reading. - - Parameters - ---------- - energies : BoundaryEnergies - The backend's reading of the committed state. - - Returns - ------- - BoundaryEnergies - The reading to judge. - - Raises - ------ - BackendError - Under the split, if the reading carries nothing found, or found nothing - where the state holds light: every committed step was split, and a split - finds at least the energy it leaves. """ if not self.split: return energies @@ -1077,17 +952,7 @@ def _found(self, energies: BoundaryEnergies) -> BoundaryEnergies: def _judge(self, shares: Float[np.ndarray, " 4"], z: float | None) -> None: """Warn once, or stop, on a state's guard occupancy (D7). - Parameters - ---------- - shares : numpy.ndarray - The state's shares above the floor. - z : float or None - Its position, m, or ``None`` for the launch. - - Raises - ------ - BoundaryError - When the square root of a guard occupancy exceeds ``stop_factor`` budgets. + - ``z``: its position, m, or ``None`` for the launch. """ check = self.check worst = math.sqrt(max(float(shares[0]), float(shares[1]), 0.0)) @@ -1110,13 +975,8 @@ def _settle( raised only for a run that opted in, after :data:`QUIET_CHECKS` quiet checks in a row. - Parameters - ---------- - shares : numpy.ndarray or None - The state's shares above the floor, or ``None`` for a state with no light - to judge, which is quiet and checked lazily. - raw_collar : float, optional - The collar's share of the whole field, floor included. + - ``shares``: the state's shares above the floor, or ``None`` for a state with + no light to judge, which is quiet and checked lazily. """ if shares is None: self._busy, calm = False, True @@ -1265,10 +1125,7 @@ def _commit(self, z: float) -> None: h))`` for the step ``h`` that ended at `z`. A position behind the last committed one (a plane observed inside the step after it) adds nothing. - Parameters - ---------- - z : float - The committed position, m. + - ``z``: the committed position, m. """ step = z - self._committed if not step > 0.0: @@ -1350,10 +1207,7 @@ def _restore_quiet(self) -> None: def _plane_row(self, z: float) -> None: """Append one saved plane's row and start the next plane's guard maxima. - Parameters - ---------- - z : float - The plane's position, m. + - ``z``: the plane's position, m. """ row = self._planes read, self._read_since_plane = self._read_since_plane, False @@ -1424,32 +1278,24 @@ def summary(self) -> dict[str, object]: continues all of these (:meth:`resume`). """ - def finite(value: float) -> float | None: + def _finite(value: float) -> float | None: """Return `value`, or ``None`` when it was never measured (not finite). - Parameters - ---------- - value : float - A recorded value. - - Returns - ------- - float or None - The value, or ``None``. + - Returns: the value, or ``None``. """ return value if math.isfinite(value) else None record: dict[str, object] = { **dataclasses.asdict(self.check), - "launch_energy": finite(self._launch), + "launch_energy": _finite(self._launch), "resumed_at": list(self._resumed_at), - "launch_guard_low": finite(self._launch_occupancy[0]), - "launch_guard_high": finite(self._launch_occupancy[1]), - "launch_collar": finite(self._launch_occupancy[2]), + "launch_guard_low": _finite(self._launch_occupancy[0]), + "launch_guard_high": _finite(self._launch_occupancy[1]), + "launch_collar": _finite(self._launch_occupancy[2]), "guard_low_max": self._guard_max[0], "guard_high_max": self._guard_max[1], - "guard_low_at": finite(self._guard_at[0]), - "guard_high_at": finite(self._guard_at[1]), + "guard_low_at": _finite(self._guard_at[0]), + "guard_high_at": _finite(self._guard_at[1]), "collar_max": self._collar_max, "removed_fraction": max(self.removed, 0.0), "removed_signed": self.removed, @@ -1464,7 +1310,7 @@ def finite(value: float) -> float | None: "raw_removed_fraction": self._raw_removed, "collar_left_out_checks": self._left_out_checks, "planes": { - name: [finite(value) for value in values] + name: [_finite(value) for value in values] for name, values in self._planes.items() }, } diff --git a/src/solprop/noise.py b/src/solprop/noise.py index 5316b1ff..b214155b 100644 --- a/src/solprop/noise.py +++ b/src/solprop/noise.py @@ -189,15 +189,7 @@ def _seed_of(rng: np.random.Generator) -> int | None: from a bit generator need not carry one, and a seed given as a sequence of integers is not recorded. - Parameters - ---------- - rng : numpy.random.Generator - Generator to interrogate. - - Returns - ------- - int or None - The entropy, or ``None`` when it is absent or not a single integer. + - Returns: the entropy, or ``None`` when it is absent or not a single integer. """ # getattr rather than attribute access: the annotated type of ``seed_seq`` is the # ISeedSequence protocol, which does not declare ``entropy``. @@ -216,16 +208,6 @@ def _amplitude(grid: TimeGrid) -> Float[np.ndarray, " n_omega"]: exactly with the two prefactors of :func:`solprop.fourier.spectral_density`, so that ``S(omega_k) d_omega = hbar omega_k``. - - Parameters - ---------- - grid : solprop.grid.TimeGrid - Grid to draw on. - - Returns - ------- - numpy.ndarray - Amplitude per bin, shape ``(n_omega,)``, zero outside the active band. """ active = grid.active_mask amplitude = np.zeros(grid.n_omega) @@ -249,29 +231,7 @@ def _draw( alone, so that widening or narrowing the band does not shift the random stream and a seed keeps meaning the same realisation on the same grid size. - Parameters - ---------- - grid : solprop.grid.TimeGrid - Grid to draw on. - rng : numpy.random.Generator - Source of the random phase. - model : {'input', 'modified'} - Label to record on the realisation. - n_modes : int - Number of modes, at least 1. Each gets an independent realisation. - n_pol : int - Number of polarisation components, 1 or 2. Each gets an independent realisation, - because the two transverse components are separate field modes. - - Returns - ------- - NoiseRealisation - The realisation, with its seed and model recorded. - - Raises - ------ - NoiseError - If ``n_modes`` or ``n_pol`` is smaller than one. + - ``n_modes``: number of modes, at least 1. Each gets an independent realisation. """ for name, count in (("n_modes", n_modes), ("n_pol", n_pol)): if count < 1: diff --git a/src/solprop/optics.py b/src/solprop/optics.py index ad9db05a..32957656 100644 --- a/src/solprop/optics.py +++ b/src/solprop/optics.py @@ -369,13 +369,7 @@ def __post_init__(self) -> None: ) def _medium_text(self) -> str: - """Return the medium as text, for printing. - - Returns - ------- - str - A glass by name and source, a gas with its pressure, a mixture by its repr. - """ + """Return the medium as text, for printing.""" medium = self.medium if isinstance(medium, GlassIndex): return f"{medium.name} ({medium.source})" @@ -406,15 +400,8 @@ def _index( ) -> tuple[Complex[np.ndarray, " n_omega"], Complex[np.ndarray, " n_omega"]]: """Return the index and its analytic frequency derivative. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s, > 0. - - Returns - ------- - tuple of numpy.ndarray - ``n`` and ``dn/domega`` in s/rad, complex. + - ``omega``: angular frequencies in rad/s, > 0. + - Returns: ``n`` and ``dn/domega`` in s/rad, complex. """ medium = self.medium if isinstance(medium, GlassIndex): @@ -631,26 +618,8 @@ def _parse_columns( ) -> Float[np.ndarray, "rows columns"]: """Return chosen numeric columns of a mirror data file's text. - Parameters - ---------- - text : str - The file's content. - name : str - The file's name, for the refusal. - record : _MirrorRecord - How the file is laid out. - columns : tuple of int - The columns wanted. A vendor table may leave cells of *other* columns empty. - - Returns - ------- - numpy.ndarray - The chosen columns, rows by ``len(columns)``. - - Raises - ------ - MaterialError - If a wanted cell is missing or is not a number. + - ``columns``: the columns wanted. A vendor table may leave cells of *other* columns + empty. """ lines = [ row for row in text.splitlines() if row.strip() and not row.startswith("#") @@ -668,22 +637,7 @@ def _parse_columns( def _read_columns( name: str, record: _MirrorRecord, columns: tuple[int, ...] ) -> Float[np.ndarray, "rows columns"]: - """Return chosen numeric columns of one shipped mirror file. - - Parameters - ---------- - name : str - Resource name under ``solprop/physdata/data/mirrors``. - record : _MirrorRecord - How the file is laid out. - columns : tuple of int - The columns wanted. - - Returns - ------- - numpy.ndarray - The chosen columns, rows by ``len(columns)``. - """ + """Return chosen numeric columns of one shipped mirror file.""" source = resources.files("solprop.physdata") / "data" / "mirrors" / name return _parse_columns(source.read_text(encoding="utf-8"), name, record, columns) @@ -693,17 +647,8 @@ def _mean_columns( ) -> tuple[Float[np.ndarray, " rows"], Float[np.ndarray, " rows"]]: """Return a wavelength column and the mean of the value columns beside it. - Parameters - ---------- - table : numpy.ndarray - The wavelength in nm, then one or more value columns. - geometric : bool - Geometric mean (reflectivities) rather than arithmetic (dispersions). - - Returns - ------- - tuple of numpy.ndarray - Wavelength in m and the mean value, in the file's units. + - ``table``: the wavelength in nm, then one or more value columns. + - Returns: wavelength in m and the mean value, in the file's units. """ values = table[:, 1:] if geometric: @@ -809,10 +754,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. """ low, high = self.band[1], self.band[2] centre = np.array([4.0 * math.pi * C / (low + high)]) @@ -936,33 +878,14 @@ def _from_columns( ) -> "ChirpedMirror": """Integrate the dispersion to a phase and assemble the mirror. - Parameters - ---------- - reflectivity_wavelength : numpy.ndarray - Wavelengths of the reflectivity samples, m. - reflectivity : numpy.ndarray or None - Power reflectivity in [0, 1], or ``None``. - dispersion_wavelength : numpy.ndarray - Wavelengths of the dispersion samples, m. - dispersion : numpy.ndarray - GDD in s^2, or group delay in s. - group_delay : bool - Which of the two `dispersion` holds. - reference : float - Wavelength about which the constant and linear phase are removed, m. - fit_order : int - Order of the fitted polynomial. - fit_window : tuple of float or None - Angular-frequency window of the fit, rad/s. - band : tuple of float - The taper's four wavelengths, m. - name : str - A label. - - Returns - ------- - ChirpedMirror - One reflection. + - ``reflectivity_wavelength``: wavelengths of the reflectivity samples, m. + - ``reflectivity``: power reflectivity in [0, 1], or ``None``. + - ``dispersion_wavelength``: wavelengths of the dispersion samples, m. + - ``dispersion``: GDD in s^2, or group delay in s. + - ``reference``: wavelength about which the constant and linear phase are + removed, m. + - ``fit_window``: Angular-frequency window of the fit, rad/s. + - ``band``: the taper's four wavelengths, m. """ omega, values = _ascending(dispersion_wavelength, dispersion) if omega.size <= max(fit_order, 3): @@ -1168,22 +1091,8 @@ def _ascending( ) -> tuple[Float[np.ndarray, " n_wavelength"], Float[np.ndarray, " n_values"]]: """Return samples given against wavelength as ascending in angular frequency. - Parameters - ---------- - wavelength : numpy.ndarray - Wavelengths in m, strictly monotonic and positive. - values : numpy.ndarray - The samples. - - Returns - ------- - tuple of numpy.ndarray - Angular frequency in rad/s, ascending, and the samples in that order. - - Raises - ------ - MaterialError - If the wavelengths are not positive and strictly monotonic. + - ``wavelength``: wavelengths in m, strictly monotonic and positive. + - Returns: angular frequency in rad/s, ascending, and the samples in that order. """ steps = np.diff(wavelength) if np.any(wavelength <= 0.0) or not (np.all(steps > 0.0) or np.all(steps < 0.0)): @@ -1230,10 +1139,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 f"Chain: {len(self.elements)} elements in series", [ (str(index), repr(element)) for index, element in enumerate(self.elements) @@ -1267,15 +1173,7 @@ def __call__( def _pair_frequencies(omega: ArrayLike | float) -> Float[np.ndarray, " n_omega"]: """Normalise a real, finite, one-dimensional frequency array without mutation. - Parameters - ---------- - omega : array_like - Angular frequencies in rad/s, shape (n_omega,). - - Returns - ------- - numpy.ndarray - Finite real one-dimensional array with unchanged values. + - ``omega``: angular frequencies in rad/s, shape (n_omega,). """ if np.iscomplexobj(omega): raise MaterialError("angular frequencies must be real") @@ -1291,12 +1189,8 @@ def _pair_frequencies(omega: ArrayLike | float) -> Float[np.ndarray, " n_omega"] def _pair_reference(wavelength: float, band: tuple[float, float] | None) -> None: """Check the reference and the fixed wavelength band. - Parameters - ---------- - wavelength : float - Vacuum reference wavelength in m. - band : tuple of float or None - Fixed inclusive ascending wavelength band in m. + - ``wavelength``: vacuum reference wavelength in m. + - ``band``: fixed inclusive ascending wavelength band in m. """ if not math.isfinite(wavelength) or wavelength <= 0.0: raise MaterialError("reference wavelength must be finite and positive") @@ -1319,20 +1213,8 @@ def _finite_pair_phase( ) -> Float[np.ndarray, " n_omega"]: """Reject unrepresentable phases before forming a complex response. - Parameters - ---------- - phase : numpy.ndarray - Computed residual phase in rad, shape (n_omega,). - - Returns - ------- - numpy.ndarray - The finite phase with the same shape. - - Raises - ------ - MaterialError - If floating-point range was exceeded by the settings or frequencies. + - ``phase``: computed residual phase in rad, shape (n_omega,). + - Returns: the finite phase with the same shape. """ if not np.all(np.isfinite(phase)): raise MaterialError( @@ -1346,17 +1228,8 @@ def _pair_selected( ) -> Bool[np.ndarray, " n_omega"]: """Select positive frequencies in the fixed inclusive wavelength band. - Parameters - ---------- - omega : array_like - Angular frequencies in rad/s, shape (n_omega,). - band : tuple of float or None - Fixed inclusive ascending wavelength band in m. - - Returns - ------- - numpy.ndarray - Boolean selection mask, shape (n_omega,). + - ``omega``: angular frequencies in rad/s, shape (n_omega,). + - ``band``: fixed inclusive ascending wavelength band in m. """ selected = omega > 0.0 if band is not None: @@ -1370,17 +1243,9 @@ def _pair_phase_domain( ) -> Float[np.ndarray, " n_omega"]: """Require frequencies for which a transmitted phase or ray is requested. - Parameters - ---------- - omega : array_like - Angular frequencies in rad/s, shape (n_omega,). - band : tuple of float or None - Fixed inclusive ascending wavelength band in m. - - Returns - ------- - numpy.ndarray - Finite positive in-band angular frequencies, shape (n_omega,). + - ``omega``: angular frequencies in rad/s, shape (n_omega,). + - ``band``: fixed inclusive ascending wavelength band in m. + - Returns: finite positive in-band angular frequencies, shape (n_omega,). """ values = _pair_frequencies(omega) if not np.all(_pair_selected(values, band)): @@ -1391,12 +1256,7 @@ def _pair_phase_domain( def _grating_parameters(line_density: float, order: int) -> None: """Validate density in lines per metre and a signed integer diffraction order. - Parameters - ---------- - line_density : float - Positive grating density in lines/m. - order : int - Nonzero signed diffraction order. + - ``line_density``: positive grating density in lines/m. """ if not math.isfinite(line_density) or line_density <= 0.0: raise MaterialError("line_density must be finite and positive, in lines/m") @@ -1500,15 +1360,7 @@ def _cosine( ) -> Float[np.ndarray, " n_omega"]: """Select the forward, non-grazing diffraction branch. - Parameters - ---------- - omega : array_like - Angular frequencies in rad/s, shape (n_omega,). - - Returns - ------- - numpy.ndarray - Positive diffraction cosine, shape (n_omega,). + - ``omega``: angular frequencies in rad/s, shape (n_omega,). """ with np.errstate(over="ignore", divide="ignore"): sine = math.sin(self.incidence_angle) + ( @@ -1594,18 +1446,7 @@ def __call__(self, omega: ArrayLike | float) -> Complex[np.ndarray, " n_omega"]: def _prism_glass(medium: GlassIndex | str) -> GlassIndex: - """Resolve only the existing glass registry, never a gas or mixture. - - Parameters - ---------- - medium : GlassIndex or str - Glass model or registry name. - - Returns - ------- - GlassIndex - Resolved glass model. - """ + """Resolve only the existing glass registry, never a gas or mixture.""" return glass(medium) if isinstance(medium, str) else medium @@ -1614,17 +1455,7 @@ def _prism_index( ) -> Float[np.ndarray, " n_omega"]: """Require a finite refractive index above the ambient index of one. - Parameters - ---------- - medium : GlassIndex or str - Glass model or registry name. - omega : array_like - Angular frequencies in rad/s, shape (n_omega,). - - Returns - ------- - numpy.ndarray - Finite real index above one, shape (n_omega,). + - ``omega``: angular frequencies in rad/s, shape (n_omega,). """ if omega.size == 0: return np.empty(0, dtype=float) @@ -1776,22 +1607,7 @@ def _prism_refract( normal: Float[np.ndarray, " 2"], ratio: Float[np.ndarray, " n_omega"], ) -> Float[np.ndarray, "n_omega 2"]: - """Apply vector Snell on the intended forward face, refusing TIR and grazing. - - Parameters - ---------- - direction : numpy.ndarray - Unit ray directions, shape (n_omega, 2). - normal : numpy.ndarray - Unit normal directed into the next medium, shape (2,). - ratio : numpy.ndarray - Incident over transmitted index, shape (n_omega,). - - Returns - ------- - numpy.ndarray - Transmitted unit directions, shape (n_omega, 2). - """ + """Apply vector Snell on the intended forward face, refusing TIR and grazing.""" cosine = direction @ normal tangent = ratio[:, None] * (direction - cosine[:, None] * normal) discriminant = 1.0 - np.sum(tangent**2, axis=1) @@ -1814,21 +1630,10 @@ def _prism_intersect( ]: """Intersect a forward ray with the positive half of the intended prism face. - Parameters - ---------- - point : numpy.ndarray - Current ray positions in m, shape (n_omega, 2). - direction : numpy.ndarray - Unit ray directions, shape (n_omega, 2). - apex : numpy.ndarray - Target prism apex coordinates in m, shape (2,). - face : numpy.ndarray - Unit vector along the target face away from its apex. - - Returns - ------- - tuple of numpy.ndarray - Intersection coordinates, forward path lengths and signed face distances, in m. + - ``point``: current ray positions in m, shape (n_omega, 2). + - ``apex``: target prism apex coordinates in m, shape (2,). + - Returns: intersection coordinates, forward path lengths and signed face distances, + in m. """ offset = apex - point determinant = direction[:, 0] * face[1] - direction[:, 1] * face[0] @@ -1925,15 +1730,7 @@ def _exit_angle( ) -> Float[np.ndarray, " n_omega"]: """Return the signed external exit angle of the first prism. - Parameters - ---------- - index : numpy.ndarray - Real refractive index at each frequency, shape (n_omega,). - - Returns - ------- - numpy.ndarray - First-prism exit angles in rad, shape (n_omega,). + - Returns: First-prism exit angles in rad, shape (n_omega,). """ geometry = self.geometry sine = math.sin(geometry.incidence_angle) @@ -1950,10 +1747,7 @@ def _exit_angle( def _apex(self) -> Float[np.ndarray, " 2"]: """Return the second apex in the fixed laboratory coordinates. - Returns - ------- - numpy.ndarray - Second apex coordinates in m, shape (2,). + - Returns: second apex coordinates in m, shape (2,). """ angle = self.geometry.apex_angle / 2.0 - self._exit_reference return self.geometry.separation * np.array( diff --git a/src/solprop/polarisation.py b/src/solprop/polarisation.py index 52af2884..02c57ed7 100644 --- a/src/solprop/polarisation.py +++ b/src/solprop/polarisation.py @@ -67,10 +67,7 @@ class PolarisationEllipse: def _angle(value: float) -> None: """Reject a non-finite angle before evaluating trigonometric functions. - Parameters - ---------- - value : float - Angle in rad. + - ``value``: angle in rad. """ if not math.isfinite(value): raise PolarisationError(f"angle must be finite, got {value!r}") @@ -79,20 +76,7 @@ def _angle(value: float) -> None: def _complex_array( value: ArrayLike, shape: tuple[int, ...] ) -> Complex[np.ndarray, " *shape"]: - """Validate a finite Jones vector or matrix without broadcasting. - - Parameters - ---------- - value : array_like - Jones data in field units or dimensionless for a matrix. - shape : tuple of int - Required dimensions. - - Returns - ------- - numpy.ndarray - Finite complex128 data with the required shape. - """ + """Validate a finite Jones vector or matrix without broadcasting.""" array = np.asarray(value, dtype=np.complex128) if array.shape != shape or not np.all(np.isfinite(array)): raise PolarisationError( @@ -104,15 +88,7 @@ def _complex_array( def _stokes_vector(value: ArrayLike) -> Float[np.ndarray, " 4"]: """Validate the Stokes cone with arithmetic roundoff at the surface. - Parameters - ---------- - value : array_like - Stokes vector of shape ``(4,)`` in squared field units. - - Returns - ------- - numpy.ndarray - Finite real Stokes vector inside the physical cone. + - ``value``: stokes vector of shape ``(4,)`` in squared field units. """ array = np.asarray(value) if np.iscomplexobj(array) or array.shape != (4,) or not np.all(np.isfinite(array)): diff --git a/src/solprop/profiles.py b/src/solprop/profiles.py index b0cfad84..5cbfd3d6 100644 --- a/src/solprop/profiles.py +++ b/src/solprop/profiles.py @@ -416,20 +416,8 @@ def derivative(self, z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: def _as_positions(z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: """Validate a position argument and return it as a float array. - Parameters - ---------- - z : numpy.ndarray - Positions along the waveguide in m, shape ``(n_z,)``. - - Returns - ------- - numpy.ndarray - The same values as a finite float array. - - Raises - ------ - TranslationError - If any position is not finite. + - ``z``: positions along the waveguide in m, shape ``(n_z,)``. + - Returns: the same values as a finite float array. """ values = np.asarray(z, dtype=float) if not np.all(np.isfinite(values)): @@ -442,23 +430,8 @@ def _checked_edges( ) -> Float[np.ndarray, " n_breakpoints"]: """Validate a sequence of stage positions and return it as an array. - Parameters - ---------- - positions : array_like - Stage boundaries in m; at least two, finite and strictly increasing. - what : str - The name used in error messages. - - Returns - ------- - numpy.ndarray - The positions as a float array. - - Raises - ------ - TranslationError - If there are fewer than two, if any is not finite, or if they are not strictly - increasing. + - ``positions``: stage boundaries in m; at least two, finite and strictly + increasing. """ edges = np.asarray(positions, dtype=float) if edges.ndim != 1 or edges.size < 2: @@ -479,23 +452,8 @@ def _checked_pressures( ) -> Float[np.ndarray, " n_pressures"]: """Validate the stage pressures of a fill and return them as an array. - Parameters - ---------- - edges : numpy.ndarray - Stage boundaries in m, already validated. - pressures : array_like - Absolute pressure in bar at each boundary. - - Returns - ------- - numpy.ndarray - The pressures as a float array. - - Raises - ------ - TranslationError - If the lengths disagree, a pressure is negative or not finite, every pressure - is zero, or the highest is so small that its square underflows. + - ``edges``: stage boundaries in m, already validated. + - ``pressures``: absolute pressure in bar at each boundary. """ values = np.asarray(pressures, dtype=float) if values.shape != edges.shape: @@ -526,23 +484,8 @@ def _interval_knots( ) -> Float[np.ndarray, " n_knots"]: """Return the ends of an interval and the given positions strictly inside it. - Parameters - ---------- - start, end : float - The closed interval in m. - inner : numpy.ndarray - Candidate knots in m, any order. - - Returns - ------- - numpy.ndarray - ``start``, the candidates inside ``(start, end)`` sorted and unique, and - ``end``. - - Raises - ------ - TranslationError - If the interval is not finite or not increasing. + - ``start``, ``end``: the closed interval in m. + - ``inner``: candidate knots in m, any order. """ start, end = float(start), float(end) if not (math.isfinite(start) and math.isfinite(end) and end > start): @@ -561,17 +504,7 @@ def _monotone_pieces( ) -> PathVariation: """Return the exact record of a path monotone between its knots. - Parameters - ---------- - knots : numpy.ndarray - Piece edges in m. - first, last : numpy.ndarray - One-sided limits at each piece's ends. - - Returns - ------- - PathVariation - Monotone pieces whose variation is ``abs(last - first)``. + - ``knots``: piece edges in m. """ return PathVariation( knots, @@ -589,17 +522,7 @@ def _continuous_monotone( ) -> PathVariation: """Return the record of a continuous path monotone between the given knots. - Parameters - ---------- - path : ZProfile - The path, continuous on the interval. - knots : numpy.ndarray - Piece edges in m between which the path is monotone. - - Returns - ------- - PathVariation - The exact record, from the path's values at the knots. + - ``knots``: piece edges in m between which the path is monotone. """ values = np.asarray(path(knots), dtype=float) return _monotone_pieces(knots, values[:-1], values[1:]) @@ -608,18 +531,10 @@ def _continuous_monotone( def _padded(record: PathVariation, start: float, end: float) -> PathVariation: """Extend a record by constant pieces out to a wider interval. - Parameters - ---------- - record : PathVariation - The record over a subinterval of ``[start, end]``. - start, end : float - The wider interval in m. - - Returns - ------- - PathVariation - The record, with a constant piece before and after it where the interval - extends beyond it: the path is held at its end values there. + - ``record``: the record over a subinterval of ``[start, end]``. + - ``start``, ``end``: the wider interval in m. + - Returns: the record, with a constant piece before and after it where the interval + extends beyond it: the path is held at its end values there. """ pieces = [record] if start < record.knots[0]: @@ -655,22 +570,8 @@ def _mapped_variation( monotone the variation is at most the function's largest slope on the range times the inner variation. - Parameters - ---------- - record : PathVariation - The inner path's record. - function : collections.abc.Callable - The outer function, vectorised. - critical : collections.abc.Callable - ``critical(low, high)`` returns every point in ``(low, high)`` where the - function may turn; extra points only cost evaluations. - slope : collections.abc.Callable - ``slope(low, high)`` returns an upper bound of ``abs(function')`` on the range. - - Returns - ------- - PathVariation - The composite's record, on the same knots. + - ``critical``: ``critical(low, high)`` returns every point in ``(low, high)`` where + the function may turn; extra points only cost evaluations. """ pieces = record.first.size first = np.asarray(function(record.first), dtype=float) @@ -881,10 +782,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. """ pairs = zip(self.positions, self.values, strict=True) rows: list[tuple[str, object]] = [ @@ -1223,21 +1121,11 @@ def _linear_bore( ) -> Float[np.ndarray, " n_z"]: """Integrate ``a^-4`` exactly from `start` to `z` on a piece where ``a`` is linear. - Parameters - ---------- - start : numpy.ndarray - Left edge of each piece in m. - radius_start : numpy.ndarray - Radius at `start` in m, > 0. - z : numpy.ndarray - Upper limit of each integral in m, on the same piece. - radius_z : numpy.ndarray - Radius at `z` in m, > 0. - - Returns - ------- - numpy.ndarray - ``int_start^z a^-4 dz`` in m^-3. + - ``start``: left edge of each piece in m. + - ``radius_start``: radius at `start` in m, > 0. + - ``z``: upper limit of each integral in m, on the same piece. + - ``radius_z``: radius at `z` in m, > 0. + - Returns: ``int_start^z a^-4 dz`` in m^-3. """ # (a0^-3 - a^-3) / (3 a') rewritten with a' = (a - a0) / dz: the difference # cancels against the slope, leaving a form with no subtraction that is also right @@ -1301,25 +1189,10 @@ def build( ) -> "_BoreIntegral": """Cut the fill into pieces and integrate the bore weight over each. - Parameters - ---------- - radius : ZProfile or None - Core radius in m, positive over the fill; ``None`` for a uniform bore. - stage_edges : numpy.ndarray - Stage boundaries in m, validated and strictly increasing. - factor : ZProfile or None, optional - Flow weight multiplying ``a^-4``, positive over the fill. - - Returns - ------- - _BoreIntegral - The cumulative resistance. - - Raises - ------ - TranslationError - If the radius or the weight is not finite and positive over the fill, or - their product does not converge on a piece by the highest degree tried. + - ``radius``: core radius in m, positive over the fill; ``None`` for a uniform + bore. + - ``stage_edges``: stage boundaries in m, validated and strictly increasing. + - ``factor``: flow weight multiplying ``a^-4``, positive over the fill. """ start, end = float(stage_edges[0]), float(stage_edges[-1]) kinks = [stage_edges] @@ -1376,15 +1249,8 @@ def build( def weight(self, z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: """Return the integrand ``w a^-4`` at each position. - Parameters - ---------- - z : numpy.ndarray - Positions in m, inside the fill. - - Returns - ------- - numpy.ndarray - ``w(z) a(z)^-4``; ``a(z)^-4`` in m^-4 without a flow weight. + - ``z``: positions in m, inside the fill. + - Returns: ``w(z) a(z)^-4``; ``a(z)^-4`` in m^-4 without a flow weight. """ if self.factor is None: assert self.radius is not None @@ -1396,17 +1262,7 @@ def from_stage_start( ) -> Float[np.ndarray, " n_z"]: """Return ``int w a^-4 dz`` from the start of each position's stage. - Parameters - ---------- - z : numpy.ndarray - Positions in m, each inside its stage. - stage : numpy.ndarray - The stage index of each position. - - Returns - ------- - numpy.ndarray - The resistance from the stage start to each position. + - ``z``: positions in m, each inside its stage. """ piece = np.clip( np.searchsorted(self.edges, z, side="right") - 1, 0, self.offsets.size - 1 @@ -1432,17 +1288,8 @@ def stage_fraction( ) -> Float[np.ndarray, " n_z"]: """Return each position's share of its stage's resistance. - Parameters - ---------- - z : numpy.ndarray - Positions in m, each clamped into its stage. - stage : numpy.ndarray - The stage index of each position. - - Returns - ------- - numpy.ndarray - ``R(z_i, z) / R(z_i, z_{i+1})``, in [0, 1]. + - ``z``: positions in m, each clamped into its stage. + - Returns: ``R(z_i, z) / R(z_i, z_{i+1})``, in [0, 1]. """ return np.clip( self.from_stage_start(z, stage) / self.stage_totals[stage], 0.0, 1.0 @@ -1452,17 +1299,8 @@ def stage_fraction( def _checked_radius(radius: ZProfile, z: Float[np.ndarray, " n_z"]) -> None: """Refuse a radius that is not finite and positive at the given positions. - Parameters - ---------- - radius : ZProfile - Core radius in m. - z : numpy.ndarray - Positions in m. - - Raises - ------ - TranslationError - If any sampled radius is not finite or not positive. + - ``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): @@ -1477,32 +1315,17 @@ def _flow_weight( ) -> Callable[[Float[np.ndarray, " n_z"]], Float[np.ndarray, " n_z"]]: """Return the integrand ``w a^-4`` of the hydraulic resistance, checking both. - Parameters - ---------- - radius : ZProfile or None - Core radius in m; ``None`` for a uniform bore, whose ``a^-4`` cancels. - factor : ZProfile or None - Flow weight ``w``; ``None`` for none. - - Returns - ------- - callable - ``z -> w(z) a(z)^-4``, raising :class:`~solprop.exceptions.TranslationError` - where the radius or the weight is not finite and positive. + - ``radius``: core radius in m; ``None`` for a uniform bore, whose ``a^-4`` cancels. + - ``factor``: flow weight ``w``; ``None`` for none. + - Returns: ``z -> w(z) a(z)^-4``, raising + :class:`~solprop.exceptions.TranslationError` where the radius or the weight is + not finite and positive. """ def weight(z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: """Return the integrand at the given positions, refusing a bad path. - Parameters - ---------- - z : numpy.ndarray - Positions in m. - - Returns - ------- - numpy.ndarray - ``w(z) a(z)^-4``. + - ``z``: positions in m. """ result = np.ones_like(np.asarray(z, dtype=float)) if radius is not None: @@ -1531,27 +1354,9 @@ def _bore_series( ) -> Chebyshev: """Return the exact antiderivative of a converged interpolant of a bore weight. - Parameters - ---------- - weight : callable - The integrand ``w a^-4``, smooth and positive on the piece; it refuses a - radius or weight that is not. - left, right : float - The piece in m. - degrees : tuple of int - The Chebyshev degrees tried in turn. - subject : str - The radius's repr, or a description of the radius and weight, for the error. - - Returns - ------- - numpy.polynomial.Chebyshev - ``int_left^z w a^-4 dz`` as a Chebyshev series in ``z``, zero at `left`. - - Raises - ------ - TranslationError - If the interpolant has not converged by the highest degree tried. + - ``weight``: the integrand ``w a^-4``, smooth and positive on the piece; it refuses + a radius or weight that is not. + - ``left``, ``right``: the piece in m. """ for degree in degrees: interpolant = Chebyshev.interpolate(weight, degree, domain=[left, right]) @@ -1738,10 +1543,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. """ pairs = zip(self.positions, self.pressures, strict=True) rows: list[tuple[str, object]] = [ @@ -2024,15 +1826,8 @@ def variation(self, start: float, end: float) -> PathVariation: def _stage_index(self, z: Float[np.ndarray, " n_z"]) -> Int[np.ndarray, " n_z"]: """Return the index of the stage each position falls in. - Parameters - ---------- - z : numpy.ndarray - Positions in m, shape ``(n_z,)``. - - Returns - ------- - numpy.ndarray - Stage indices, clamped to the declared range. + - ``z``: positions in m, shape ``(n_z,)``. + - Returns: stage indices, clamped to the declared range. """ return np.asarray( np.clip( @@ -2110,18 +1905,7 @@ def derivative(self, z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: def _chebyshev_nodes(degree: int) -> Float[np.ndarray, " n_nodes"]: - """Return the Chebyshev points of the first kind on ``[-1, 1]``. - - Parameters - ---------- - degree : int - The interpolation degree; ``degree + 1`` points, none at an end. - - Returns - ------- - numpy.ndarray - The points, decreasing. - """ + """Return the Chebyshev points of the first kind on ``[-1, 1]``.""" return np.cos(np.pi * (np.arange(degree + 1) + 0.5) / (degree + 1)) @@ -2159,26 +1943,10 @@ def build( ) -> "_Compressibility": """Tabulate ``Z`` at Chebyshev nodes, raising each axis's degree to convergence. - Parameters - ---------- - density_at : callable - ``(pressure in bar, temperature in K) -> number density in m^-3``. - pressures : tuple of float - The pressure interval in bar, ``low < high``. - temperatures : tuple of float - The temperature interval in K, ``low < high``. - what : str - The gas, for the error. - - Returns - ------- - _Compressibility - The converged series. - - Raises - ------ - MaterialError - If an axis has not converged by the highest degree tried. + - ``density_at``: ``(pressure in bar, temperature in K) -> number density in + m^-3``. + - ``pressures``: the pressure interval in bar, ``low < high``. + - ``temperatures``: the temperature interval in K, ``low < high``. """ degrees = [_Z_DEGREES[0], _Z_DEGREES[0]] while True: @@ -2214,19 +1982,9 @@ def _fit( ) -> Float[np.ndarray, "n_p n_temperature"]: """Return the tensor Chebyshev coefficients of ``Z`` at the given degrees. - Parameters - ---------- - density_at : callable - ``(pressure in bar, temperature in K) -> number density in m^-3``. - pressures, temperatures : tuple of float - The intervals in bar and K. - degrees : sequence of int - The degree in pressure and in temperature. - - Returns - ------- - numpy.ndarray - Coefficients, shape ``(degrees[0] + 1, degrees[1] + 1)``. + - ``density_at``: ``(pressure in bar, temperature in K) -> number density in + m^-3``. + - ``pressures``, ``temperatures``: the intervals in bar and K. """ unit_p = _chebyshev_nodes(degrees[0]) unit_t = _chebyshev_nodes(degrees[1]) @@ -2258,17 +2016,9 @@ def _unit( ) -> tuple[Float[np.ndarray, " n_z"], Float[np.ndarray, " n_z"]]: """Map states onto the series' unit square. - Parameters - ---------- - pressure : numpy.ndarray - Pressures in bar. - temperature : numpy.ndarray - Temperatures in K. - - Returns - ------- - tuple of numpy.ndarray - The two coordinates on ``[-1, 1]``. + - ``pressure``: pressures in bar. + - ``temperature``: temperatures in K. + - Returns: the two coordinates on ``[-1, 1]``. """ (p0, p1), (t0, t1) = self.pressures, self.temperatures return (2.0 * pressure - (p0 + p1)) / (p1 - p0), ( @@ -2282,17 +2032,9 @@ def covers( ) -> Bool[np.ndarray, " n_z"]: """Return which states lie inside the tabulated rectangle. - Parameters - ---------- - pressure : numpy.ndarray - Pressures in bar. - temperature : numpy.ndarray - Temperatures in K. - - Returns - ------- - numpy.ndarray - ``True`` where the series interpolates. + - ``pressure``: pressures in bar. + - ``temperature``: temperatures in K. + - Returns: ``True`` where the series interpolates. """ (p0, p1), (t0, t1) = self.pressures, self.temperatures return ( @@ -2311,17 +2053,9 @@ def values( ]: """Return ``Z`` and its partial derivatives at each state. - Parameters - ---------- - pressure : numpy.ndarray - Pressures in bar, inside the table. - temperature : numpy.ndarray - Temperatures in K, inside the table. - - Returns - ------- - tuple of numpy.ndarray - ``Z``, ``dZ/dp`` in 1/bar and ``dZ/dT`` in 1/K. + - ``pressure``: pressures in bar, inside the table. + - ``temperature``: temperatures in K, inside the table. + - Returns: ``Z``, ``dZ/dp`` in 1/bar and ``dZ/dT`` in 1/K. """ chebyshev = np.polynomial.chebyshev x, y = self._unit(pressure, temperature) @@ -2464,12 +2198,7 @@ def __post_init__(self) -> None: def _tabulate(self, species: Gas | Species, temperature: ZProfile) -> None: """Tabulate the compressibility over the states a temperature path spans. - Parameters - ---------- - species : Gas or Species - The resolved species. - temperature : ZProfile - The varying temperature path in K. + - ``temperature``: the varying temperature path in K. """ probes = _probe_positions(self) pressures = np.maximum(self.pressure(probes), 0.0) @@ -2494,17 +2223,9 @@ def _tabulate(self, species: Gas | Species, temperature: ZProfile) -> None: def _density_at(self, pressure: float, temperature: float) -> float: """Return the equation of state's density at one state. - Parameters - ---------- - pressure : float - Pressure in bar, non-negative. - temperature : float - Temperature in K, > 0. - - Returns - ------- - float - Number density in m^-3. + - ``pressure``: pressure in bar, non-negative. + - ``temperature``: temperature in K, > 0. + - Returns: number density in m^-3. """ species = self.gas if isinstance(species, Species): @@ -2606,15 +2327,8 @@ def _from_pressure( ) -> Float[np.ndarray, " n_p"]: """Return the tabulated density at each pressure, at the one temperature. - Parameters - ---------- - pressure : numpy.ndarray - Pressures in bar; a negative one is held at zero. - - Returns - ------- - numpy.ndarray - Number density in m^-3, the arithmetic of :meth:`__call__`. + - ``pressure``: pressures in bar; a negative one is held at zero. + - Returns: number density in m^-3, the arithmetic of :meth:`__call__`. """ assert self._spline is not None pressures = np.maximum(np.asarray(pressure, dtype=float), 0.0) @@ -2693,17 +2407,10 @@ def variation(self, start: float, end: float) -> PathVariation: def _turning_pressures(self, low: float, high: float) -> Float[np.ndarray, " n"]: """Return the pressures in ``(low, high)`` where the tabulated density may turn. - Parameters - ---------- - low, high : float - Pressure range in bar. - - Returns - ------- - numpy.ndarray - The real roots of the spline's derivative there, and the table's ends - and zero pressure where they fall inside, at which the arithmetic - changes branch. + - ``low``, ``high``: pressure range in bar. + - Returns: the real roots of the spline's derivative there, and the table's ends + and zero pressure where they fall inside, at which the arithmetic changes + branch. """ spline = self._spline assert spline is not None @@ -2717,17 +2424,10 @@ def _turning_pressures(self, low: float, high: float) -> Float[np.ndarray, " n"] def _slope_bound(self, low: float, high: float) -> float: """Return the largest ``abs(d(rho)/dp)`` of the spline on a pressure range. - Parameters - ---------- - low, high : float - Pressure range in bar. - - Returns - ------- - float - The maximum over the range of the spline's piecewise quadratic - derivative, from its values at the range's ends, the spline knots and - the vertices inside, in m^-3 per bar. + - ``low``, ``high``: pressure range in bar. + - Returns: the maximum over the range of the spline's piecewise quadratic + derivative, from its values at the range's ends, the spline knots and the + vertices inside, in m^-3 per bar. """ spline = self._spline assert spline is not None @@ -2741,13 +2441,7 @@ def _slope_bound(self, low: float, high: float) -> float: return float(np.max(np.abs(slope(inside)))) def _warn_outside(self, outside: Bool[np.ndarray, " n_z"]) -> None: - """Log that some states lay outside the table built at construction. - - Parameters - ---------- - outside : numpy.ndarray - Which positions were evaluated directly. - """ + """Log that some states lay outside the table built at construction.""" if self._table is None: assert self._spline is not None extent = f"{self._spline.x[0]:.6g} to {self._spline.x[-1]:.6g} bar" @@ -2770,17 +2464,9 @@ def _along_temperature( ) -> tuple[Float[np.ndarray, " n_z"], Float[np.ndarray, " n_z"]]: """Return the density and its z derivative under a temperature path. - Parameters - ---------- - positions : numpy.ndarray - Positions in m, inside the span. - pressures : numpy.ndarray - The pressure in bar at each, non-negative. - - Returns - ------- - tuple of numpy.ndarray - ``rho`` in m^-3 and ``rho_p p' + rho_T T'`` in m^-4. + - ``positions``: positions in m, inside the span. + - ``pressures``: the pressure in bar at each, non-negative. + - Returns: ``rho`` in m^-3 and ``rho_p p' + rho_T T'`` in m^-4. """ table = self._table temperature = self.temperature @@ -2814,15 +2500,8 @@ def _covered( ) -> Bool[np.ndarray, " n_z"]: """Return which pressures lie within the range the spline was sampled on. - Parameters - ---------- - pressures : numpy.ndarray - Pressures in bar, shape ``(n_z,)``. - - Returns - ------- - numpy.ndarray - ``True`` where the spline interpolates rather than extrapolates. + - ``pressures``: pressures in bar, shape ``(n_z,)``. + - Returns: ``True`` where the spline interpolates rather than extrapolates. """ assert self._spline is not None x = self._spline.x @@ -2833,15 +2512,7 @@ def _within( ) -> Float[np.ndarray, " n_z"]: """Return the pressures clamped to the spline's sampled range. - Parameters - ---------- - pressures : numpy.ndarray - Pressures in bar, shape ``(n_z,)``. - - Returns - ------- - numpy.ndarray - The same values, held at the range's ends outside it. + - ``pressures``: pressures in bar, shape ``(n_z,)``. """ assert self._spline is not None x = self._spline.x @@ -2850,15 +2521,8 @@ def _within( def _density(self, pressure: float) -> float: """Return the equation of state's density at one pressure. - Parameters - ---------- - pressure : float - Pressure in bar, non-negative. - - Returns - ------- - float - Number density in m^-3. + - ``pressure``: pressure in bar, non-negative. + - Returns: number density in m^-3. """ species = self.gas assert not isinstance(self.temperature, ZProfile) @@ -2944,10 +2608,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. """ low, high = (float(v) for v in self.series.domain) return "SeriesOfPath: a Chebyshev series of a path", [ @@ -3051,37 +2712,15 @@ def variation(self, start: float, end: float) -> PathVariation: raise TranslationError("a series' variation needs the window [-1, 1]") bound = float(np.sum(np.abs(slope.coef))) - def critical(low: float, high: float) -> Float[np.ndarray, " n"]: - """Return the derivative's real roots strictly inside an interval. - - Parameters - ---------- - low, high : float - The interval's ends in the series' variable. - - Returns - ------- - numpy.ndarray - The roots inside it. - """ + def _critical(low: float, high: float) -> Float[np.ndarray, " n"]: + """Return the derivative's real roots strictly inside an interval.""" return real[(real > low) & (real < high)] - def series(values: Float[np.ndarray, " n"]) -> Float[np.ndarray, " n"]: - """Evaluate the series as real values. - - Parameters - ---------- - values : numpy.ndarray - Points in the series' variable. - - Returns - ------- - numpy.ndarray - The series there. - """ + def _series(values: Float[np.ndarray, " n"]) -> Float[np.ndarray, " n"]: + """Evaluate the series as real values.""" return np.asarray(self.series(values), dtype=float) - return _mapped_variation(record, series, critical, lambda _low, _high: bound) + return _mapped_variation(record, _series, _critical, lambda _low, _high: bound) def __call__(self, z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: """Return ``f(g(z))`` at each position. @@ -3119,17 +2758,9 @@ def derivative(self, z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: def _probe_positions(path: ZProfile) -> Float[np.ndarray, " n_probes"]: """Return a path's breakpoints and evenly spaced positions over its span. - Parameters - ---------- - path : ZProfile - Any path. - - Returns - ------- - numpy.ndarray - Sorted unique positions in m: the breakpoints, and - :data:`_TEMPERATURE_PROBES` positions over a finite span (its start alone - for an unbounded one). + - Returns: sorted unique positions in m: the breakpoints, and + :data:`_TEMPERATURE_PROBES` positions over a finite span (its start alone for an + unbounded one). """ start, end = path.z_span interior = ( @@ -3148,25 +2779,8 @@ def _checked_temperature(value: object, owner: str = "") -> float | ZProfile: A path that is constant is returned as the number it holds, so that every consumer takes its constant-temperature code, bit for bit. - Parameters - ---------- - value : object - A temperature in K, finite and > 0: a real number, or a ``ZProfile`` finite - and positive at its breakpoints and at evenly spaced probes over its span. - owner : str, optional - What carries the temperature, for the message. - - Returns - ------- - float or ZProfile - A Python number as given, a NumPy scalar as a float, a constant path as its - value, a varying path itself. - - Raises - ------ - MaterialError - If `value` is a boolean, neither a real number nor a ``ZProfile``, or not - finite and positive where it is sampled. + - ``value``: a temperature in K, finite and > 0: a real number, or a ``ZProfile`` + finite and positive at its breakpoints and at evenly spaced probes over its span. """ where = f" of {owner}" if owner else "" if isinstance(value, ZProfile): @@ -3205,17 +2819,7 @@ def _checked_temperature(value: object, owner: str = "") -> float | ZProfile: def _same_temperature(given: float | ZProfile, own: float | ZProfile) -> bool: """Return whether a declared temperature is a mixture's own. - Parameters - ---------- - given : float or ZProfile - A validated temperature in K (a constant path already its number). - own : float or ZProfile - The mixture's. - - Returns - ------- - bool - ``True`` for the same path object, or two equal numbers. + - ``given``: a validated temperature in K (a constant path already its number). """ if isinstance(given, ZProfile) or isinstance(own, ZProfile): return given is own @@ -3225,15 +2829,7 @@ def _same_temperature(given: float | ZProfile, own: float | ZProfile) -> bool: def _temperature_text(temperature: float | ZProfile) -> str: """Return a temperature for printing: ``'293.15 K'`` or the path's own ``repr``. - Parameters - ---------- - temperature : float or ZProfile - A temperature in K, or a path of it along z. - - Returns - ------- - str - The text. + - ``temperature``: a temperature in K, or a path of it along z. """ if isinstance(temperature, ZProfile): return f"{temperature!r} K" @@ -3245,17 +2841,9 @@ def _temperature_range( ) -> tuple[float, float]: """Return the lowest and highest temperature a path takes at given positions. - Parameters - ---------- - temperature : ZProfile - The temperature path in K. - positions : numpy.ndarray - Positions in m, the path's breakpoints among them. - - Returns - ------- - tuple of float - ``(lowest, highest)`` in K. + - ``temperature``: the temperature path in K. + - ``positions``: positions in m, the path's breakpoints among them. + - Returns: ``(lowest, highest)`` in K. """ values = np.asarray(temperature(positions), dtype=float) return float(np.min(values)), float(np.max(values)) @@ -3638,22 +3226,7 @@ def _declared( ) -> Float[np.ndarray, " n"]: """Validate one declared list of positions. - Parameters - ---------- - positions : array_like - Positions in m. - what : str - The name used in the error message. - - Returns - ------- - numpy.ndarray - The positions as a float array. - - Raises - ------ - TranslationError - If they are not strictly increasing inside the span. + - ``positions``: positions in m. """ start, end = self.span edges = np.asarray(positions, dtype=float) @@ -3852,35 +3425,15 @@ def as_profile( def _number_identity(value: float) -> str: - """Return the exact hexadecimal literal of a double. - - Parameters - ---------- - value : float - Any float, including an infinity. - - Returns - ------- - str - ``float.hex`` of the value: exact, round-trippable and independent of - ``numpy.set_printoptions``. - """ + """Return the exact hexadecimal literal of a double.""" return float(value).hex() def _array_identity(values: Float[np.ndarray, " n_values"]) -> dict[str, object]: """Return the exact identity of a float array, by value or by digest. - Parameters - ---------- - values : numpy.ndarray - Finite or infinite float values, shape ``(n_values,)``. - - Returns - ------- - dict - ``{'n': size, 'values': [hex, ...]}`` for a short array, otherwise - ``{'n': size, 'sha256': digest}`` of the contiguous float64 bytes. + - Returns: ``{'n': size, 'values': [hex, ...]}`` for a short array, otherwise + ``{'n': size, 'sha256': digest}`` of the contiguous float64 bytes. """ array = np.ascontiguousarray(values, dtype=np.float64) if array.size <= _IDENTITY_ARRAY_LIMIT: diff --git a/src/solprop/pulses.py b/src/solprop/pulses.py index 987d04eb..0ad2cde0 100644 --- a/src/solprop/pulses.py +++ b/src/solprop/pulses.py @@ -268,10 +268,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]] = [ ("waist", f"{engineering(self.waist, 'm')} (1/e^2 intensity radius)"), @@ -549,15 +546,7 @@ def incident( def _profile(self, radius: Float[np.ndarray, " n_r"]) -> Float[np.ndarray, " n_r"]: """Return the unit-peak field profile at the given radii. - Parameters - ---------- - radius : numpy.ndarray - Distances from the beam axis in m, shape ``(n_r,)``. - - Returns - ------- - numpy.ndarray - ``exp(-(r/waist)**(2 order))``, shape ``(n_r,)``. + - ``radius``: distances from the beam axis in m, shape ``(n_r,)``. """ limit = _GAUSS_EXPONENT_CLIP ** (1 / (2 * self.order)) scaled = np.minimum(radius, self.waist * limit) / self.waist @@ -666,10 +655,7 @@ def __repr__(self) -> str: def _describe(self) -> tuple[str, list[tuple[str, object]]]: """Return the title and one row per member. - Returns - ------- - tuple - The title and ``(key, value)`` rows. + - Returns: the title and ``(key, value)`` rows. """ return "SpatialPulseSum", [ (f"beam {index}", repr(member)) for index, member in enumerate(self.beams) @@ -751,29 +737,8 @@ def of( ) -> "_Aligner | None": """Return a beam's aligner, refusing what the launch cannot carry. - Parameters - ---------- - beam : SpatialPulse - The beam. - grid : solprop.grid.TimeGrid - The temporal grid. - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - The transverse grid. - medium : solprop.spatial.SpatialMedium - The launch medium. - at : float - Where the medium is evaluated, m. - - Returns - ------- - _Aligner or None - ``None`` for a beam neither displaced nor tilted. - - Raises - ------ - PulseError - On a radial grid, a y component on a line grid, a tilt outside the grid's - band, or a pulse-front delay longer than the time window. + - ``at``: where the medium is evaluated, m. + - Returns: ``None`` for a beam neither displaced nor tilted. """ if not beam.aligned: return None @@ -828,20 +793,7 @@ def apply( ) -> Complex[np.ndarray, "n_nodes n_pol n_omega"]: """Tilt, then displace, a launch spectrum; refuse one that reaches the edge. - Parameters - ---------- - field : numpy.ndarray - The Cartesian DFT spectrum, shape ``(n_nodes, n_pol, n_omega)``. - - Returns - ------- - numpy.ndarray - The moved spectrum. - - Raises - ------ - PulseError - If the moved beam is not negligible at the window's edges. + - ``field``: the Cartesian DFT spectrum, shape ``(n_nodes, n_pol, n_omega)``. """ space = self.space cube = field.reshape(space.n_y, space.n_x, *field.shape[1:]) @@ -885,17 +837,9 @@ def apply( def _edge(axis: Float[np.ndarray, " n"], radius: float) -> np.ndarray: """Return the samples of an axis at or beyond its window's half-width. - Parameters - ---------- - axis : numpy.ndarray - The centred axis, m. - radius : float - The requested half-width, m; the outermost samples when none reach it. - - Returns - ------- - numpy.ndarray - Boolean mask, shape ``(n,)``. + - ``axis``: the centred axis, m. + - ``radius``: the requested half-width, m; the outermost samples when none reach it. + - Returns: boolean mask, shape ``(n,)``. """ reach = np.abs(axis) return reach >= min(radius, float(np.max(reach))) @@ -927,15 +871,7 @@ class IncidentField: def _unit_jones(jones: Sequence[complex]) -> Complex[np.ndarray, " n_pol"]: """Return a Jones vector scaled to unit squared magnitude. - Parameters - ---------- - jones : sequence of complex - One or two finite components, not all zero. - - Returns - ------- - numpy.ndarray - The vector divided by its norm, complex128, shape ``(n_pol,)``. + - ``jones``: one or two finite components, not all zero. """ vector = np.asarray(jones, dtype=np.complex128) # Dividing by the largest part first keeps the norm from overflowing. @@ -1052,10 +988,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 "PlanarPulse: an astigmatic Gaussian beam into a planar guide", [ ("waist in x", f"{engineering(self.waist_x, 'm')} (1/e field radius)"), @@ -1186,25 +1119,9 @@ def _incident_admittance( # noqa: PLR0917 - called positionally average. The integrand is analytic away from cutoff, where its weight is negligible for any beam the grid resolves. - Parameters - ---------- - grid : solprop.grid.TimeGrid - Temporal grid. - space : solprop.modes.slab.PlanarGrid - The y axis. - medium : solprop.spatial.SpatialMedium - The fill medium. - z : float - Position of the entrance plane in m. - side : {'before', 'after'} or None - Side of a declared index jump at ``z``. - waist_x : float - The 1/e field radius in x, m. - - Returns - ------- - numpy.ndarray - Complex dimensionless admittance, shape ``(n_y, n_omega)``. + - ``space``: the y axis. + - ``z``: position of the entrance plane in m. + - ``waist_x``: the 1/e field radius in x, m. """ frame = Frame(grid.omega0, subtract_beta0=False, beta0=0.0, beta1=0.0) material, _, _, _ = SpatialDispersion(medium, grid, frame).shared_values( @@ -1232,20 +1149,7 @@ def _incident_admittance( # noqa: PLR0917 - called positionally def _check_positive(name: str, value: float) -> None: - """Reject a quantity that is not finite and strictly positive. - - Parameters - ---------- - name : str - Name of the quantity, used in the message. - value : float - Value to check. - - Raises - ------ - PulseError - If the value is not a finite positive number. - """ + """Reject a quantity that is not finite and strictly positive.""" if not math.isfinite(value) or value <= 0.0: msg = f"{name} must be a finite positive number, got {value!r}" raise PulseError(msg, parameter=name) @@ -1256,22 +1160,9 @@ def _scaling(energy: float | None, peak_power: float | None) -> tuple[float, _Qu Every pulse constructor shares this one definition and one message. - Parameters - ---------- - energy : float or None - Requested pulse energy, J. - peak_power : float or None - Requested peak power, W. - - Returns - ------- - tuple - The target value and the name of the quantity it fixes. - - Raises - ------ - PulseError - If both or neither is given, or the given one is not finite and positive. + - ``energy``: requested pulse energy, J. + - ``peak_power``: requested peak power, W. + - Returns: the target value and the name of the quantity it fixes. """ if energy is not None: if peak_power is not None: @@ -1308,29 +1199,7 @@ def _hard_masked( and an energy-normalised launch requires it unoccupied (SEP 34 section 4.3): content beyond the rounding of the beam's field norm there is refused. - Parameters - ---------- - field : numpy.ndarray - The beam spectrum, ``(n_modes, n_pol, n_omega)``. - live : numpy.ndarray - Bins with nonzero admittance, ``(n_modes, n_omega)``. - weights : numpy.ndarray - Transverse Parseval weights, ``(n_modes,)``. - grid : solprop.grid.TimeGrid - Temporal grid. - column_norms : numpy.ndarray - Filled with each column's ``sum |field|^2`` over the polarisations before - the mask, ``(n_modes, n_omega)``. - - Returns - ------- - numpy.ndarray - The masked beam: `field`, masked in place. - - Raises - ------ - PulseError - If the nonpropagating share of the field norm exceeds its rounding budget. + - ``live``: bins with nonzero admittance, ``(n_modes, n_omega)``. """ band = grid.active_mask & (grid.omega > 0) outside = band[None, :] & ~live @@ -1338,15 +1207,7 @@ def _hard_masked( def shares(rows: slice) -> tuple[float, float]: """Return a block's norm in the columns that don't propagate, and in all. - Parameters - ---------- - rows : slice - The block of the first axis. - - Returns - ------- - tuple of float - The block's two sums. + - ``rows``: the block of the first axis. """ squares = np.sum(np.abs(field[rows]) ** 2, axis=1) column_norms[rows] = squares @@ -1356,10 +1217,7 @@ def shares(rows: slice) -> tuple[float, float]: def mask(rows: slice) -> None: """Zero one block's dead bins. - Parameters - ---------- - rows : slice - The block of the first axis. + - ``rows``: the block of the first axis. """ field[rows] *= live[rows, None, :] @@ -1387,31 +1245,14 @@ def _separable_field( ) -> np.ndarray: """Return ``spectrum * jones * temporal`` in a new array, its rows in threads. - Parameters - ---------- - spectrum : numpy.ndarray - The beam's transverse spectrum, ``(n_modes,)``. - jones : numpy.ndarray - Its polarisation, ``(n_pol,)``. - temporal : numpy.ndarray - Its temporal spectrum, ``(1, 1, n_omega)``. - n_omega : int - The bins. - - Returns - ------- - numpy.ndarray - The field, ``(n_modes, n_pol, n_omega)``. + - ``temporal``: its temporal spectrum, ``(1, 1, n_omega)``. """ field = np.empty((spectrum.size, jones.size, n_omega), dtype=np.complex128) def work(rows: slice) -> None: """Form one block, whose first touch is then this thread's. - Parameters - ---------- - rows : slice - The block of the first axis. + - ``rows``: the block of the first axis. """ field[rows] = spectrum[rows, None, None] * jones[None, :, None] * temporal @@ -1422,22 +1263,14 @@ def work(rows: slice) -> None: def _add_rows(total: np.ndarray, part: np.ndarray | None) -> None: """Add `part` to `total` in place, row by row, in threads. - Parameters - ---------- - total : numpy.ndarray - The sum so far. - part : numpy.ndarray or None - What to add, of the same shape; ``None`` adds zero, which leaves every - number as it is but makes a negative zero positive. + - ``part``: what to add, of the same shape; ``None`` adds zero, which leaves every + number as it is but makes a negative zero positive. """ def work(rows: slice) -> None: """Add one block. - Parameters - ---------- - rows : slice - The block of the first axis. + - ``rows``: the block of the first axis. """ total[rows] += 0.0 if part is None else part[rows] @@ -1445,18 +1278,7 @@ def work(rows: slice) -> None: def _members(pulse: Pulse) -> list[Pulse]: - """Return the members of a pulse sum, flattened, or the pulse itself. - - Parameters - ---------- - pulse : Pulse - A temporal pulse or a :class:`PulseSum`. - - Returns - ------- - list of Pulse - Each member carries its own requested energy or peak power. - """ + """Return the members of a pulse sum, flattened, or the pulse itself.""" if isinstance(pulse, PulseSum): return [leaf for member in pulse.pulses for leaf in _members(member)] return [pulse] @@ -1467,20 +1289,9 @@ def _target( ) -> tuple[_Quantity, float]: """Return which quantity a temporal pulse fixes and its requested value. - Parameters - ---------- - pulse : Pulse - One temporal pulse. - temporal : numpy.ndarray - Its built spectrum, shape ``(1, 1, n_omega)``. - grid : solprop.grid.TimeGrid - Temporal grid. - - Returns - ------- - tuple - ``("peak_power", W)`` for a pulse given a peak power, otherwise - ``("energy", J)``: the energy its built spectrum carries. + - ``temporal``: its built spectrum, shape ``(1, 1, n_omega)``. + - Returns: ``("peak_power", W)`` for a pulse given a peak power, otherwise + ``("energy", J)``: the energy its built spectrum carries. """ peak = getattr(pulse, "peak_power", None) if isinstance(pulse, ShapedPulse) and peak is not None: @@ -1500,32 +1311,10 @@ def _normalising_factor( ) -> float: """Return the factor giving a beam its requested physical energy or peak power. - Parameters - ---------- - pulse : Pulse - The temporal pulse fixing the request. - temporal : numpy.ndarray - Its built spectrum, shape ``(1, 1, n_omega)``. - beam : numpy.ndarray - The unnormalised beam's column spectra, ``(n_modes, n_pol, n_omega)``. - plane : _Plane - Its measure at the launch plane. - grid : solprop.grid.TimeGrid - Temporal grid. - column_norms : numpy.ndarray or None, optional - Each column's ``sum |beam|^2`` over the polarisations, ``(n_modes, - n_omega)``, where the caller has it: wherever it differs from the beam's own, - the admittance must be zero. - - Returns - ------- - float - The amplitude factor. - - Raises - ------ - PulseError - If the beam carries no positive, finite forward flux. + - ``temporal``: its built spectrum, shape ``(1, 1, n_omega)``. + - ``column_norms``: each column's ``sum |beam|^2`` over the polarisations, + ``(n_modes, n_omega)``, where the caller has it: wherever it differs from the + beam's own, the admittance must be zero. """ quantity, target = _target(pulse, temporal, grid) if quantity == "energy": @@ -1535,10 +1324,7 @@ def _normalising_factor( def work(rows: slice) -> None: """Form one block of the flux's terms. - Parameters - ---------- - rows : slice - The block of the first axis. + - ``rows``: the block of the first axis. """ squares = ( np.sum(np.abs(beam[rows]) ** 2, axis=1) @@ -1585,27 +1371,8 @@ def _normalised( # noqa: PLR0917 - called positionally ) -> np.ndarray: """Scale a spatial beam in place to its requested physical energy or peak power. - Parameters - ---------- - pulse : Pulse - The temporal pulse fixing the request. - temporal : numpy.ndarray - Its built spectrum, shape ``(1, 1, n_omega)``. - field : numpy.ndarray - The unnormalised beam spectrum, ``(n_modes, n_pol, n_omega)``, scaled in place. - flux : solprop.spatial.SpatialFlux - The launch plane's measure. - admittance : numpy.ndarray - Its admittance, ``(n_modes, n_omega)``. - grid : solprop.grid.TimeGrid - Temporal grid. - column_norms : numpy.ndarray or None, optional - Passed to :func:`_normalising_factor`. - - Returns - ------- - numpy.ndarray - The normalised beam. + - ``temporal``: its built spectrum, shape ``(1, 1, n_omega)``. + - Returns: the normalised beam. """ plane = _Plane( weights=flux.space.spectral_weights, @@ -1619,10 +1386,7 @@ def _normalised( # noqa: PLR0917 - called positionally def scale(rows: slice) -> None: """Scale one block. - Parameters - ---------- - rows : slice - The block of the first axis. + - ``rows``: the block of the first axis. """ field[rows] *= factor @@ -1633,21 +1397,7 @@ def scale(rows: slice) -> None: def _frozen_phase(phase: Sequence[float] | np.ndarray) -> Float[np.ndarray, " n_deg"]: """Return the Taylor coefficients as a validated read-only array. - Parameters - ---------- - phase : array_like - Spectral phase Taylor coefficients about the pulse's centre frequency: radians - for the coefficient of order 0 and ``s**n`` for order ``n`` thereafter. - - Returns - ------- - numpy.ndarray - A read-only copy, shape ``(n_deg,)``. - - Raises - ------ - PulseError - If any coefficient is not finite. + - Returns: a read-only copy, shape ``(n_deg,)``. """ coefficients = np.array(phase, dtype=np.float64).reshape(-1) if not np.all(np.isfinite(coefficients)): @@ -1663,17 +1413,7 @@ def _check_delay(grid: TimeGrid, t0: float) -> None: The delay is applied as a spectral phase, so a value past the half-window wraps the pulse round to the other end rather than moving it out of the way. - Parameters - ---------- - grid : solprop.grid.TimeGrid - Grid the pulse is built on. - t0 : float - Requested delay, s. - - Raises - ------ - PulseError - If ``t0`` is not finite, or reaches the edge of the window. + - ``t0``: requested delay, s. """ half = 0.5 * grid.n_t * grid.dt if not math.isfinite(t0) or abs(t0) >= half: @@ -1703,21 +1443,9 @@ def _phase_factor( step. A Taylor phase and a pure delay both commute with the half-window origin shift, so they are applied to the stored spectrum directly (conventions.md §2). - Parameters - ---------- - grid : solprop.grid.TimeGrid - Grid supplying the frequency axis. - phase : numpy.ndarray - Taylor coefficients, shape ``(n_deg,)``, in ``s**n`` for order ``n``. - t0 : float - Delay, s, positive for later arrival. - omega_ref : float - Frequency the Taylor expansion is about, rad/s. - - Returns - ------- - numpy.ndarray - Unit-modulus factor, shape ``(n_omega,)``, to multiply a stored spectrum by. + - ``grid``: grid supplying the frequency axis. + - ``t0``: delay, s, positive for later arrival. + - ``omega_ref``: frequency the Taylor expansion is about, rad/s. """ optics_phase = taylor_phase(grid.omega, phase, omega_ref, t0) return fourier.to_internal(np.exp(1j * optics_phase)) @@ -1726,19 +1454,9 @@ def _phase_factor( def _analytic( spectrum: Complex[np.ndarray, " n_omega"], grid: TimeGrid ) -> Complex[np.ndarray, " n_t"]: - """Return the analytic signal of a stored spectrum, whose modulus squared is power. - - Parameters - ---------- - spectrum : numpy.ndarray - Stored spectrum, shape ``(n_omega,)``. - grid : solprop.grid.TimeGrid - Grid the spectrum belongs to. + """Return the analytic signal of a stored spectrum, in sqrt(W). - Returns - ------- - numpy.ndarray - Analytic signal, shape ``(n_t,)``, in the square root of watts. + Its modulus squared is the power. """ samples = fourier.inverse(spectrum, kind=grid.kind) if grid.kind == "envelope": @@ -1751,20 +1469,8 @@ def _realised( ) -> float: """Return the value a spectrum already has of the quantity being scaled. - Parameters - ---------- - spectrum : numpy.ndarray - Stored spectrum, shape ``(n_omega,)``. - grid : solprop.grid.TimeGrid - Grid the spectrum belongs to. - quantity : {'energy', 'peak_power', 'average_power'} - Which quantity to measure. - - Returns - ------- - float - The energy in J, or the peak or mean power in W. The mean is taken over the flat - part of the temporal window, where the collar is exactly one. + - Returns: the energy in J, or the peak or mean power in W. The mean is taken over + the flat part of the temporal window, where the collar is exactly one. """ if quantity == "energy": return float(fourier.energy_omega(spectrum, grid.dt, kind=grid.kind)) @@ -1782,26 +1488,7 @@ def _rescale( ) -> Complex[np.ndarray, " n_omega"]: """Scale a spectrum so that the named quantity takes the requested value. - Parameters - ---------- - spectrum : numpy.ndarray - Stored spectrum, shape ``(n_omega,)``. - grid : solprop.grid.TimeGrid - Grid the spectrum belongs to. - target : float - Requested value, J for an energy and W for a power. - quantity : {'energy', 'peak_power', 'average_power'} - Which quantity ``target`` fixes. - - Returns - ------- - numpy.ndarray - The scaled spectrum, shape ``(n_omega,)``. - - Raises - ------ - PulseError - If the spectrum carries nothing to scale, so no factor exists. + - ``target``: requested value, J for an energy and W for a power. """ realised = _realised(spectrum, grid, quantity) if realised <= 0.0: @@ -1819,15 +1506,8 @@ def _canonical( ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: """Return a single-mode spectrum in the canonical kernel layout. - Parameters - ---------- - spectrum : numpy.ndarray - Stored spectrum, shape ``(n_omega,)``. - - Returns - ------- - numpy.ndarray - The same spectrum, shape ``(1, 1, n_omega)``, complex128 and C-contiguous. + - Returns: the same spectrum, shape ``(1, 1, n_omega)``, complex128 and + C-contiguous. """ return np.ascontiguousarray(spectrum, dtype=np.complex128)[np.newaxis, np.newaxis] @@ -1883,20 +1563,14 @@ def __post_init__(self) -> None: def _shape_fields(self) -> list[tuple[str, object]]: """Return the fields a subclass adds, for printing. - Returns - ------- - list of tuple - ``(key, value)`` pairs describing the shape: its duration, its order. + - Returns: ``(key, value)`` pairs describing the shape: its duration, its order. """ return [] def _scale(self) -> tuple[str, str]: """Return which of energy and peak power scales the pulse, for printing. - Returns - ------- - tuple of str - The key and the formatted value. + - Returns: the key and the formatted value. """ if self.energy is not None: return "energy", engineering(self.energy, "J") @@ -1924,10 +1598,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 type(self).__name__, [ ("wavelength", engineering(self.wavelength, "m")), @@ -2083,10 +1754,7 @@ def __repr__(self) -> str: def _describe(self) -> tuple[str, list[tuple[str, object]]]: """Return the title and one row per member. - Returns - ------- - tuple - The title and ``(key, value)`` rows. + - Returns: the title and ``(key, value)`` rows. """ return "PulseSum", [ (f"pulse {index}", repr(member)) for index, member in enumerate(self.pulses) @@ -2124,23 +1792,7 @@ def build(self, grid: TimeGrid) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: def _power_envelope(pulse: ShapedPulse, grid: TimeGrid) -> Float[np.ndarray, " n_t"]: """Evaluate a pulse's envelope on the grid and check that it is usable. - Parameters - ---------- - pulse : ShapedPulse - Pulse whose :meth:`ShapedPulse.intensity` is evaluated. - grid : solprop.grid.TimeGrid - Grid supplying the time axis. - - Returns - ------- - numpy.ndarray - The power envelope, shape ``(n_t,)``. - - Raises - ------ - PulseError - If the envelope has the wrong length, is not finite, is negative anywhere, or is - zero at every sample. + - ``grid``: grid supplying the time axis. """ values = np.asarray(pulse.intensity(grid.t), dtype=np.float64) if values.shape != grid.t.shape: @@ -2222,10 +1874,7 @@ def __post_init__(self) -> None: def _shape_fields(self) -> list[tuple[str, object]]: """Return the duration, and the order when it is not the plain Gaussian's. - Returns - ------- - list of tuple - ``(key, value)`` pairs. + - Returns: ``(key, value)`` pairs. """ fields: list[tuple[str, object]] = [("fwhm", engineering(self.fwhm, "s"))] if self.m != 1: @@ -2316,10 +1965,7 @@ def __post_init__(self) -> None: def _shape_fields(self) -> list[tuple[str, object]]: """Return whichever of the two durations was given. - Returns - ------- - list of tuple - ``(key, value)`` pairs. + - Returns: ``(key, value)`` pairs. """ if self.fwhm is not None: return [("fwhm", engineering(self.fwhm, "s"))] @@ -2354,22 +2000,9 @@ def intensity(self, t: Float[np.ndarray, " n_t"]) -> Float[np.ndarray, " n_t"]: def _sech_width(fwhm: float | None, width: float | None) -> float: """Return the natural width of a ``sech**2`` pulse from whichever duration is given. - Parameters - ---------- - fwhm : float or None - Intensity full width at half maximum, s. - width : float or None - Natural width, s. - - Returns - ------- - float - The natural width, s. - - Raises - ------ - PulseError - If both or neither is given, or the given one is not finite and positive. + - ``fwhm``: intensity full width at half maximum, s. + - ``width``: natural width, s. + - Returns: the natural width, s. """ if width is not None: if fwhm is not None: @@ -2426,10 +2059,7 @@ class CustomPulse(ShapedPulse): def _shape_fields(self) -> list[tuple[str, object]]: """Return the name of the shape function. - Returns - ------- - list of tuple - ``(key, value)`` pairs. + - Returns: ``(key, value)`` pairs. """ return [("shape_fn", getattr(self.shape_fn, "__name__", "callable"))] @@ -2473,22 +2103,8 @@ def _checked_spectral_phase( disagreement keeps a ``dataclasses.replace`` of ``spectrum`` from silently keeping the old phase. - Parameters - ---------- - spectral_phase : numpy.ndarray - The given phase, rad. - spectrum : numpy.ndarray - The complex spectrum it belongs to, shape ``(n_data,)``. - - Returns - ------- - numpy.ndarray - The phase as a read-only float array, shape ``(n_data,)``. - - Raises - ------ - PulseError - If the phase differs from the spectrum in length, or is not its phase. + - ``spectral_phase``: the given phase, rad. + - Returns: the phase as a read-only float array, shape ``(n_data,)``. """ angle = np.array(spectral_phase, dtype=np.float64).reshape(-1) if angle.size != spectrum.size: @@ -2648,10 +2264,7 @@ def reference_wavelength(self) -> float: def _reference_frequency(self) -> float: """Return the frequency the Taylor phase is expanded about, rad/s. - Returns - ------- - float - The frequency of ``wavelength``, else the weighted mean of ``omega``. + - Returns: the frequency of ``wavelength``, else the weighted mean of ``omega``. """ if self.wavelength is not None: return 2.0 * np.pi * C / self.wavelength @@ -2672,23 +2285,14 @@ def __repr__(self) -> str: ) def _data_text(self) -> str: - """Return the extent of the measured spectrum, for printing. - - Returns - ------- - str - The number of samples and the wavelengths they span. - """ + """Return the extent of the measured spectrum, for printing.""" wavelengths = 2.0 * np.pi * C / np.asarray(self.omega, dtype=float) return summarise(wavelengths, "m") 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. """ reference = ( "the data's centre" @@ -2866,22 +2470,10 @@ def _record_spectrum( The chirp-z transform evaluates the sum in ``O((n_data + n_wanted) log)`` operations for any two spacings, where an FFT needs them to be commensurate. - Parameters - ---------- - t : numpy.ndarray - Uniform time axis, s, shape ``(n_data,)``. - values : numpy.ndarray - Samples on ``t``, shape ``(n_data,)``. - omega : numpy.ndarray - The frequencies wanted, rad/s, shape ``(n_wanted,)``, in any order, each a whole - number of `d_omega` from the others. - d_omega : float - Spacing of the axis `omega` belongs to, rad/s. - - Returns - ------- - numpy.ndarray - The sum at each frequency, shape ``(n_wanted,)``. + - ``t``: uniform time axis, s, shape ``(n_data,)``. + - ``omega``: the frequencies wanted, rad/s, shape ``(n_wanted,)``, in any order, + each a whole number of `d_omega` from the others. + - ``d_omega``: spacing of the axis `omega` belongs to, rad/s. """ # scipy.signal takes half a second to import: only this function needs it. from scipy.signal import czt # noqa: PLC0415 @@ -3018,15 +2610,8 @@ def __post_init__(self) -> None: def _spectral_centre(self) -> float: """Return the spectral centre of mass of the record, rad/s. - Returns - ------- - float - The mean of the positive frequencies of the record's DFT, by its power. - - Raises - ------ - PulseError - If the record has nothing at positive frequencies. + - Returns: the mean of the positive frequencies of the record's DFT, by its + power. """ dt = float(self.t[-1] - self.t[0]) / (self.t.size - 1) omega = 2.0 * np.pi * np.fft.fftfreq(self.t.size, dt) @@ -3054,10 +2639,8 @@ def reference_wavelength(self) -> float: def _reference_frequency(self) -> float: """Return the frequency the Taylor phase is expanded about, rad/s. - Returns - ------- - float - The frequency of ``wavelength``, else the record's spectral centre of mass. + - Returns: the frequency of ``wavelength``, else the record's spectral centre of + mass. """ if self.wavelength is not None: return 2.0 * np.pi * C / self.wavelength @@ -3077,22 +2660,13 @@ def __repr__(self) -> str: ) def _data_text(self) -> str: - """Return the extent of the measured record, for printing. - - Returns - ------- - str - The number of samples and the times they span. - """ + """Return the extent of the measured record, for printing.""" return summarise(np.asarray(self.t, dtype=float), "s") 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. """ reference = ( "the record's centre" @@ -3109,15 +2683,7 @@ def _describe(self) -> tuple[str, list[tuple[str, object]]]: def _warn_if_folded( self, values: Inexact[np.ndarray, " n_data"], grid: TimeGrid ) -> None: - """Warn when enough of the record lies outside the grid's window to matter. - - Parameters - ---------- - values : numpy.ndarray - The record as it is transformed, shape ``(n_data,)``. - grid : solprop.grid.TimeGrid - Grid to build on. - """ + """Warn when enough of the record lies outside the grid's window to matter.""" arrival = self.t + self.t0 half = 0.5 * grid.n_t * grid.dt power = np.abs(values) ** 2 @@ -3240,15 +2806,7 @@ def build(self, grid: TimeGrid) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: def _is_number(text: str) -> bool: """Return whether a field of a text table reads as a number. - Parameters - ---------- - text : str - The field. - - Returns - ------- - bool - True if ``float(text)`` succeeds. + - Returns: True if ``float(text)`` succeeds. """ try: float(text) @@ -3266,21 +2824,7 @@ def _read_table( holding a comma is split on commas, any other on whitespace. The first line that is not a comment may be a header, and is skipped when none of its fields is a number. - Parameters - ---------- - path : str, os.PathLike or io.TextIOBase - The file, or an open text stream, which is read from where it stands. - - Returns - ------- - numpy.ndarray - The table, shape ``(n_rows, n_columns)``. - - Raises - ------ - PulseError - If there is no file at ``path``, a line after the header is not a row of - numbers, the rows differ in length, or there is no row at all. + - ``path``: the file, or an open text stream, which is read from where it stands. """ if isinstance(path, io.TextIOBase): text, name = path.read(), "the stream" @@ -3328,25 +2872,9 @@ def _chosen_columns( ) -> tuple[int, ...]: """Return the zero-based indices of the axis, power and optional phase columns. - Parameters - ---------- - n_columns : int - Number of columns in the file. - columns : sequence or None - The indices asked for, two or three integers; ``None`` takes the first three - columns, or the first two of a two-column file. Anything else is refused here, - because the public annotation is not enforced at runtime. - - Returns - ------- - tuple of int - Two or three distinct indices of columns the file has. - - Raises - ------ - PulseError - If the file has fewer than two columns, or ``columns`` is not two or three - distinct indices of columns the file has. + - ``columns``: the indices asked for, two or three integers; ``None`` takes the + first three columns, or the first two of a two-column file. Anything else is + refused here, because the public annotation is not enforced at runtime. """ if columns is None: if n_columns < 2: @@ -3385,24 +2913,11 @@ def _chosen_columns( def _axis_factor(axis: str, axis_unit: str | None) -> float: """Return the factor that takes the axis column to rad/s or to metres. - Parameters - ---------- - axis : str - What the axis column holds, ``"frequency"`` or ``"wavelength"``; it is - checked here because the public annotation is not enforced at runtime. - axis_unit : str or None - Its unit; ``None`` is the axis's SI unit, Hz or m. - - Returns - ------- - float - The angular frequency in rad/s of one unit of a frequency column, or the - length in metres of one unit of a wavelength column. - - Raises - ------ - PulseError - If ``axis`` is not one of the two, or ``axis_unit`` is not one of its units. + - ``axis``: what the axis column holds, ``"frequency"`` or ``"wavelength"``; it is + checked here because the public annotation is not enforced at runtime. + - ``axis_unit``: its unit; ``None`` is the axis's SI unit, Hz or m. + - Returns: the angular frequency in rad/s of one unit of a frequency column, or the + length in metres of one unit of a wavelength column. """ _check_choice("axis", axis, ("frequency", "wavelength")) units, default = ( @@ -3418,22 +2933,7 @@ def _axis_factor(axis: str, axis_unit: str | None) -> float: def _check_choice(name: str, value: str, choices: Sequence[str]) -> None: - """Reject a keyword that is not one of its documented values. - - Parameters - ---------- - name : str - Name of the keyword, used in the message. - value : str - Value given. - choices : sequence of str - The values it may take. - - Raises - ------ - PulseError - If ``value`` is not among ``choices``. - """ + """Reject a keyword that is not one of its documented values.""" if value not in choices: allowed = " or ".join(repr(choice) for choice in choices) msg = f"{name} must be {allowed}, got {value!r}" @@ -3448,19 +2948,11 @@ def _undersampled_share( Unwrapping cannot tell a step near ``+pi`` from one near ``-pi``, so the first such step on each side of the peak, counting outward, is where a wrong turn may start. - Parameters - ---------- - steps : numpy.ndarray - Wrapped phase steps between neighbouring significant samples, rad, shape - ``(n_significant - 1,)``. - power : numpy.ndarray - Power of the significant samples, shape ``(n_significant,)``, positive. - - Returns - ------- - float - The power of the samples beyond the innermost near-pi step on either side of - the peak, as a share of the whole; zero if there is no such step. + - ``steps``: wrapped phase steps between neighbouring significant samples, rad, + shape ``(n_significant - 1,)``. + - ``power``: power of the significant samples, shape ``(n_significant,)``, positive. + - Returns: the power of the samples beyond the innermost near-pi step on either side + of the peak, as a share of the whole; zero if there is no such step. """ peak = int(np.argmax(power)) near = np.abs(steps) > _NEAR_PI @@ -3488,29 +2980,12 @@ def _unwrapped_above_floor( samples the phase is interpolated linearly across any gap, and beyond the first and last it is held at their values. - Parameters - ---------- - omega : numpy.ndarray - Angular frequency, rad/s, shape ``(n_data,)``, increasing. - phase : numpy.ndarray - The wrapped phase, rad, shape ``(n_data,)``. - significant : numpy.ndarray - Boolean mask of the samples above the floor, shape ``(n_data,)``, not all - false. - power : numpy.ndarray - The power column, shape ``(n_data,)``, positive where ``significant``. - - Returns - ------- - numpy.ndarray - The unwrapped phase, rad, shape ``(n_data,)``. - - Warns - ----- - SolpropWarning - If the phase looks undersampled: the samples beyond the innermost step within a - tenth of pi of ``+-pi``, on either side of the peak, hold more than a - thousandth of the power. + - ``omega``: angular frequency, rad/s, shape ``(n_data,)``, increasing. + - ``phase``: the wrapped phase, rad, shape ``(n_data,)``. + - ``significant``: boolean mask of the samples above the floor, shape ``(n_data,)``, + not all false. + - ``power``: the power column, shape ``(n_data,)``, positive where ``significant``. + - Returns: the unwrapped phase, rad, shape ``(n_data,)``. """ index = np.flatnonzero(significant) unwrapped = np.unwrap(phase[index]) @@ -3540,26 +3015,13 @@ def _read_phase( ) -> Float[np.ndarray, " n_data"]: """Return the optics-convention spectral phase of a phase column in radians. - Parameters - ---------- - omega : numpy.ndarray - Angular frequency, rad/s, shape ``(n_data,)``, increasing. - values : numpy.ndarray - The phase column, rad, shape ``(n_data,)``. - power : numpy.ndarray - The power column, shape ``(n_data,)``, with a positive peak. - wrapped : bool or None - Whether the column is wrapped; ``None`` takes it as wrapped when it spans at - most ``2 pi``. - floor : float - Share of the peak power above which a sample is significant. - phase_convention : {"optics", "fft"} - The sign convention of the column. - - Returns - ------- - numpy.ndarray - The phase, rad, shape ``(n_data,)``. + - ``omega``: angular frequency, rad/s, shape ``(n_data,)``, increasing. + - ``values``: the phase column, rad, shape ``(n_data,)``. + - ``power``: the power column, shape ``(n_data,)``, with a positive peak. + - ``wrapped``: whether the column is wrapped; ``None`` takes it as wrapped when it + spans at most ``2 pi``. + - ``phase_convention``: the sign convention of the column. + - Returns: the phase, rad, shape ``(n_data,)``. """ is_wrapped = float(np.ptp(values)) <= _WRAPPED_RANGE if wrapped is None else wrapped phase = ( @@ -3851,10 +3313,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 "CWPulse: a continuous-wave field", [ ("wavelength", engineering(self.wavelength, "m")), diff --git a/src/solprop/scan.py b/src/solprop/scan.py index 1d770a62..fc214a9c 100644 --- a/src/solprop/scan.py +++ b/src/solprop/scan.py @@ -74,13 +74,7 @@ def main(argv: Sequence[str] | None = None) -> int: def _parser() -> argparse.ArgumentParser: - """Declare CLI modes without interpreting values as Python expressions. - - Returns - ------- - argparse.ArgumentParser - Parser for safe execution and Slurm resource controls. - """ + """Declare CLI modes without interpreting values as Python expressions.""" parser = argparse.ArgumentParser( description=__doc__, epilog=_COLLECT_HELP, @@ -109,20 +103,7 @@ def _parser() -> argparse.ArgumentParser: def _policy(args: argparse.Namespace, parser: argparse.ArgumentParser) -> Executor: - """Resolve one execution default while retaining explicit script precedence. - - Parameters - ---------- - args : argparse.Namespace - Parsed command-line execution and resource controls. - parser : argparse.ArgumentParser - Argument parser used to report incompatible controls. - - Returns - ------- - Executor - CLI default, overridden by explicit script executors. - """ + """Resolve one execution default while retaining explicit script precedence.""" slurm = args.submit or args.emit_slurm is not None worker_controls = ( args.workers is not None diff --git a/src/solprop/spatial.py b/src/solprop/spatial.py index 3bb49628..034e1a68 100644 --- a/src/solprop/spatial.py +++ b/src/solprop/spatial.py @@ -173,14 +173,7 @@ def __post_init__(self) -> None: object.__setattr__(self, "density", Constant(1.0)) def _resolve_temperature(self) -> None: - """Resolve the one temperature: the given one, the mixture's, or room's. - - Raises - ------ - MaterialError - If a temperature is a path along z, is not finite and positive, or - differs from the mixture's beside a mixture. - """ + """Resolve the one temperature: the given one, the mixture's, or room's.""" own = self.material.temperature if isinstance(self.material, Mixture) else None given = self.temperature if isinstance(given, ZProfile) or isinstance(own, ZProfile): @@ -216,11 +209,6 @@ def _resolve_gas(self) -> None: """Resolve a named gas's susceptibility and density path. The built-in gas is optmat's: its law and its equation of state. - - Raises - ------ - MaterialError - If the pressure is negative. """ gas = as_gas(cast("str | Gas", self.material)) pressure = as_profile(self.pressure) @@ -567,17 +555,10 @@ def _longitudinal_square( ) -> Complex[np.ndarray, "n_modes n_omega"]: """Preserve unsquared Cartesian inputs when evaluating the launch operator. - Parameters - ---------- - transverse : solprop.grid.FreeGrid or numpy.ndarray - Cartesian axes or already squared transverse frequencies in m^-2. - z : float - Material sampling position in m. - - Returns - ------- - numpy.ndarray - Complex longitudinal squares in m^-2, one row per transverse column. + - ``transverse``: cartesian axes or already squared transverse frequencies in + m^-2. + - ``z``: material sampling position in m. + - Returns: complex longitudinal squares in m^-2, one row per transverse column. """ if isinstance(transverse, FreeGrid): square, low, transverse_low = self.cartesian_squares(transverse, z) @@ -658,12 +639,8 @@ def prefactor( def _check_evaluation(omega: np.ndarray, z: float) -> None: """Reject nonphysical frequency samples and nonfinite positions. - Parameters - ---------- - omega : numpy.ndarray - Physical angular frequencies in rad/s. - z : float - Propagation position in m. + - ``omega``: physical angular frequencies in rad/s. + - ``z``: propagation position in m. """ if not np.all(np.isfinite(omega)) or np.any(omega <= 0) or not math.isfinite(z): msg = "spatial index needs finite positive frequencies and a finite position" @@ -673,10 +650,7 @@ def _check_evaluation(omega: np.ndarray, z: float) -> None: def _check_transverse(kperp_sq: np.ndarray) -> None: """Reject nonfinite or negative squared transverse wave numbers. - Parameters - ---------- - kperp_sq : numpy.ndarray - Transverse wave-number squares in m^-2. + - ``kperp_sq``: transverse wave-number squares in m^-2. """ if not np.all(np.isfinite(kperp_sq)) or np.any(kperp_sq < 0): msg = "transverse wave-number squares must be finite and nonnegative" @@ -691,14 +665,7 @@ def _check_field( A planar grid's slab amplitudes are not field samples; every caller routes them to :func:`_check_planar` before this check. - Parameters - ---------- - grid : solprop.grid.TimeGrid - Temporal grid specifying the frequency count. - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - Transverse geometry specifying the column count. - spectrum : numpy.ndarray - Internal spatial spectrum, shape (n_modes, n_pol, n_omega). + - ``spectrum``: internal spatial spectrum, shape (n_modes, n_pol, n_omega). """ space.validate() if ( @@ -717,21 +684,12 @@ def _analytic( ) -> np.ndarray: """Reconstruct the analytic field with the common temporal FFT convention. - Parameters - ---------- - grid : solprop.grid.TimeGrid - Temporal transform layout and sampling. - spectrum : numpy.ndarray - Internal temporal spectrum with frequency on the final axis. - oversampling : int, optional - Factor by which to resample the time axis by zero-padding the spectrum, at - least 1; the samples then lie on - :func:`solprop.processing.fields.time_axis` of the grid. - - Returns - ------- - numpy.ndarray - Complex analytic samples with time on the final axis. + - ``grid``: temporal transform layout and sampling. + - ``spectrum``: internal temporal spectrum with frequency on the final axis. + - ``oversampling``: factor by which to resample the time axis by zero-padding the + spectrum, at least 1; the samples then lie on + :func:`solprop.processing.fields.time_axis` of the grid. + - Returns: complex analytic samples with time on the final axis. """ _check_oversampling(oversampling) if oversampling > 1: @@ -741,18 +699,7 @@ def _analytic( def _check_oversampling(oversampling: int) -> None: - """Refuse an oversampling factor below one. - - Parameters - ---------- - oversampling : int - The factor. - - Raises - ------ - GridError - If it is below one. - """ + """Refuse an oversampling factor below one.""" if oversampling < 1: msg = f"oversampling must be at least 1, got {oversampling}" raise GridError(msg) @@ -980,18 +927,7 @@ def whole_admittance(self) -> Complex[np.ndarray, "n_modes n_omega"]: return self._admittance_of(self.space.spectral_weights.size) def _admittance_of(self, n_columns: int) -> Complex[np.ndarray, "n_modes n_omega"]: - """Return the admittance of the coefficients' first `n_columns` columns. - - Parameters - ---------- - n_columns : int - The number of columns the coefficients hold. - - Returns - ------- - numpy.ndarray - :meth:`admittance` of every column, its chunks formed in threads. - """ + """Return the admittance of the coefficients' first `n_columns` columns.""" size = max(1, _FLUX_CHUNK // self.grid.n_omega) chunks = [ slice(start, min(start + size, n_columns)) @@ -1000,13 +936,7 @@ def _admittance_of(self, n_columns: int) -> Complex[np.ndarray, "n_modes n_omega admittance = np.empty((n_columns, self.grid.n_omega), dtype=np.complex128) def work(rows: slice) -> None: - """Form one block of the chunks' admittance. - - Parameters - ---------- - rows : slice - The block of chunks. - """ + """Form one block of the chunks' admittance.""" for columns in chunks[rows]: admittance[columns] = self.admittance(columns) @@ -1037,29 +967,11 @@ def _gathered( A large launch's single gather, page faults and all, took 2 s of its build (solprop issue #347). - - Parameters - ---------- - rows : numpy.ndarray - The distinct rows. - index : numpy.ndarray - The row each output row copies, ``(n_modes,)``. - - Returns - ------- - numpy.ndarray - The gathered rows. """ gathered = np.empty((index.size, rows.shape[1]), dtype=rows.dtype) def work(block: slice) -> None: - """Copy one block of rows. - - Parameters - ---------- - block : slice - The block of output rows. - """ + """Copy one block of rows.""" gathered[block] = rows[index[block]] parallel_rows(index.size, gathered.nbytes, work) @@ -1071,16 +983,9 @@ def _exact_squares( ) -> tuple[Float[np.ndarray, " n"], Float[np.ndarray, " n"]]: """Return each value's square as the sum of two floats, exactly (Dekker, Veltkamp). - Parameters - ---------- - values : numpy.ndarray - Each zero or of magnitude within ``[2**-450, 2**500]``, where the split can't - overflow and the low part can't underflow. - - Returns - ------- - tuple of numpy.ndarray - The rounded squares and their exact residuals. + - ``values``: each zero or of magnitude within ``[2**-450, 2**500]``, where the + split can't overflow and the low part can't underflow. + - Returns: the rounded squares and their exact residuals. """ split = 134217729.0 * values # 2**27 + 1 upper = split - (split - values) @@ -1094,15 +999,8 @@ def _transverse_residual(space: FreeGrid) -> Float[np.ndarray, " n_modes"]: ``math.fsum`` rounds the exact sum of its floats, as a rational sum would. - Parameters - ---------- - space : solprop.grid.FreeGrid - Stored unsquared transverse axes in rad/m, with x fastest. - - Returns - ------- - numpy.ndarray - The real residuals in m^-2. + - ``space``: stored unsquared transverse axes in rad/m, with x fastest. + - Returns: the real residuals in m^-2. """ k_x, k_y = np.asarray(space.k_x), np.asarray(space.k_y) moduli = np.abs(np.concatenate((k_x, k_y))) @@ -1142,19 +1040,8 @@ def _check_flux( ) -> None: """Validate a spectrum against its flux record. - Parameters - ---------- - spectrum : numpy.ndarray - Internal spectrum, shape ``(n_modes, n_pol, n_omega)``. - flux : SpatialFlux - The plane's measure. - planar : bool or None, optional - True requires a planar grid, False refuses one, None admits either. - - Raises - ------ - GridError - If the shape or finiteness is invalid or the grid kind is refused. + - ``spectrum``: internal spectrum, shape ``(n_modes, n_pol, n_omega)``. + - ``planar``: True requires a planar grid, False refuses one, None admits either. """ space = flux.space if isinstance(space, PlanarGrid): @@ -1178,18 +1065,9 @@ def _column_power( ) -> Float[np.ndarray, " n_modes"]: """Return each column's weighted axial energy, summed over polarisation. - Parameters - ---------- - spectrum : numpy.ndarray - Internal spectrum, shape ``(n_modes, n_pol, n_omega)``. - flux : SpatialFlux - The plane's measure. - - Returns - ------- - numpy.ndarray - ``scale * v_j * sum_k e_k Re(Y_jk) sum_p |X_jpk|^2`` in J (J/m on a line), - one per column. + - ``spectrum``: internal spectrum, shape ``(n_modes, n_pol, n_omega)``. + - Returns: ``scale * v_j * sum_k e_k Re(Y_jk) sum_p |X_jpk|^2`` in J (J/m on a + line), one per column. """ grid = flux.grid weights = fourier.energy_weights(grid.n_omega, grid.dt, kind=grid.kind) @@ -1435,18 +1313,9 @@ def _synthesise( ) -> np.ndarray: """Return a spectrum's physical field in V/m at the transverse nodes. - Parameters - ---------- - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - Transverse geometry. - spectrum : numpy.ndarray - Internal spectrum, shape ``(n_modes, n_pol, n_omega)``. - - Returns - ------- - numpy.ndarray - Frequency-domain field at the nodes, shape ``(n_nodes, n_pol, n_omega)``; - planar nodes are flattened ``ix + n_x*iy``. + - ``spectrum``: internal spectrum, shape ``(n_modes, n_pol, n_omega)``. + - Returns: Frequency-domain field at the nodes, shape ``(n_nodes, n_pol, n_omega)``; + planar nodes are flattened ``ix + n_x*iy``. """ if isinstance(space, RadialGrid): return np.moveaxis(space.hankel.inverse(np.moveaxis(spectrum, 0, -1)), -1, 0) @@ -1466,17 +1335,8 @@ def _closure(spectrum: np.ndarray, flux: SpatialFlux) -> np.ndarray: conjugate of the optics one (``docs/dev/conventions.md`` §2), so the closure applies the conjugate admittance. - Parameters - ---------- - spectrum : numpy.ndarray - Internal spectrum, shape ``(n_modes, n_pol, n_omega)``. - flux : SpatialFlux - The plane's measure. - - Returns - ------- - numpy.ndarray - Spectrum of the same shape. + - ``spectrum``: internal spectrum, shape ``(n_modes, n_pol, n_omega)``. + - Returns: spectrum of the same shape. """ closure = np.empty_like(spectrum, dtype=np.complex128) for columns in flux.chunks(): @@ -1491,17 +1351,9 @@ def _local_pair( ) -> tuple[np.ndarray, np.ndarray]: """Synthesise E and its closure at the nodes, in the frequency domain. - Parameters - ---------- - spectrum : numpy.ndarray - Internal spectrum, shape ``(n_modes, n_pol, n_omega)``. - flux : SpatialFlux - The plane's measure. - - Returns - ------- - tuple of numpy.ndarray - ``E`` in V/m and ``G / (eps0 c)`` in V/m, each ``(n_nodes, n_pol, n_omega)``. + - ``spectrum``: internal spectrum, shape ``(n_modes, n_pol, n_omega)``. + - Returns: ``E`` in V/m and ``G / (eps0 c)`` in V/m, each ``(n_nodes, n_pol, + n_omega)``. """ return _synthesise(flux.space, spectrum), _synthesise( flux.space, _closure(spectrum, flux) @@ -1513,18 +1365,9 @@ def _signed_fluence( ) -> tuple[Float[np.ndarray, " n_nodes"], Float[np.ndarray, " n_nodes"]]: """Return the signed fluence and its rounding budget at the nodes. - Parameters - ---------- - spectrum : numpy.ndarray - Internal spectrum, shape ``(n_modes, n_pol, n_omega)``. - flux : SpatialFlux - The plane's measure. - - Returns - ------- - tuple of numpy.ndarray - Fluence in J/m^2 at the nodes and a bound on its accumulated rounding, - the same at every node. + - ``spectrum``: internal spectrum, shape ``(n_modes, n_pol, n_omega)``. + - Returns: fluence in J/m^2 at the nodes and a bound on its accumulated rounding, + the same at every node. """ grid = flux.grid weights = fourier.energy_weights(grid.n_omega, grid.dt, kind=grid.kind) @@ -1634,19 +1477,10 @@ def _local_intensity( ) -> np.ndarray: """Return the signed local axial flux at the nodes in time. - Parameters - ---------- - spectrum : numpy.ndarray - Internal spectrum, shape ``(n_modes, n_pol, n_omega)``, already validated. - flux : SpatialFlux - The plane's measure. - oversampling : int, optional - Factor by which to resample the time axis, at least 1. - - Returns - ------- - numpy.ndarray - Flux in W/m^2, shape ``(n_nodes, oversampling n_t)``. + - ``spectrum``: internal spectrum, shape ``(n_modes, n_pol, n_omega)``, already + validated. + - ``oversampling``: factor by which to resample the time axis, at least 1. + - Returns: flux in W/m^2, shape ``(n_nodes, oversampling n_t)``. """ field, closure = _local_pair(spectrum, flux) grid = flux.grid @@ -1772,20 +1606,11 @@ def _check_planar( ) -> np.ndarray: """Validate a planar ``(p, k_y)`` spectrum and return it as y rows. - Parameters - ---------- - grid : solprop.grid.TimeGrid - Temporal grid specifying the frequency count. - space : solprop.modes.slab.PlanarGrid - The slab channels and the y axis. - spectrum : numpy.ndarray - Internal spectrum, shape ``(n_p*n_y, 1, n_omega)``, the channel fastest. - - Returns - ------- - numpy.ndarray - The same state as slab amplitudes in sqrt(W/m) at each y sample, shape - ``(n_y, n_p, n_omega)``. + - ``space``: the slab channels and the y axis. + - ``spectrum``: internal spectrum, shape ``(n_p*n_y, 1, n_omega)``, the channel + fastest. + - Returns: the same state as slab amplitudes in sqrt(W/m) at each y sample, shape + ``(n_y, n_p, n_omega)``. """ if spectrum.shape != (space.n_columns, 1, grid.n_omega) or not np.all( np.isfinite(spectrum) @@ -1803,19 +1628,10 @@ def _on_axis_pair( ) -> tuple[np.ndarray, np.ndarray]: """Reconstruct E and its closure at the geometric axis. - Parameters - ---------- - spectrum : numpy.ndarray - Internal radial spectrum in V m or Cartesian spectrum in V/m, - shape ``(n_modes, n_pol, n_omega)``. - flux : SpatialFlux - The plane's measure. - - Returns - ------- - tuple of numpy.ndarray - On-axis temporal spectra of ``E`` and ``G / (eps0 c)`` in V/m, each - ``(n_pol, n_omega)``. + - ``spectrum``: internal radial spectrum in V m or Cartesian spectrum in V/m, shape + ``(n_modes, n_pol, n_omega)``. + - Returns: On-axis temporal spectra of ``E`` and ``G / (eps0 c)`` in V/m, each + ``(n_pol, n_omega)``. """ _check_flux(spectrum, flux) space = flux.space @@ -2915,21 +2731,8 @@ def _aperture_weight( ) -> Float[np.ndarray, "n_chunk n_omega"]: """Return one aperture's weight on a chunk of columns at every frequency. - Parameters - ---------- - tables : ApertureTables - The far-field tables. - index : int - The aperture. - k_x, k_y : numpy.ndarray - The chunk's stored transverse wavevectors in rad/m, shape ``(n_chunk,)``. - - Returns - ------- - numpy.ndarray - ``W_a`` of the column's image, exactly zero at and below zero frequency and - wherever the column's image lies at or beyond the mask plane's distance from - the origin (SEP 23 sections 4.2 and 4.10), shape ``(n_chunk, n_omega)``. + - ``k_x``, ``k_y``: the chunk's stored transverse wavevectors in rad/m, shape + ``(n_chunk,)``. """ scale = tables.scale[index] across, along = mask_plane_offsets(scale, k_x, k_y, tables.centres[index]) @@ -3026,29 +2829,7 @@ def _far_field_request( field: Complex[np.ndarray, "n_modes n_pol n_omega"], tables: ApertureTables, ) -> set[str]: - """Check a far-field request against the field and its tables. - - Parameters - ---------- - names : collections.abc.Sequence of str - The statistics wanted. - grid : solprop.grid.TimeGrid - The temporal grid. - field : numpy.ndarray - The spectrum, shape ``(n_x*n_y, n_pol, n_omega)``. - tables : ApertureTables - The far-field tables. - - Returns - ------- - set of str - The names, checked. - - Raises - ------ - OutputError - As :func:`aperture_statistics` documents. - """ + """Check a far-field request against the field and its tables.""" wanted = set(names) unknown = sorted(wanted - set(FAR_FIELD_STATISTICS)) if unknown: @@ -3082,19 +2863,7 @@ def _far_field_request( class _FarFieldSums: - """Accumulate the far-field partials chunk by chunk, in ascending order. - - Parameters - ---------- - wanted : set of str - The far-field statistics asked for. - grid : solprop.grid.TimeGrid - The temporal grid. - tables : ApertureTables - The far-field tables. - n_pol : int - The spectrum's polarisation components. - """ + """Accumulate the far-field partials chunk by chunk, in ascending order.""" def __init__( self, wanted: set[str], grid: TimeGrid, tables: ApertureTables, n_pol: int @@ -3120,22 +2889,7 @@ def add( spectrum: Complex[np.ndarray, "n_chunk n_pol n_omega"], axial: AxialFactors, ) -> None: - """Add one chunk of columns, starting at column `first`. - - Parameters - ---------- - first : int - The chunk's first column. - spectrum : numpy.ndarray - The chunk of the spectrum, shape ``(n_chunk, n_pol, n_omega)``. - axial : callable - The plane's per-chunk admittance and rates. - - Raises - ------ - OutputError - If ``absorbed_power`` is wanted and the rates are not available. - """ + """Add one chunk of columns, starting at column `first`.""" tables, grid, wanted = self._tables, self._grid, self._wanted stop = first + spectrum.shape[0] columns = np.arange(first, stop) @@ -3172,12 +2926,7 @@ def _add_regions( ) -> None: """Add a chunk's weighted density to every region its columns belong to. - Parameters - ---------- - weighted : numpy.ndarray - ``w_c`` times the polarisation-summed spectral density. - k_x, k_y : numpy.ndarray - The chunk's stored transverse wavevectors in rad/m. + - ``k_x``, ``k_y``: the chunk's stored transverse wavevectors in rad/m. """ # Membership compares the physical wavevector, an exact negation of the # stored axes, with half-open edges, so regions that tile the plane tile the @@ -3203,18 +2952,7 @@ def _add_apertures( # noqa: PLR0917 - called positionally ) -> None: """Add a chunk to every aperture's collected spectrum and on-axis amplitude. - Parameters - ---------- - first : int - The chunk's first column. - spectrum : numpy.ndarray - The chunk of the spectrum. - admittance : numpy.ndarray - The chunk's complex admittance. - weighted : numpy.ndarray - ``w_c`` times the polarisation-summed spectral density. - k_x, k_y : numpy.ndarray - The chunk's stored transverse wavevectors in rad/m. + - ``k_x``, ``k_y``: the chunk's stored transverse wavevectors in rad/m. """ tables = self._tables stop = first + spectrum.shape[0] @@ -3239,13 +2977,7 @@ def _add_apertures( # noqa: PLR0917 - called positionally ) def values(self) -> dict[str, np.ndarray]: - """Return the requested statistics from the accumulated partials. - - Returns - ------- - dict - The statistics :func:`aperture_statistics` documents. - """ + """Return the requested statistics from the accumulated partials.""" conversion = 0.5 * EPS0 * C grid = self._grid factor = fourier.spectral_density( @@ -3349,19 +3081,12 @@ def radial_fluence_widths( values = np.concatenate(([axis_fluence], fluence)) positions = np.concatenate(([0.0], radius)) - def crossing(fraction: float) -> float: + def _crossing(fraction: float) -> float: """Interpolate the first outward threshold crossing. - Parameters - ---------- - fraction : float - Threshold divided by the central fluence, between zero and one. - - Returns - ------- - float - Radius in m, or NaN when the axis lies below the threshold or the - trace never crosses it. + - ``fraction``: threshold divided by the central fluence, between zero and one. + - Returns: radius in m, or NaN when the axis lies below the threshold or the + trace never crosses it. """ level = fraction * peak if axis_fluence < level: @@ -3378,7 +3103,7 @@ def crossing(fraction: float) -> float: * (positions[index] - positions[index - 1]) ) - return second, crossing(math.exp(-2)), 2 * crossing(0.5) + return second, _crossing(math.exp(-2)), 2 * _crossing(0.5) def cartesian_fluence_widths( @@ -3520,18 +3245,10 @@ def _fluence_rings( centroid inside the axes, and an angular spacing no larger than that arc length on the largest ring of the plane. - Parameters - ---------- - x, y : numpy.ndarray - Uniform increasing physical axes in m, of at least two samples each. - center_x, center_y : float - The fluence centroid in m. - - Returns - ------- - _FluenceRings or None - The rings, or ``None`` when the centroid lies within two half-spacings of an - edge, where the threshold widths are undefined. + - ``x``, ``y``: uniform increasing physical axes in m, of at least two samples each. + - ``center_x``, ``center_y``: the fluence centroid in m. + - Returns: the rings, or ``None`` when the centroid lies within two half-spacings of + an edge, where the threshold widths are undefined. """ limit = min(center_x - x[0], x[-1] - center_x, center_y - y[0], y[-1] - center_y) spacing = min(x[1] - x[0], y[1] - y[0]) / 2 @@ -3553,15 +3270,8 @@ def _ring_angles( circle inside the axes: fixed by the axes alone, whatever the centroid, so a backend that finishes the widths natively forms them once (SEP 47 section 6.4). - Parameters - ---------- - x, y : numpy.ndarray - Uniform increasing physical axes in m, of at least two samples each. - - Returns - ------- - tuple of numpy.ndarray - The cosines and the sines, shape ``(n_angles,)``, at least eight of each. + - ``x``, ``y``: uniform increasing physical axes in m, of at least two samples each. + - Returns: the cosines and the sines, shape ``(n_angles,)``, at least eight of each. """ spacing = min(x[1] - x[0], y[1] - y[0]) / 2 largest_circle = min(x[-1] - x[0], y[-1] - y[0]) / 2 @@ -3575,23 +3285,10 @@ def _ring_fluence_widths( ) -> tuple[float, float]: """Return the threshold widths of a fluence averaged on its rings. - Parameters - ---------- - radii : numpy.ndarray - The rings' radii in m, the first zero (``_fluence_rings``). - averaged : numpy.ndarray - The nonnegative fluence averaged on each ring. - - Returns - ------- - tuple of float - The first 1/e^2 radius and the FWHM in m, as :func:`radial_fluence_widths` - defines them, with the first ring as the axis. - - Raises - ------ - GridError - With fewer than three radii, too few to find a width from. + - ``radii``: the rings' radii in m, the first zero (``_fluence_rings``). + - ``averaged``: the nonnegative fluence averaged on each ring. + - Returns: the first 1/e^2 radius and the FWHM in m, as + :func:`radial_fluence_widths` defines them, with the first ring as the axis. """ _, radius_e2, width = radial_fluence_widths( radii[1:], averaged[1:], np.ones(radii.size - 1), float(averaged[0]) @@ -3604,24 +3301,11 @@ def _line_fluence_widths( ) -> tuple[float, float, float]: """Measure the one-dimensional widths of a line's fluence about its centroid. - Parameters - ---------- - x : numpy.ndarray - Validated uniform increasing axis in m. - samples : numpy.ndarray - Fluence divided by its maximum, nonnegative and not all zero. - - Returns - ------- - tuple of float - ``2*sqrt(mean((x - x_c)**2))``, the first 1/e^2 half-width and the FWHM in - m, the last two NaN when the centroid lies within two half-spacings of an - edge or the profile does not cross the level. - - Raises - ------ - GridError - If the moment is not representable. + - ``x``: validated uniform increasing axis in m. + - ``samples``: fluence divided by its maximum, nonnegative and not all zero. + - Returns: ``2*sqrt(mean((x - x_c)**2))``, the first 1/e^2 half-width and the FWHM + in m, the last two NaN when the centroid lies within two half-spacings of an edge + or the profile does not cross the level. """ total = float(np.sum(samples)) center = float(np.sum(samples * x) / total) @@ -3655,24 +3339,9 @@ def _line_fluence_widths( def _beam_widths(spectrum: np.ndarray, flux: SpatialFlux) -> tuple[float, float, float]: """Reduce the signed fluence profile with its spatial measure. - Parameters - ---------- - spectrum : numpy.ndarray - Internal radial spectrum in V m or Cartesian spectrum in V/m, - shape ``(n_modes, n_pol, n_omega)``. - flux : SpatialFlux - The plane's measure on a radial or Cartesian grid. - - Returns - ------- - tuple of float - Second-moment radius, central 1/e^2 radius and FWHM in m. - - Raises - ------ - GridError - If the fluence is negative anywhere beyond its rounding budget: a width - needs a nonnegative profile, and a signed axial flux is not clipped. + - ``spectrum``: internal radial spectrum in V m or Cartesian spectrum in V/m, shape + ``(n_modes, n_pol, n_omega)``. + - Returns: Second-moment radius, central 1/e^2 radius and FWHM in m. """ _check_flux(spectrum, flux, planar=False) fluence, budget = _signed_fluence(spectrum, flux) diff --git a/src/solprop/spatial_absorber.py b/src/solprop/spatial_absorber.py index 7e982bf1..b77ae6b8 100644 --- a/src/solprop/spatial_absorber.py +++ b/src/solprop/spatial_absorber.py @@ -266,31 +266,23 @@ def collar_strength( # since |Im sqrt(a + ib)| <= b / (2 sqrt(a)), so half its estimate falls short. estimate = float(np.max(attenuation * kappa_p / (0.5 * k_z * width))) - def short(alpha0: float) -> float: + def _short(alpha0: float) -> float: """Return how far the band's weakest attenuation exceeds the requirement. - Parameters - ---------- - alpha0 : float - A trial peak rate, 1/m. - - Returns - ------- - float - Field nepers; negative while the collar is too weak. + - ``alpha0``: a trial peak rate, 1/m. """ reached = float(np.min(collar_attenuation(kappa_p, k, alpha0, width))) return reached - attenuation low, high = 0.5 * estimate, 2.0 * estimate for _ in range(60): - if short(high) > 0.0: + if _short(high) > 0.0: break high *= 2.0 else: msg = "the collar's strength could not be bracketed" raise GridError(msg) - return float(brentq(short, low, high, xtol=1e-12 * high, rtol=1e-12)) + return float(brentq(_short, low, high, xtol=1e-12 * high, rtol=1e-12)) def reflection_threshold( diff --git a/src/solprop/stats.py b/src/solprop/stats.py index cdb7eafd..075deb90 100644 --- a/src/solprop/stats.py +++ b/src/solprop/stats.py @@ -288,18 +288,9 @@ def _power( ) -> Float[np.ndarray, "*batch n_t"]: """Return the instantaneous power of a stored spectrum, in watts. - Parameters - ---------- - grid : solprop.grid.TimeGrid - The grid the spectrum lives on. - field : numpy.ndarray - Stored spectrum, shape ``(..., n_omega)``, internal convention, in the square - root of watts. - - Returns - ------- - numpy.ndarray - ``P(t) = |E_a(t)|^2``, shape ``(..., n_t)``, W. + - ``field``: stored spectrum, shape ``(..., n_omega)``, internal convention, in the + square root of watts. + - Returns: ``P(t) = |E_a(t)|^2``, shape ``(..., n_t)``, W. """ return np.abs(analytic_field(grid, field)) ** 2 @@ -328,24 +319,11 @@ def _whitened( ``metric=None`` is the identity and returns the field itself, unchanged and uncopied, so an unweighted run's arithmetic is bitwise what it was. - Parameters - ---------- - field : numpy.ndarray - Stored spectrum, shape ``(n_modes, n_pol, n_omega)``, internal convention. - metric : numpy.ndarray or None - The basis flux metric ``W(omega)``, Hermitian positive definite, shape - ``(n_modes, n_modes, n_omega)``, dimensionless; ``None`` is the identity. - - Returns - ------- - numpy.ndarray - ``c = W^(1/2) a``, of the field's own shape and units. - - Raises - ------ - solprop.exceptions.OutputError - If the metric's extents disagree with the field, or it is not positive - definite at some frequency. + - ``field``: stored spectrum, shape ``(n_modes, n_pol, n_omega)``, internal + convention. + - ``metric``: the basis flux metric ``W(omega)``, Hermitian positive definite, shape + ``(n_modes, n_modes, n_omega)``, dimensionless; ``None`` is the identity. + - Returns: ``c = W^(1/2) a``, of the field's own shape and units. """ if metric is None: return field @@ -383,17 +361,8 @@ def _window_masks( option, because a windowed *energy* never returns to the time domain and so has nothing to gain from a smooth edge. - Parameters - ---------- - grid : solprop.grid.TimeGrid - The grid, whose ``omega`` axis the gate is taken on. - windows : numpy.ndarray - Window edges, shape ``(n_windows, 2)``, rad/s, each row ``(lo, hi)``. - - Returns - ------- - numpy.ndarray - Boolean masks, shape ``(n_windows, n_omega)``. + - ``grid``: the grid, whose ``omega`` axis the gate is taken on. + - ``windows``: window edges, shape ``(n_windows, 2)``, rad/s, each row ``(lo, hi)``. """ lower = windows[:, 0, np.newaxis] upper = windows[:, 1, np.newaxis] @@ -913,22 +882,6 @@ def _crossing( intercept of the line and evaluating ``(level - c)/m`` instead would, on a time axis whose values are of order ``1e-12`` and whose intercept is of order ``1e12``, lose most of the significand. - - Parameters - ---------- - x : numpy.ndarray - Abscissa, shape ``(n,)``. - y : numpy.ndarray - The trace, shape ``(n,)``. - level : float - The value to cross. - pair : tuple of int - Indices of the two samples that bracket the crossing. - - Returns - ------- - float - The crossing point, in the units of ``x``. """ low, high = pair span = y[high] - y[low] @@ -944,22 +897,9 @@ def _half_max_range( :func:`solprop.processing.time.half_max_range`, which documents the two conventions, both call it. - Parameters - ---------- - x : numpy.ndarray - Abscissa, shape ``(n,)``, increasing. - y : numpy.ndarray - The trace, shape ``(n,)``, unclipped. - outer : bool - Take the first and last sample above the level anywhere, rather than walking - outwards from the peak to the first sample below it on either side. - - Returns - ------- - tuple of float - The lower and upper crossings, both ``nan`` when the trace carries nothing, does - not fall to half its maximum on both sides, or reaches the end of the axis still - above it. + - Returns: the lower and upper crossings, both ``nan`` when the trace carries + nothing, does not fall to half its maximum on both sides, or reaches the end of + the axis still above it. """ undefined = (float("nan"), float("nan")) peak = float(np.max(y)) @@ -1002,23 +942,11 @@ def _half_width( ) -> Float[np.ndarray, ""]: """Return the full width at half maximum of the total power, in seconds. - Parameters - ---------- - grid : solprop.grid.TimeGrid - The grid the spectrum lives on. - field : numpy.ndarray - Stored spectrum, shape ``(n_modes, n_pol, n_omega)``, internal convention. - outermost : bool - Whether to take the outermost pair of half-maximum crossings rather than the - innermost pair on either side of the global peak. - metric : numpy.ndarray or None, optional - The basis flux metric ``W(omega)``; ``None`` is the identity. - - Returns - ------- - numpy.ndarray - The width as a zero-dimensional array, s, or ``nan`` when the trace does not - cross half its maximum on both sides of the peak. + - ``field``: stored spectrum, shape ``(n_modes, n_pol, n_omega)``, internal + convention. + - ``metric``: the basis flux metric ``W(omega)``; ``None`` is the identity. + - Returns: the width as a zero-dimensional array, s, or ``nan`` when the trace does + not cross half its maximum on both sides of the peak. """ return _power_half_width( grid, @@ -1032,19 +960,8 @@ def _power_half_width( ) -> Float[np.ndarray, ""]: """Measure half-maximum crossings of a power trace without clipping its sign. - Parameters - ---------- - grid : solprop.grid.TimeGrid - Temporal grid defining frequency and Parseval weights. - power : numpy.ndarray - Unclipped temporal flux, shape (n_t,), in watts. - outermost : bool - Use the outermost half-maximum crossings when true. - - Returns - ------- - numpy.ndarray - Scalar duration in seconds, NaN when crossings are absent. + - ``outermost``: use the outermost half-maximum crossings when true. + - Returns: scalar duration in seconds, NaN when crossings are absent. """ start, end = _half_max_range(grid.t, power, outer=outermost) return np.asarray(end - start) @@ -1335,13 +1252,7 @@ class StatDefinition: def _catalogue() -> dict[str, StatDefinition]: - """Return the fixed catalogue of statistics, keyed by name. - - Returns - ------- - dict - Every statistic solprop knows about, implemented or reserved. - """ + """Return the fixed catalogue of statistics, keyed by name.""" entries = ( StatDefinition( name="omega0", @@ -1850,24 +1761,8 @@ def _aperture_array( ) -> np.ndarray: """Own a finite float64 copy of one aperture table, read-only. - Parameters - ---------- - values : object - The table as given. - shape : tuple of int or None - The shape it must have; ``None`` asks for any flat vector. - name : str - The field, named in the refusal. - - Returns - ------- - numpy.ndarray - A read-only, C-contiguous float64 copy. - - Raises - ------ - OutputError - If the table is not numeric, finite and of the stated shape. + - ``shape``: the shape it must have; ``None`` asks for any flat vector. + - Returns: a read-only, C-contiguous float64 copy. """ stated = "a flat vector" if shape is None else f"shape {shape}" try: @@ -1918,18 +1813,7 @@ def check_aperture_choices(profile: object, weighting: object) -> None: def _off_axis(shape: str) -> int: - """Return the index of the coordinate a half-plane does not depend on. - - Parameters - ---------- - shape : str - A half-plane's name. - - Returns - ------- - int - 1 for a half-plane bounded in x, 0 for one bounded in y. - """ + """Return the index of the coordinate a half-plane does not depend on.""" return 1 if shape.startswith("half_plane_x") else 0 @@ -2101,17 +1985,6 @@ def _adopt_rows(self, count: int) -> None: All circles are stored as no `shapes`, and rows of one profile as that `profile`, so that one request has one record. - - Parameters - ---------- - count : int - The number of apertures. - - Raises - ------ - OutputError - If a choice is unknown, there is not one per aperture, or `profile` is - given beside `profiles`. """ if self.shapes is not None: shapes = tuple(self.shapes) @@ -2144,19 +2017,7 @@ def _adopt_rows(self, count: int) -> None: object.__setattr__(self, "profiles", None if len(one) == 1 else profiles) def _check_outlines(self, count: int) -> None: - """Own the extents and check each row's measures against its shape. - - Parameters - ---------- - count : int - The number of apertures. - - Raises - ------ - OutputError - If a diameter or an extent is missing, not positive or silently unused, - or a half-plane's centre is off the axis it names. - """ + """Own the extents and check each row's measures against its shape.""" shapes = self.row_shapes circle = np.array([shape == "circle" for shape in shapes]) sized = np.array([shape in ("ellipse", "rectangle") for shape in shapes]) @@ -2198,12 +2059,9 @@ def _check_outlines(self, count: int) -> None: def _edge_measures(self) -> Float[np.ndarray, " n_apertures"]: """Return the width each row's edge width is measured against. - Returns - ------- - numpy.ndarray - A circle's diameter, ``sqrt(D_x D_y)`` of an ellipse (the diameter of its - circular frame), the smaller of a rectangle's widths, and zero on a - half-plane, which has none, in m, shape ``(n_apertures,)``. + - Returns: a circle's diameter, ``sqrt(D_x D_y)`` of an ellipse (the diameter of + its circular frame), the smaller of a rectangle's widths, and zero on a + half-plane, which has none, in m, shape ``(n_apertures,)``. """ measures = np.array(np.asarray(self.diameters)) if self.extents is not None: @@ -2216,18 +2074,7 @@ def _edge_measures(self) -> Float[np.ndarray, " n_apertures"]: return measures def _check_edges(self, count: int) -> None: - """Own the edge table and check it against the profiles and the weighting. - - Parameters - ---------- - count : int - The number of apertures. - - Raises - ------ - OutputError - If the edges disagree with the profiles or the weighting. - """ + """Own the edge table and check it against the profiles and the weighting.""" kinds = self.row_profiles smooth = [kind for kind in kinds if kind != "hard"] if not smooth: @@ -2259,20 +2106,7 @@ def _check_edges(self, count: int) -> None: def _check_orders( self, edges: Float[np.ndarray, " n_apertures"], rows: np.ndarray ) -> None: - """Check the rows with a supergauss profile. - - Parameters - ---------- - edges : numpy.ndarray - The edge table. - rows : numpy.ndarray - Which rows have the profile. - - Raises - ------ - OutputError - Under quadrature weighting, on a half-plane, or for an order below 2. - """ + """Check the rows with a supergauss profile.""" if self.weighting == "quadrature": msg = ( "quadrature weighting needs a symmetric edge, and a supergauss " @@ -2298,24 +2132,7 @@ def _check_widths( ) -> None: """Check the rows with one symmetric edge profile. - Parameters - ---------- - kind : str - The profile, ``tanh`` or ``rcos``. - edges : numpy.ndarray - The edge table. - rows : numpy.ndarray - Which rows have the profile. - - Raises - ------ - OutputError - If an edge width is out of its range. - - Warns - ----- - solprop.exceptions.ApertureEdgeWarning - For ``tanh`` under amplitude weighting with ``r/w < 4``. + - ``kind``: the profile, ``tanh`` or ``rcos``. """ shapes = self.row_shapes half_plane = np.array([shape.startswith("half_plane") for shape in shapes]) @@ -2361,11 +2178,6 @@ def _check_paraxial(self) -> None: A half-plane reaches the disc's edge by its nature, and the columns there carry zero weight (SEP 23 section 4.2); its edge must lie inside the disc, or it would select all of the disc or nothing. - - Raises - ------ - OutputError - Naming the apertures that reach it. """ centres = np.asarray(self.centres) reach = np.array( @@ -2386,20 +2198,9 @@ def _check_paraxial(self) -> None: def _reach(self, row: int, shape: str, centre: Float[np.ndarray, " 2"]) -> float: """Return how far from the origin a row's outline reaches in the mask plane. - Parameters - ---------- - row : int - The aperture. - shape : str - Its outline. - centre : numpy.ndarray - Its centre, m. - - Returns - ------- - float - The reach in m. A raised cosine's reaches its edge width further out; the - unbounded ``tanh`` and ``supergauss`` tails are zeroed at the disc's edge. + - ``centre``: its centre, m. + - Returns: the reach in m. A raised cosine's reaches its edge width further out; + the unbounded ``tanh`` and ``supergauss`` tails are zeroed at the disc's edge. """ width = ( float(np.asarray(self.edges)[row]) @@ -2500,24 +2301,12 @@ def same_as(self, other: "ApertureSet | None") -> bool: def _no_windows() -> Float[np.ndarray, "n_windows 2"]: - """Return the empty window list a spec defaults to. - - Returns - ------- - numpy.ndarray - An array of shape ``(0, 2)``. - """ + """Return the empty window list a spec defaults to.""" return np.empty((0, 2)) def _no_regions() -> Float[np.ndarray, "n_regions 4"]: - """Return the empty region table a spec defaults to. - - Returns - ------- - numpy.ndarray - An array of shape ``(0, 4)``. - """ + """Return the empty region table a spec defaults to.""" return np.empty((0, 4)) @@ -2693,18 +2482,7 @@ def n_windows(self) -> int: def _check_names(names: Sequence[str]) -> None: - """Check that every requested statistic exists, is implemented and appears once. - - Parameters - ---------- - names : collections.abc.Sequence - The requested names. - - Raises - ------ - OutputError - If a name is unknown, reserved for physics that does not exist yet, or repeated. - """ + """Check that every requested statistic exists, is implemented and appears once.""" unknown = sorted(set(names) - set(CATALOGUE)) if unknown: msg = f"unknown statistics {unknown}; the catalogue holds {sorted(CATALOGUE)}" @@ -2723,21 +2501,7 @@ def _check_names(names: Sequence[str]) -> None: def _check_between_planes(names: Sequence[str]) -> None: - """Check that every periodic statistic is one a backend evaluates between planes. - - Parameters - ---------- - names : collections.abc.Sequence - The periodic subset, already known to be a subset of the requested names. - - Raises - ------ - OutputError - Naming the statistics that cannot be evaluated between planes, and the ones - that can. The refusal is by name and at construction because the alternative -- - evaluating them anyway -- can cost a fifth of a step: ``zdw`` is a root search - per mode and does not belong on every accepted step. - """ + """Check that every periodic statistic is one a backend evaluates between planes.""" refused = sorted(name for name in names if not CATALOGUE[name].between_planes) if refused: available = sorted( @@ -2753,17 +2517,7 @@ def _check_between_planes(names: Sequence[str]) -> None: def _check_windows(windows: Float[np.ndarray, "n_windows 2"]) -> None: """Check that every spectral window is a properly ordered pair. - Parameters - ---------- - windows : numpy.ndarray - Window edges, shape ``(n_windows, 2)``, rad/s. - - Raises - ------ - OutputError - If the array holds a NaN, or has a row whose lower edge is not below its upper - edge. That it is a list of pairs is fixed by the annotation, as it is for - :class:`solprop.stepper.tableau.Tableau`. + - ``windows``: window edges, shape ``(n_windows, 2)``, rad/s. """ if np.isnan(windows).any(): msg = "energy_windows must not hold a NaN edge" @@ -2780,17 +2534,7 @@ def _check_windows(windows: Float[np.ndarray, "n_windows 2"]) -> None: def _check_regions(regions: Float[np.ndarray, "n_regions 4"]) -> None: """Check that every region of transverse wavevector is a properly ordered box. - Parameters - ---------- - regions : numpy.ndarray - Region edges, shape ``(n_regions, 4)``, rad/m. - - Raises - ------ - OutputError - If there are more than :data:`MAX_REGIONS` rows, an edge is NaN, or a row's - lower edge is not below its upper edge on either axis. That it is a list of - quadruples is fixed by the annotation, as for the energy windows. + - ``regions``: region edges, shape ``(n_regions, 4)``, rad/m. """ if regions.shape[0] > MAX_REGIONS: msg = ( @@ -2814,22 +2558,7 @@ def _check_regions(regions: Float[np.ndarray, "n_regions 4"]) -> None: def _profile_for(profiles: Mapping[str, ZProfile] | None, name: str) -> ZProfile: """Return the z-profile one statistic needs, or say which one is missing. - Parameters - ---------- - profiles : collections.abc.Mapping or None - The profiles the run has, keyed by statistic name. - name : str - The statistic asking. - - Returns - ------- - solprop.profiles.ZProfile - The profile. - - Raises - ------ - OutputError - If no profile of that name was given. + - ``profiles``: the profiles the run has, keyed by statistic name. """ if profiles is None or name not in profiles: available = sorted(profiles) if profiles else [] @@ -2850,24 +2579,8 @@ def _zdw_or_nan( plane records ``nan`` with a warning, as :func:`fwhm_t` does for a missing crossing. A direct call to :func:`zdw` still raises. - Parameters - ---------- - modes : collections.abc.Sequence - The propagated modes, in state order. - z : float - Position along the waveguide, m. - grid : solprop.grid.TimeGrid - The grid whose band is searched. - - Returns - ------- - numpy.ndarray - Zero-dispersion wavelength or ``nan``, shape ``(n_modes,)``, m. - - Warns - ----- - solprop.exceptions.SolpropWarning - If ``beta_2`` has one sign at both ends of the band for any mode. + - ``z``: position along the waveguide, m. + - Returns: Zero-dispersion wavelength or ``nan``, shape ``(n_modes,)``, m. """ window = inband_wavelengths(grid) values = np.full(len(modes), np.nan) @@ -3117,23 +2830,8 @@ def _poynting_statistics( The real instantaneous sum-frequency term is excluded. Negative broadband excursions of the Hermitian flux are retained, without clipping. - Parameters - ---------- - spec : StatsSpec - Requested statistics and half-open spectral windows. - grid : solprop.grid.TimeGrid - Temporal grid defining frequency and Parseval weights. - field : numpy.ndarray - Internal modal spectrum, shape (n_modes, n_pol, n_omega). - weight : numpy.ndarray - Positive power weight, with the same modal shape. - axis : numpy.ndarray or None - Physical on-axis synthesis matrix, shape (2, n_modes). - - Returns - ------- - dict - Requested energy, temporal flux and axial intensity statistics. + - ``axis``: physical on-axis synthesis matrix, shape (2, n_modes). + - Returns: requested energy, temporal flux and axial intensity statistics. """ names = set(spec.names) & _POYNTING_STATISTICS values: dict[str, np.ndarray] = {} @@ -3209,19 +2907,9 @@ def _far_field_statistics( ) -> dict[str, np.ndarray]: """Form every saved-field far-field statistic a spec asks for, in one pass. - Parameters - ---------- - spec : StatsSpec - The request, with its far-field tables. - flux : solprop.spatial.SpatialFlux - The plane's measure; a radial grid is refused by name. - field : numpy.ndarray - Internal Cartesian spectrum in V/m, shape ``(n_x*n_y, n_pol, n_omega)``. - - Returns - ------- - dict - The requested statistics of :data:`_SAVED_FAR_FIELD`, keyed by name. + - ``field``: internal Cartesian spectrum in V/m, shape ``(n_x*n_y, n_pol, + n_omega)``. + - Returns: the requested statistics of :data:`_SAVED_FAR_FIELD`, keyed by name. """ # The translation layer imports this module, so its tables are reached lazily. from solprop.translate.spatial import aperture_tables # noqa: PLC0415 @@ -3249,20 +2937,8 @@ def _spatial_statistic( ) -> np.ndarray: """Evaluate a spatial saved-field statistic through its physics reference. - Parameters - ---------- - name : str - Requested spatial catalogue name. - flux : solprop.spatial.SpatialFlux - The plane's measure, binding its grids and medium. - field : numpy.ndarray - Internal spectrum, shape (n_modes, n_pol, n_omega): a Hankel spectrum in - V m or a raw Cartesian DFT spectrum in V/m. - - Returns - ------- - numpy.ndarray - Scalar or array value in the catalogue's units. + - ``field``: internal spectrum, shape (n_modes, n_pol, n_omega): a Hankel spectrum + in V m or a raw Cartesian DFT spectrum in V/m. """ if name == "energy": return np.asarray(spatial_energy(field, flux)) diff --git a/src/solprop/tools.py b/src/solprop/tools.py index 3c0cef86..c857f1f7 100644 --- a/src/solprop/tools.py +++ b/src/solprop/tools.py @@ -210,11 +210,6 @@ def _probing() -> Iterator[None]: not results. The answer itself is re-evaluated outside this context, so a root that lies outside a model's range, or at a density beyond the dilute law, still warns. - - Yields - ------ - None - Control, with the warning categories ignored. """ with warnings.catch_warnings(): warnings.simplefilter("ignore", MaterialRangeWarning) @@ -223,25 +218,7 @@ def _probing() -> Iterator[None]: def _positive(name: str, value: float) -> float: - """Return a finite positive number or refuse it by name. - - Parameters - ---------- - name : str - The argument's name, for the message. - value : float - The value. - - Returns - ------- - float - The value as a float. - - Raises - ------ - ToolsError - 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) or number <= 0.0: raise ToolsError(f"{name} must be finite and positive, got {value!r}") @@ -251,15 +228,8 @@ def _positive(name: str, value: float) -> float: def _omega(wavelength: float) -> float: """Return the angular frequency of a vacuum wavelength. - Parameters - ---------- - wavelength : float - Vacuum wavelength in m, > 0. - - Returns - ------- - float - Angular frequency in rad/s. + - ``wavelength``: vacuum wavelength in m, > 0. + - Returns: angular frequency in rad/s. """ return 2.0 * math.pi * C / _positive("wavelength", wavelength) @@ -267,15 +237,7 @@ def _omega(wavelength: float) -> float: def _check_shape(shape: str) -> None: """Refuse a pulse shape the design numbers are not defined for. - Parameters - ---------- - shape : str - The shape keyword. - - Raises - ------ - ToolsError - If it is neither ``"sech"`` nor ``"gauss"``. + - ``shape``: the shape keyword. """ if shape not in ("sech", "gauss"): raise ToolsError(f"shape must be 'sech' or 'gauss', got {shape!r}") @@ -571,62 +533,18 @@ class SolitonContent: def _scattering_angle( x: float, theta: Float[np.ndarray, " n_components"], order: float, eta: float ) -> list[float]: - """Evaluate the real Zakharov-Shabat phase equation for a Gaussian. - - Parameters - ---------- - x : float - Time divided by the input width. - theta : numpy.ndarray - Unwrapped scattering angle, one element. - order : float - Conventional soliton order, the field's peak. - eta : float - Trial imaginary eigenvalue. - - Returns - ------- - list of float - Angle derivative. - """ + """Evaluate the real Zakharov-Shabat phase equation for a Gaussian.""" return [-order * math.exp(-x * x / 2.0) - eta * math.sin(2 * theta[0])] def _gaussian_eigenvalue(order: float) -> float: - """Solve the half-line scattering problem for the largest Gaussian eigenvalue. - - Parameters - ---------- - order : float - Conventional soliton order, above the first Gaussian threshold. - - Returns - ------- - float - Positive imaginary part of the dimensionless eigenvalue. - - Raises - ------ - ToolsError - If integration or eigenvalue refinement fails. - """ + """Solve the half-line scattering problem for the largest Gaussian eigenvalue.""" # The omitted field area is <= 1e-13. Moving the boundary with the amplitude # avoids assuming that the Gaussian tail is negligible for every input N. extent = math.sqrt(2.0) * float(erfcinv(1e-13 / (order * math.sqrt(math.pi / 2.0)))) def residual(eta: float) -> float: - """Return the ground-state symmetry residual at the pulse centre. - - Parameters - ---------- - eta : float - Trial imaginary eigenvalue. - - Returns - ------- - float - Phase angle plus pi/4. - """ + """Return the ground-state symmetry residual at the pulse centre.""" if eta == 0.0: # The exact area protects the first threshold from tail truncation. return math.pi / 4.0 - order * math.sqrt(math.pi / 2.0) @@ -1269,32 +1187,13 @@ def _resolve_fill( ) -> Mixture: """Return the mixture a fill declaration means, as the run interfaces read it. - Parameters - ---------- - gas : Gas, str, Mapping or Mixture - A built-in gas, ``"air"`` (nitrogen and oxygen), mole fractions, or a - complete mixture. - pressure : float or None - Total pressure in bar; must be ``None`` beside a `Mixture`, which carries its - own, and defaults to one bar otherwise. - temperature : float or None - Fill temperature in K; ``None`` is room temperature, or the mixture's own - beside a `Mixture`, which refuses any other. - materials : MaterialSelection or None, optional - Resolve a name, a fraction mapping's names and ``"air"`` from optmat under - this selection, as the drivers' ``capillary_fill`` does; ``None`` is - ``MaterialSelection()``. A mixture is taken as it is. - - Returns - ------- - Mixture - The ordered populations. - - Raises - ------ - ToolsError - If a pressure, or a temperature other than its own, is given beside a - mixture, or the mixture's temperature varies along z. + - ``pressure``: total pressure in bar; must be ``None`` beside a `Mixture`, which + carries its own, and defaults to one bar otherwise. + - ``temperature``: fill temperature in K; ``None`` is room temperature, or the + mixture's own beside a `Mixture`, which refuses any other. + - ``materials``: resolve a name, a fraction mapping's names and ``"air"`` from + optmat under this selection, as the drivers' ``capillary_fill`` does; ``None`` is + ``MaterialSelection()``. A mixture is taken as it is. """ if isinstance(gas, Mixture): if pressure is not None: @@ -1337,17 +1236,7 @@ def _check_carrier(materials: MaterialSelection | None, wavelength: float) -> No wavelength, so a design tool refuses one other than the wavelength it evaluates the Kerr coefficient at: the two then evaluate it at the same carrier. - Parameters - ---------- - materials : MaterialSelection or None - The design's material selection. - wavelength : float - The pump wavelength in m. - - Raises - ------ - ToolsError - If the selection names another carrier. + - ``wavelength``: the pump wavelength in m. """ carrier = None if materials is None else materials.carrier_wavelength if carrier is not None and not carriers_agree(float(carrier), wavelength): @@ -1365,16 +1254,7 @@ def _default_cladding( ) -> GlassIndex | complex: """Return the cladding a design's capillary takes, as the capillary driver does. - 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 fused - silica. + - ``cladding``: the cladding given; ``None`` is the package default. """ return fused_silica() if cladding is None else cladding @@ -1382,24 +1262,10 @@ def _default_cladding( def _fill_chi3(mixture: Mixture, wavelength: float, z: float = 0.0) -> float: """Return a fill's density-weighted third-order susceptibility. - Parameters - ---------- - mixture : Mixture - The fill. - wavelength : float - Vacuum wavelength in m, the carrier at which an optmat Kerr model is taken. - z : float, optional - Position in m at which the densities are evaluated. - - Returns - ------- - float - ``chi3`` in m^2/V^2. - - Raises - ------ - ToolsError - If a component of the fill has no Kerr data. + - ``wavelength``: vacuum wavelength in m, the carrier at which an optmat Kerr model + is taken. + - ``z``: position in m at which the densities are evaluated. + - Returns: ``chi3`` in m^2/V^2. """ try: return float(mixture.gamma3_path(wavelength)(np.array([float(z)]))[0]) @@ -1412,25 +1278,11 @@ def _fill_n2( ) -> tuple[float, float]: """Return a fill's nonlinear index and its linear index at a wavelength. - Parameters - ---------- - mixture : Mixture - The fill. - wavelength : float - Vacuum wavelength in m, the carrier at which an optmat Kerr model is taken. - z : float, optional - Position in m at which the densities are evaluated. - - Returns - ------- - tuple of float - ``n2`` in m^2/W, from the density-weighted ``chi3`` and the exact ``n0^2`` of - the fill, and that linear index ``n0``. - - Raises - ------ - ToolsError - If a component of the fill has no Kerr data. + - ``wavelength``: vacuum wavelength in m, the carrier at which an optmat Kerr model + is taken. + - ``z``: position in m at which the densities are evaluated. + - Returns: ``n2`` in m^2/W, from the density-weighted ``chi3`` and the exact + ``n0^2`` of the fill, and that linear index ``n0``. """ chi3 = _fill_chi3(mixture, wavelength, z) here = np.array([float(z)]) @@ -1442,20 +1294,10 @@ def _fill_n2( def _on_axis_intensity(mode: Mode, peak_power: float, z: float) -> float: """Return the on-axis intensity of a mode carrying a given power. - Parameters - ---------- - mode : Mode - The mode. - peak_power : float - Power in W. - z : float - Position along the waveguide, m. - - Returns - ------- - float - Intensity on the axis in W/m^2, both transverse components included; NaN for - a mode that declares no transverse profile. + - ``peak_power``: power in W. + - ``z``: position along the waveguide, m. + - Returns: intensity on the axis in W/m^2, both transverse components included; NaN + for a mode that declares no transverse profile. """ # The same synthesis the run's peak_intensity statistic uses: the normalised mode # field at the origin in V/m per sqrt(W), so that eps0 c |e|^2 / 2 is the @@ -1481,20 +1323,11 @@ def _refine_zero(mode: Mode, low: float, high: float, z: float) -> float: of the differenced ``beta2``, which a scalar search converges onto until the model returns NaN. - Parameters - ---------- - mode : Mode - The mode. - low, high : float - The bracket, rad/s, whose ends have ``beta2`` of opposite signs. - z : float - Position along the waveguide, m. - - Returns - ------- - float - The first sign change upwards in frequency, rad/s, linearly interpolated in the - last bracket; or NaN when samples that are not finite separate the signs. + - ``low``, ``high``: the bracket, rad/s, whose ends have ``beta2`` of opposite + signs. + - ``z``: position along the waveguide, m. + - Returns: the first sign change upwards in frequency, rad/s, linearly interpolated + in the last bracket; or NaN when samples that are not finite separate the signs. """ first = last = math.nan for _ in range(_ZDW_REFINE_ROUNDS): @@ -1524,19 +1357,10 @@ def _zdw_near(mode: Mode, omega: float, z: float) -> float: interval (3.6e13 rad/s) restore the sign and are both missed, and a sample that is not finite cannot bracket anything. - Parameters - ---------- - mode : Mode - The mode. - omega : float - Pump angular frequency in rad/s. - z : float - Position along the waveguide, m. - - Returns - ------- - float - Wavelength in m, or NaN when the dispersion has no zero in 100 nm to 3 um. + - ``omega``: pump angular frequency in rad/s. + - ``z``: position along the waveguide, m. + - Returns: wavelength in m, or NaN when the dispersion has no zero in 100 nm to 3 + um. """ grid = np.linspace( 2.0 * math.pi * C / _ZDW_RANGE[1], @@ -1709,29 +1533,9 @@ def _capillary( ) -> MarcatiliMode: """Build the HE, TE or TM capillary the capillary design tools report on. - Parameters - ---------- - radius : float - Core radius in m, > 0. - mixture : Mixture - The resolved fill. - n : int - Azimuthal mode index. - m : int - Radial mode index, >= 1. - kind : {'HE', 'TE', 'TM'} - Mode family. - model : {'full', 'reduced'} - Marcatili model. - loss : bool, float or callable - Loss policy of the mode. - cladding : GlassIndex, complex or None - Cladding index; ``None`` is fused silica. - - Returns - ------- - MarcatiliMode - The mode, as `prop_capillary` builds it for the same arguments. + - ``radius``: core radius in m, > 0. + - ``m``: radial mode index, >= 1. + - ``cladding``: cladding index; ``None`` is fused silica. """ return MarcatiliMode( _positive("radius", radius), @@ -1841,15 +1645,7 @@ def capillary_params( def _area_per_order(shape: PulseShape) -> float: """Return the pulse area over pi per unit conventional soliton order. - Parameters - ---------- - shape : {'sech', 'gauss'} - The pulse shape. - - Returns - ------- - float - One for sech, ``sqrt(2 / pi)`` for a Gaussian (:func:`soliton_content`). + - ``shape``: the pulse shape. """ return 1.0 if shape == "sech" else math.sqrt(2.0 / math.pi) @@ -1857,36 +1653,13 @@ def _area_per_order(shape: PulseShape) -> float: def _gaussian_order(eigenvalue: float) -> float: """Return the conventional order whose Gaussian has a given largest eigenvalue. - Parameters - ---------- - eigenvalue : float - Target imaginary part of the largest Zakharov-Shabat eigenvalue, > 0. - - Returns - ------- - float - The conventional soliton order. - - Raises - ------ - ToolsError - If the eigenvalue solve fails or the bracket does not hold the root. + - ``eigenvalue``: target imaginary part of the largest Zakharov-Shabat eigenvalue, > + 0. """ threshold = 0.5 / _area_per_order("gauss") def residual(order: float) -> float: - """Return the largest eigenvalue at an order less the target. - - Parameters - ---------- - order : float - Conventional soliton order. - - Returns - ------- - float - The difference, dimensionless. - """ + """Return the largest eigenvalue at an order less the target.""" # At and below the first threshold there is no bound state: the eigenvalue # that appears there starts from zero. if order <= threshold: @@ -1916,26 +1689,10 @@ def _target_order( ) -> float: """Return the conventional soliton order that meets exactly one design target. - Parameters - ---------- - order : float or None - A conventional soliton order, > 0. - count : int or None - A number of solitons, >= 1. - eigenvalue : float or None - The largest Zakharov-Shabat eigenvalue, > 0. - shape : {'sech', 'gauss'} - The pulse shape. - - Returns - ------- - float - The conventional soliton order. - - Raises - ------ - ToolsError - If not exactly one target is given, or it is out of range. + - ``order``: a conventional soliton order, > 0. + - ``count``: a number of solitons, >= 1. + - ``eigenvalue``: the largest Zakharov-Shabat eigenvalue, > 0. + - ``shape``: the pulse shape. """ given = [ name @@ -2195,15 +1952,9 @@ def excess_beta( difference quotient removes its rounding, which at a gas's ``n - 1`` of 1e-5 is the larger part of the quotient. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s. - - Returns - ------- - numpy.ndarray - ``omega (n - 1) / c`` in 1/m, with ``n - 1`` formed without cancellation. + - ``omega``: angular frequencies in rad/s. + - Returns: ``omega (n - 1) / c`` in 1/m, with ``n - 1`` formed without + cancellation. """ chi = self.fill.susceptibility(omega, np.zeros(1))[0] excess = chi / (1.0 + np.sqrt(1.0 + chi)) @@ -2213,17 +1964,8 @@ def excess_beta( def _bulk_beta2(bulk: _BulkGas, omega: float) -> float: """Return the bulk group-velocity dispersion by a Richardson central difference. - Parameters - ---------- - bulk : _BulkGas - The gas. - omega : float - Angular frequency in rad/s. - - Returns - ------- - float - ``d^2 beta / d omega^2`` in s^2/m. + - ``omega``: angular frequency in rad/s. + - Returns: ``d^2 beta / d omega^2`` in s^2/m. """ # A second difference of the gas's part of beta: only a ratio of two such numbers is # used, so the few parts in 1e5 this reaches at the step modes.base chose are ample. @@ -2232,15 +1974,8 @@ def _bulk_beta2(bulk: _BulkGas, omega: float) -> float: def second(h: float) -> float: """Return the three-point second difference at step ``h``. - Parameters - ---------- - h : float - Step in rad/s. - - Returns - ------- - float - The difference quotient in s^2/m. + - ``h``: step in rad/s. + - Returns: the difference quotient in s^2/m. """ values = bulk.excess_beta(np.array([omega - h, omega, omega + h])) return float(values[0] - 2.0 * values[1] + values[2]) / h**2 @@ -2252,18 +1987,7 @@ def second(h: float) -> float: def _check_phase(nonlinear_phase: str) -> None: - """Refuse a nonlinear phase model the phase matching does not define. - - Parameters - ---------- - nonlinear_phase : str - The phase keyword. - - Raises - ------ - ToolsError - If it is not ``"largest"``, ``"supplied_power"`` or ``"linear"``. - """ + """Refuse a nonlinear phase model the phase matching does not define.""" if nonlinear_phase not in ("largest", "supplied_power", "linear"): raise ToolsError( "nonlinear_phase must be 'largest', 'supplied_power' or 'linear', " @@ -2282,13 +2006,7 @@ class _PhasePulse: nonlinear_phase: NonlinearPhase def __post_init__(self) -> None: - """Refuse incomplete or contradictory phase declarations. - - Raises - ------ - ToolsError - If the phase choice or pulse declaration is invalid. - """ + """Refuse incomplete or contradictory phase declarations.""" _check_shape(self.shape) _check_phase(self.nonlinear_phase) if (self.peak_power is None) != (self.gamma is None): @@ -2324,24 +2042,9 @@ def __post_init__(self) -> None: def wavenumber(self, mode: Mode, omega: float, z: float) -> float: """Calculate the nonlinear phase in the soliton mode. - Parameters - ---------- - mode : Mode - Mode carrying the soliton. - omega : float - Pump angular frequency in rad/s. - z : float - Position in m. - - Returns - ------- - float - Nonlinear wavenumber in 1/m. - - Raises - ------ - ToolsError - If the largest-constituent model has no discrete soliton. + - ``omega``: pump angular frequency in rad/s. + - ``z``: position in m. + - Returns: nonlinear wavenumber in 1/m. """ if self.peak_power is None or self.gamma is None: return 0.0 @@ -2375,15 +2078,8 @@ def __call__( ) -> Float[np.ndarray, " n_omega"]: """Evaluate the mismatch at trial frequencies. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s. - - Returns - ------- - numpy.ndarray - Mismatch in 1/m. + - ``omega``: angular frequencies in rad/s. + - Returns: mismatch in 1/m. """ return np.asarray( base.beta(self.mode, omega, self.z) @@ -2402,23 +2098,9 @@ def _prepare_mismatch( ) -> _PhaseMismatch: """Prepare a phase-matching calculation at one position. - Parameters - ---------- - mode : Mode - Dispersive-wave mode. - wavelength : float - Pump wavelength in m. - soliton_mode : Mode or None - Pump mode, or None to use mode. - z : float - Position in m. - pulse : _PhasePulse - Validated pulse and phase choice. - - Returns - ------- - _PhaseMismatch - Callable with the nonlinear phase evaluated once. + - ``wavelength``: pump wavelength in m. + - ``soliton_mode``: pump mode, or None to use mode. + - ``z``: position in m. """ soliton = mode if soliton_mode is None else soliton_mode omega0 = _omega(wavelength) @@ -2592,18 +2274,11 @@ def lambda_rdw_all( 2.0 * math.pi * C / longest, 2.0 * math.pi * C / shortest, _MISMATCH_SAMPLES ) - def mismatch(value: float) -> float: + def _mismatch(value: float) -> float: """Return the mismatch at one frequency, for the root finder. - Parameters - ---------- - value : float - Angular frequency in rad/s. - - Returns - ------- - float - Mismatch in 1/m. + - ``value``: angular frequency in rad/s. + - Returns: mismatch in 1/m. """ return float(prepared(np.array([value]))[0]) @@ -2625,13 +2300,13 @@ def mismatch(value: float) -> float: # The refinement may land on a resonance exactly, where a model may # divide by zero: such a point is no root, and is told apart below. with np.errstate(divide="ignore", invalid="ignore"): - root = float(brentq(mismatch, float(grid[i]), float(grid[i + 1]))) + root = float(brentq(_mismatch, float(grid[i]), float(grid[i + 1]))) except ValueError: # The index model is not a number inside the bracket: a resonance of # the guide (an antiresonant fibre's), not a phase-matched wavelength. continue with np.errstate(divide="ignore", invalid="ignore"): - residual = mismatch(root) + residual = _mismatch(root) if not abs(residual) <= min(abs(sampled[i]), abs(sampled[i + 1])): # Refined onto a pole, through which the sign changed: no root. continue @@ -2746,24 +2421,9 @@ def _pressure_root( ) -> float: """Return the pressure at which a function of pressure vanishes. - Parameters - ---------- - residual : callable - A function of the pressure in bar. - pressure_max : float - Upper end of the bracket in bar. - what : str - What is being matched, for the refusal. - - Returns - ------- - float - The root in bar. - - Raises - ------ - ToolsError - If the function has the same sign at both ends of the bracket. + - ``residual``: a function of the pressure in bar. + - ``pressure_max``: upper end of the bracket in bar. + - Returns: the root in bar. """ top = _positive("pressure_max", pressure_max) if top <= _PRESSURE_FLOOR: @@ -2804,29 +2464,10 @@ def _design_capillary( ``"air"``) as an optmat fill at one bar and ``cladding=None`` as optmat's recommended silica. - Parameters - ---------- - radius : float - Core radius in m, > 0. - gas : Gas or str - The species. - temperature : float - Fill temperature in K. - m : int - Radial index of the HE_1m mode. - model : {'full', 'reduced'} - Marcatili model. - cladding : GlassIndex, complex or None - Cladding index; ``None`` is fused silica. - lattice : TubeLattice or None - Tube geometry for a VincettiMode; None builds a Marcatili capillary. - materials : MaterialSelection or None - The design's material selection. - - Returns - ------- - MarcatiliMode - The capillary at one bar. + - ``radius``: core radius in m, > 0. + - ``temperature``: fill temperature in K. + - ``cladding``: cladding index; ``None`` is fused silica. + - ``lattice``: tube geometry for a VincettiMode; None builds a Marcatili capillary. """ bore = _positive("radius", radius) wall = _default_cladding(cladding) @@ -2857,17 +2498,7 @@ def _capillary_at(mode: MarcatiliMode, pressure: float) -> MarcatiliMode: species' own equation of state (optmat's); a selected air, two populations at one shared pressure, is frozen at that pressure. - Parameters - ---------- - mode : MarcatiliMode - The capillary, filled at any pressure. - pressure : float - Pressure in bar. - - Returns - ------- - MarcatiliMode - The same capillary at that pressure. + - ``pressure``: pressure in bar. """ # pressure_rdw and pressure_zdw build this mode at their one scalar temperature. temperature = cast("float", mode.temperature) @@ -2885,18 +2516,8 @@ def _search_grid( ) -> Float[np.ndarray, " n_samples"]: """Return the values a sampled root search evaluates its residual at. - Parameters - ---------- - low, high : float - Finite ordered ends of the search range. - samples : int, optional - Number of linear samples, and of logarithmic ones. - - Returns - ------- - numpy.ndarray - `samples` linear values, and as many logarithmic ones where ``low > 0``, - merged and ascending; both ends are among them. + - Returns: `samples` linear values, and as many logarithmic ones where ``low > 0``, + merged and ascending; both ends are among them. """ # The logarithmic grid resolves the low end of a range spanning decades (a # pressure from 1e-6 bar); the linear grid resolves structure at its high end. @@ -2941,28 +2562,8 @@ def _sampled_roots( point is a root only if the residual there is below :data:`_ROOT_RESIDUAL` of its larger value at the bracket's ends. A sample that is exactly zero is a root. - Parameters - ---------- - residual : callable - A function of the parameter returning a finite float, or None where the - target is undefined (an inadmissible state, such as no soliton). - limits : tuple of float - Finite ordered ends of the search range. - what : str - What the residual is, for the refusals. - narrow : str - What the caller narrows to resolve a bracket, for the refusals. - - Returns - ------- - _Sampled - The roots, and the samples. - - Raises - ------ - ToolsError - If the residual is not finite at a value, or a refinement reaches a value - where it is undefined. + - ``residual``: a function of the parameter returning a finite float, or None where + the target is undefined (an inadmissible state, such as no soliton). """ low, high = limits grid = _search_grid(low, high) @@ -2973,20 +2574,7 @@ def _sampled_roots( def checked(value: float) -> float | None: """Evaluate the residual once per value, refusing a value that is not finite. - Parameters - ---------- - value : float - Trial value of the parameter. - - Returns - ------- - float or None - Finite residual, or None where the target is undefined. - - Raises - ------ - ToolsError - If the residual is not finite. + - Returns: finite residual, or None where the target is undefined. """ if value not in seen: result = residual(value) @@ -2996,23 +2584,7 @@ def checked(value: float) -> float | None: return seen[value] def refine(value: float) -> float: - """Evaluate the residual inside one bracket, where it must be defined. - - Parameters - ---------- - value : float - Trial value of the parameter. - - Returns - ------- - float - The residual. - - Raises - ------ - ToolsError - If the bracket crossed a value where the target is undefined. - """ + """Evaluate the residual inside one bracket, where it must be defined.""" result = checked(value) if result is None: # The only undefined state a target has is the largest constituent's @@ -3060,23 +2632,10 @@ def _rdw_pressure_root( ) -> float: """Find a unique pressure root without crossing a sampled inadmissible region. - Parameters - ---------- - residual : callable - Mismatch in 1/m, or None only where no focusing discrete soliton exists. - limits : tuple of float - Positive ordered pressure bracket in bar. - - Returns - ------- - float - Unique pressure root in bar. - - Raises - ------ - ToolsError - If the bracket is invalid, the mismatch is nonfinite, a refinement enters - an inadmissible region, or there is no unique sampled root. + - ``residual``: mismatch in 1/m, or None only where no focusing discrete soliton + exists. + - ``limits``: positive ordered pressure bracket in bar. + - Returns: unique pressure root in bar. """ low, high = limits if not (math.isfinite(low) and math.isfinite(high) and 0 < low < high): @@ -3211,18 +2770,11 @@ def pressure_rdw( materials=materials, ) - def residual(pressure: float) -> float | None: + def _residual(pressure: float) -> float | None: """Return the mismatch at the target for one pressure. - Parameters - ---------- - pressure : float - Pressure in bar. - - Returns - ------- - float or None - Mismatch in 1/m, or None when the constituent does not exist. + - ``pressure``: pressure in bar. + - Returns: mismatch in 1/m, or None when the constituent does not exist. """ mode = _capillary_at(base_mode, pressure) gamma = None @@ -3232,7 +2784,7 @@ def residual(pressure: float) -> float | None: gamma = nonlinear_coefficient(mode, index, wavelength) return _rdw_residual(design, mode, gamma, 0.0) - return _rdw_pressure_root(residual, limits) + return _rdw_pressure_root(_residual, limits) def pressure_zdw( @@ -3302,18 +2854,11 @@ def pressure_zdw( ) omega = np.array([_omega(zdw_wavelength)]) - def residual(pressure: float) -> float: + def _residual(pressure: float) -> float: """Return the dispersion at the wavelength for one pressure. - Parameters - ---------- - pressure : float - Pressure in bar. - - Returns - ------- - float - ``beta_2`` in s^2/m, scaled to order one. + - ``pressure``: pressure in bar. + - Returns: ``beta_2`` in s^2/m, scaled to order one. """ mode = _capillary_at(base_mode, pressure) # Scaled so that brentq's absolute tolerance on the residual is meaningless @@ -3321,7 +2866,7 @@ def residual(pressure: float) -> float: return float(base.beta2(mode, omega)[0]) * 1e30 return _pressure_root( - residual, + _residual, pressure_max, f"a zero-dispersion wavelength of {zdw_wavelength * 1e9:.4g} nm", ) @@ -3670,13 +3215,7 @@ def pick(self, root: RootChoice = "lowest") -> float: return self.roots[root] def _unreachable(self) -> str: - """Return why no root was found. - - Returns - ------- - str - The search range and the attainable range of the quantity. - """ + """Return why no root was found.""" low, high = self.search if self.attainable is None: seen = f"the {self.quantity} is undefined across it" @@ -3694,15 +3233,7 @@ def _unreachable(self) -> str: def _describe(target: DesignTarget) -> str: """Return a target in words, for messages. - Parameters - ---------- - target : ZDW, RDW or SolitonOrder - The target. - - Returns - ------- - str - For example ``"a zero-dispersion wavelength of 500 nm"``. + - Returns: for example ``"a zero-dispersion wavelength of 500 nm"``. """ if isinstance(target, ZDW): return f"a zero-dispersion wavelength of {target.wavelength * 1e9:.6g} nm" @@ -3715,18 +3246,7 @@ def _describe(target: DesignTarget) -> str: def _quantity_name(target: DesignTarget) -> str: - """Return what a target's attainable range measures, with its unit. - - Parameters - ---------- - target : ZDW, RDW or SolitonOrder - The target. - - Returns - ------- - str - The quantity and its unit. - """ + """Return what a target's attainable range measures, with its unit.""" if isinstance(target, ZDW): return "zero-dispersion wavelength nearest the target (m)" if isinstance(target, RDW): @@ -3735,18 +3255,7 @@ def _quantity_name(target: DesignTarget) -> str: def _needs_gamma(target: DesignTarget) -> bool: - """Return whether a target needs the fill's nonlinear coefficient. - - Parameters - ---------- - target : ZDW, RDW or SolitonOrder - The target. - - Returns - ------- - bool - True for a soliton order and for an RDW with a peak power. - """ + """Return whether a target needs the fill's nonlinear coefficient.""" if isinstance(target, RDW): return target.peak_power is not None return isinstance(target, SolitonOrder) @@ -3755,24 +3264,9 @@ def _needs_gamma(target: DesignTarget) -> bool: def _pump_dispersion(mode: Mode, wavelength: float, z: float) -> float: """Return the group-velocity dispersion at a pump, refusing one not finite. - Parameters - ---------- - mode : Mode - The mode. - wavelength : float - Pump wavelength in m. - z : float - Position along the waveguide, m. - - Returns - ------- - float - ``beta_2`` in s^2/m. - - Raises - ------ - ToolsError - If it is not finite. + - ``wavelength``: pump wavelength in m. + - ``z``: position along the waveguide, m. + - Returns: ``beta_2`` in s^2/m. """ dispersion = float(base.beta2(mode, np.array([_omega(wavelength)]), z)[0]) if not math.isfinite(dispersion): @@ -3788,27 +3282,10 @@ def _rdw_residual( ) -> float | None: """Return the phase mismatch at a dispersive-wave target, as the RDW search uses it. - Parameters - ---------- - target : RDW - The target. - mode : Mode - The trial mode, carrying both the soliton and the wave. - gamma : float or None - The mode's nonlinear coefficient in rad/(W m); given with a peak power. - z : float - Position along the waveguide, m. - - Returns - ------- - float or None - Mismatch in 1/m, or None when the largest constituent does not exist: in - normal dispersion, or at a pulse area at or below the first threshold. - - Raises - ------ - ToolsError - If the pump dispersion is not finite. + - ``gamma``: the mode's nonlinear coefficient in rad/(W m); given with a peak power. + - ``z``: position along the waveguide, m. + - Returns: mismatch in 1/m, or None when the largest constituent does not exist: in + normal dispersion, or at a pulse area at or below the first threshold. """ if target.peak_power is not None and target.fwhm is not None: dispersion = _pump_dispersion(mode, target.pump, z) @@ -3843,22 +3320,10 @@ def _target_residual( ) -> float | None: """Return a target's residual on one trial mode. - Parameters - ---------- - target : ZDW, RDW or SolitonOrder - The target. - mode : Mode - The trial mode. - gamma : float or None - Its nonlinear coefficient in rad/(W m), where the target needs one. - z : float - Position along the waveguide, m. - - Returns - ------- - float or None - ``beta_2`` in s^2/m, the mismatch in 1/m (None where no soliton exists), or - the soliton order less the target's. + - ``gamma``: its nonlinear coefficient in rad/(W m), where the target needs one. + - ``z``: position along the waveguide, m. + - Returns: ``beta_2`` in s^2/m, the mismatch in 1/m (None where no soliton exists), + or the soliton order less the target's. """ if isinstance(target, ZDW): return float(base.beta2(mode, np.array([_omega(target.wavelength)]), z)[0]) @@ -3874,17 +3339,9 @@ def _target_residual( def _rdw_window(target: RDW) -> tuple[float, float]: """Return the window an RDW target's attainable wavelengths are searched in. - Parameters - ---------- - target : RDW - The target. - - Returns - ------- - tuple of float - :func:`lambda_rdw`'s default window, 100 nm to 0.9 of the pump, when the - target is on the blue side of the pump, widened to include it; and the - mirror of it, 1/0.9 of the pump to 3 um, on the red side. + - Returns: :func:`lambda_rdw`'s default window, 100 nm to 0.9 of the pump, when the + target is on the blue side of the pump, widened to include it; and the mirror of + it, 1/0.9 of the pump to 3 um, on the red side. """ if target.rdw < target.pump: return (min(_ZDW_RANGE[0], 0.9 * target.rdw), 0.9 * target.pump) @@ -3896,22 +3353,10 @@ def _target_quantity( ) -> float: """Return what a target measures on one trial mode, for the attainable range. - Parameters - ---------- - target : ZDW, RDW or SolitonOrder - The target. - mode : Mode - The trial mode. - gamma : float or None - Its nonlinear coefficient in rad/(W m), where the target needs one. - z : float - Position along the waveguide, m. - - Returns - ------- - float - The zero-dispersion or phase-matched wavelength nearest the target's in m, - or the soliton order; NaN where the mode has none. + - ``gamma``: its nonlinear coefficient in rad/(W m), where the target needs one. + - ``z``: position along the waveguide, m. + - Returns: the zero-dispersion or phase-matched wavelength nearest the target's in + m, or the soliton order; NaN where the mode has none. """ if isinstance(target, ZDW): return _zdw_near(mode, _omega(target.wavelength), z) @@ -3946,26 +3391,8 @@ def _built( ) -> tuple[Mode, float | None]: """Call a builder and check what it returned. - Parameters - ---------- - build : callable - The builder. - value : float - Trial value of the parameter. - target : ZDW, RDW or SolitonOrder - The target, which says whether a nonlinear coefficient is needed. - - Returns - ------- - tuple - The mode, and the nonlinear coefficient in rad/(W m), or None where the - target does not use one. - - Raises - ------ - ToolsError - If the builder returned neither a mode nor a mode and a positive - coefficient, or no coefficient for a target that needs one. + - Returns: the mode, and the nonlinear coefficient in rad/(W m), or None where the + target does not use one. """ result = build(value) if isinstance(result, tuple): @@ -4065,18 +3492,10 @@ def solve_parameter( raise ToolsError(f"z must be finite, got {z!r}") builds = 0 - def residual(value: float) -> float | None: + def _residual(value: float) -> float | None: """Build the mode at one value and return the target's residual. - Parameters - ---------- - value : float - Trial value of the parameter. - - Returns - ------- - float or None - The residual, or None where the target is undefined. + - Returns: the residual, or None where the target is undefined. """ nonlocal builds builds += 1 @@ -4084,11 +3503,11 @@ def residual(value: float) -> float | None: return _target_residual(target, mode, gamma, z) sampled = _sampled_roots( - residual, (low, high), what="the target's residual", narrow="search" + _residual, (low, high), what="the target's residual", narrow="search" ) # Outside the probing context, so a root beyond an index model's range warns. for root in sampled.roots: - residual(root) + _residual(root) attainable = None if not sampled.roots: # The modes are rebuilt rather than kept from the search, on a coarser grid: diff --git a/src/solprop/transverse_monitor.py b/src/solprop/transverse_monitor.py index f6077d99..8b8bbda8 100644 --- a/src/solprop/transverse_monitor.py +++ b/src/solprop/transverse_monitor.py @@ -99,17 +99,7 @@ def _ramp_band_message(share: float, *, stop: bool) -> str: """Return what a launch's share in the safety ramp's band reports. - Parameters - ---------- - share : float - The share of the noise-free launch's energy in the band. - stop : bool - Whether it is the stop's message, else the warning's. - - Returns - ------- - str - The message. + - ``stop``: whether it is the stop's message, else the warning's. """ if stop: return ( @@ -126,20 +116,7 @@ def _ramp_band_message(share: float, *, stop: bool) -> str: def _clip_message(clipped: float, face: str) -> str: - """Return the warning of a launch the aperture clips on a face. - - Parameters - ---------- - clipped : float - The launch's share in the outer :data:`GUARD` of the requested size there. - face : str - The face's name. - - Returns - ------- - str - The message. - """ + """Return the warning of a launch the aperture clips on a face.""" return ( f"the launch holds {clipped:.2e} of its energy in the outer " f"{GUARD:.0%} of the requested size on its {face} face: the " @@ -150,32 +127,13 @@ def _clip_message(clipped: float, face: str) -> str: def _insensitive_reason(allowance: float) -> str: """Return the ``transverse_reason`` of an edge the input noise leaves insensitive. - Parameters - ---------- - allowance : float - The noise's allowance, a share of the launch energy. - - Returns - ------- - str - The reason. + - ``allowance``: the noise's allowance, a share of the launch energy. """ return f"the input noise's allowance {allowance:.2e} exceeds the warning" def _insensitive_message(allowance: float) -> str: - """Return the warning of an edge the input noise leaves insensitive. - - Parameters - ---------- - allowance : float - The noise's allowance, a share of the launch energy. - - Returns - ------- - str - The message. - """ + """Return the warning of an edge the input noise leaves insensitive.""" return ( "the transverse edge cannot be judged below " f"{allowance:.2e} of the launch energy under this input " @@ -186,17 +144,8 @@ def _insensitive_message(allowance: float) -> str: def _coarse_message(longest: float, spacing: float) -> str: """Return the warning of a fixed schedule whose steps outrun the read spacing. - Parameters - ---------- - longest : float - The schedule's longest step, m. - spacing : float - The read spacing at the launch, m. - - Returns - ------- - str - The message. + - ``longest``: the schedule's longest step, m. + - ``spacing``: the read spacing at the launch, m. """ return ( f"the fixed schedule's longest step, {longest:.3e} m, is " @@ -209,19 +158,7 @@ def _coarse_message(longest: float, spacing: float) -> str: def _face_message(face: str, z: float, judged: float) -> str: """Return the warning of light first judged above the warning on a face. - Parameters - ---------- - face : str - The face's name. - z : float - Where, m. - judged : float - Its judged share. - - Returns - ------- - str - The message. + - ``z``: where, m. """ return ( f"light reached the transverse edge on its {face} face at " @@ -243,27 +180,9 @@ def _stop_message( ) -> str: """Return a face's stop message, naming a size and a sample count to try. - Parameters - ---------- - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The run's grid. - length : float - The guide's length, m. - stop : float - The stop, an energy share: ``(stop_factor * budget) ** 2``. - face : str - The face. - judged : float - Its judged share. - z : float - Where it stopped, m. - warned_at : float - Where the edge first warned, m; NaN where it did not. - - Returns - ------- - str - The message. + - ``length``: the guide's length, m. + - ``z``: where it stopped, m. + - ``warned_at``: where the edge first warned, m; NaN where it did not. """ first = warned_at if math.isfinite(warned_at) else z factor = length / max(first, 1e-300) @@ -291,19 +210,8 @@ def _stop_message( def _return_message(returned: float, z: float, stop: float | None) -> str: """Return what a requested collar's return reports. - Parameters - ---------- - returned : float - The returned share of the launch energy. - z : float - Where, m. - stop : float or None - The stop, for the stop's message; ``None`` for the warning's. - - Returns - ------- - str - The message. + - ``z``: where, m. + - ``stop``: the stop, for the stop's message; ``None`` for the warning's. """ if stop is not None: return ( @@ -321,17 +229,7 @@ def _return_message(returned: float, z: float, stop: float | None) -> str: def _ramp_removal_message(removed: float, stop: float | None) -> str: """Return what the safety ramp's removal reports. - Parameters - ---------- - removed : float - Its removal above the noise's share and declared content. - stop : float or None - The stop, for the stop's message; ``None`` for the warning's. - - Returns - ------- - str - The message. + - ``stop``: the stop, for the stop's message; ``None`` for the warning's. """ if stop is not None: return ( @@ -348,30 +246,12 @@ def _ramp_removal_message(removed: float, stop: float | None) -> str: def _log_declared(share: float) -> None: - """Log a declared launch's share in the ramp's band, which is not judged. - - Parameters - ---------- - share : float - The share. - """ + """Log a declared launch's share in the ramp's band, which is not judged.""" logger.info("the launch holds %.2e of its energy in the ramp's band", share) def _log_finish(status: str, largest: float, removed: float, reads: int) -> None: - """Log the edge's verdict at the end of a run. - - Parameters - ---------- - status : str - The transverse status. - largest : float - The largest judged share. - removed : float - The ramp's removal. - reads : int - The reads. - """ + """Log the edge's verdict at the end of a run.""" logger.info( "transverse edge: %s; largest judged share %.2e; the ramp removed %.2e; " "%d reads", @@ -469,16 +349,6 @@ def _row_energy(rows: np.ndarray) -> Float[np.ndarray, " n_rows"]: Double precision is summed as a real view by ``einsum``, without the temporaries of ``abs(E)**2``; a read makes these sums many times a run (section 7.7). - - Parameters - ---------- - rows : numpy.ndarray - Complex rows. - - Returns - ------- - numpy.ndarray - The per-row sums. """ if rows.dtype != np.complex128: return np.sum(np.abs(rows) ** 2, axis=1) @@ -491,21 +361,10 @@ def _axis_faces( ) -> tuple[_Face, _Face]: """Return an axis's low and high faces. - Parameters - ---------- - name : str - The axis, ``"x"`` or ``"y"``. - axis : numpy.ndarray - Its centred samples, m. - size : float - Its requested half-size, m. - flat : numpy.ndarray - Each node's coordinate along it, m. - - Returns - ------- - tuple of _Face - The two faces. + - ``name``: the axis, ``"x"`` or ``"y"``. + - ``axis``: its centred samples, m. + - ``size``: its requested half-size, m. + - ``flat``: each node's coordinate along it, m. """ outer = 0.5 * axis.size * float(axis[1] - axis[0]) low = _Face( @@ -522,18 +381,7 @@ def _axis_faces( def _faces(space: RadialGrid | FreeGrid | PlanarGrid) -> tuple[_Face, ...]: - """Return the faces of a grid's edge on its physical nodes or rows. - - Parameters - ---------- - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The grid. - - Returns - ------- - tuple of _Face - One radially, four on a Cartesian box, two on a line or a slab. - """ + """Return the faces of a grid's edge on its physical nodes or rows.""" if isinstance(space, RadialGrid): r = np.asarray(space.r) return ( @@ -555,18 +403,7 @@ def _faces(space: RadialGrid | FreeGrid | PlanarGrid) -> tuple[_Face, ...]: def _guard_width(space: RadialGrid | FreeGrid | PlanarGrid) -> float: - """Return the narrowest face's guard width, m. - - Parameters - ---------- - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The grid. - - Returns - ------- - float - ``GUARD`` times the smallest outer half-size. - """ + """Return the narrowest face's guard width, m.""" if isinstance(space, RadialGrid): return GUARD * space.outer_radius if isinstance(space, PlanarGrid): @@ -618,18 +455,7 @@ class _EdgeLine: def _requested_size(space: RadialGrid | FreeGrid | PlanarGrid) -> float: - """Return the requested half-size on the narrowest axis, m. - - Parameters - ---------- - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The run's grid. - - Returns - ------- - float - The size, m. - """ + """Return the requested half-size on the narrowest axis, m.""" if isinstance(space, RadialGrid): return space.radius if isinstance(space, PlanarGrid): @@ -648,23 +474,10 @@ def _edge_line( ) -> _EdgeLine: """Return one line's description, from its outward coordinate. - Parameters - ---------- - axis : str - The grid axis. - s : numpy.ndarray - The outward coordinate of each of its nodes, m. - measure : float - What a row's sum is multiplied by. - bounds : tuple of float - The return strip, ``(a, b)`` in `s`, m. - size : float - The requested half-size, m. - - Returns - ------- - solprop.transverse_monitor._EdgeLine - The description. + - ``axis``: the grid axis. + - ``s``: the outward coordinate of each of its nodes, m. + - ``bounds``: the return strip, ``(a, b)`` in `s`, m. + - ``size``: the requested half-size, m. """ a, b = bounds selected = np.nonzero((s >= a) & (s <= b))[0] @@ -703,17 +516,7 @@ def _edge_lines( ) -> tuple[_EdgeLine, ...]: """Return the lines a requested collar's strip and current run along. - Parameters - ---------- - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The run's grid. - design : solprop.spatial_absorber.SpatialAbsorberDesign or None - The run's transverse boundary. - - Returns - ------- - tuple of solprop.transverse_monitor._EdgeLine - One per direction along each axis; none without a collar. + - Returns: one per direction along each axis; none without a collar. """ if design is None or not design.has_collar: return () @@ -751,23 +554,7 @@ def _edge_lines( def _line_rows( space: RadialGrid | FreeGrid | PlanarGrid, line: _EdgeLine, n_pol: int, n_omega: int ) -> int: - """Return how many rows of values a line carries. - - Parameters - ---------- - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The run's grid. - line : solprop.transverse_monitor._EdgeLine - The line. - n_pol, n_omega : int - The state's polarisations and bins. - - Returns - ------- - int - The rows: one per polarisation and bin, and per transverse position across the - line, or per slab channel on the planar kind. - """ + """Return how many rows of values a line carries.""" planes = n_pol * n_omega if isinstance(space, RadialGrid): return planes @@ -777,20 +564,7 @@ def _line_rows( def _strip_batch(line: _EdgeLine, rows: int) -> int: - """Return how many of a line's rows one batch of its padded transform takes. - - Parameters - ---------- - line : solprop.transverse_monitor._EdgeLine - The line, with nodes in its strip. - rows : int - Its rows in all. - - Returns - ------- - int - The rows in a batch, under ``_STRIP_BATCH_BYTES``. - """ + """Return how many of a line's rows one batch of its padded transform takes.""" return min(rows, max(1, _STRIP_BATCH_BYTES // (16 * 2 * line.strip.size))) @@ -806,19 +580,7 @@ def _transverse_edge_bytes( the synthesis; and with a requested collar each line's nodes and window, and one padded buffer per distinct batch. A run reading no edge holds none of it. - Parameters - ---------- - space : solprop.grid.RadialGrid, solprop.grid.FreeGrid or PlanarGrid - The run's grid. - design : solprop.spatial_absorber.SpatialAbsorberDesign or None - Its transverse boundary. - n_pol, n_omega : int - The state's polarisations and bins. - - Returns - ------- - int - The bytes. + - Returns: the bytes. """ columns = int(np.asarray(space.spectral_weights).size) if isinstance(space, RadialGrid): @@ -840,18 +602,7 @@ def _transverse_edge_bytes( def _radial_inverse(space: RadialGrid) -> Float[np.ndarray, "n_r n_r"]: - """Return the radial grid's synthesis from its spectral columns to its nodes. - - Parameters - ---------- - space : solprop.grid.RadialGrid - The grid. - - Returns - ------- - numpy.ndarray - The dense matrix. - """ + """Return the radial grid's synthesis from its spectral columns to its nodes.""" hankel = space.hankel return ( hankel.inverse_scale * (hankel.j1[:, None] / hankel.j1[None, :]) * hankel.matrix @@ -895,21 +646,8 @@ def reading( ) -> _EdgeReading: """Return what the edge asked of a state. - Parameters - ---------- - state : numpy.ndarray - One batch member's spectral columns. - faces : bool, optional - Whether to read the nodes, the columns and any strip. - current : bool, optional - Whether to read a collar's inward current. - full : bool, optional - Whether every node is wanted, as a launch wants, not only the read's rows. - - Returns - ------- - solprop.transverse_monitor._EdgeReading - The reading. + - ``full``: whether every node is wanted, as a launch wants, not only the read's + rows. """ rate = self.current(state) if current else None if not faces: @@ -928,10 +666,7 @@ def reading( def synthesis(self) -> Float[np.ndarray, "n_r n_r"] | None: """Return a radial grid's synthesis from columns to nodes, or ``None``. - Returns - ------- - numpy.ndarray or None - The dense matrix; ``None`` on a grid whose transform is an FFT. + - Returns: the dense matrix; ``None`` on a grid whose transform is an FFT. """ if not isinstance(self.space, RadialGrid): return None @@ -945,22 +680,7 @@ def node_power( *, full: bool = True, ) -> Float[np.ndarray, " n_nodes"]: - """Return each physical node's (or row's) energy, over pol and frequency. - - Parameters - ---------- - state : numpy.ndarray - One batch member's spectral columns. - full : bool, optional - Whether every node is wanted. Otherwise radially the rows a read doesn't use - are not synthesised and are zero: a read happens many times a run, and the - full synthesis is an ``n_r``-square product per frequency. - - Returns - ------- - numpy.ndarray - ``|E|^2`` summed, unweighted, in the state's units squared. - """ + """Return each physical node's (or row's) energy, over pol and frequency.""" space = self.space flat = state.reshape(state.shape[0], -1) if isinstance(space, RadialGrid): @@ -993,16 +713,6 @@ def _fft_workers(self, cube: np.ndarray) -> int: A small transform is faster on one thread than on a pool woken beside the backend's own threads. - - Parameters - ---------- - cube : numpy.ndarray - The array transformed. - - Returns - ------- - int - The thread budget's threads, or 1 below ``2**18`` elements. """ return self._workers if cube.size >= _SMALL_TRANSFORM else 1 @@ -1010,18 +720,7 @@ def _fft_workers(self, cube: np.ndarray) -> int: def column_power( state: Complex[np.ndarray, "n_modes n_pol n_omega"], ) -> Float[np.ndarray, " n_columns"]: - """Return each spectral column's unweighted energy. - - Parameters - ---------- - state : numpy.ndarray - One batch member's spectral columns. - - Returns - ------- - numpy.ndarray - ``sum |E|^2`` over polarisation and frequency. - """ + """Return each spectral column's unweighted energy.""" return _row_energy(state.reshape(state.shape[0], -1)) def _axis_values( @@ -1029,16 +728,8 @@ def _axis_values( ) -> dict[str, np.ndarray]: """Return the field along each axis's nodes, line by line. - Parameters - ---------- - state : numpy.ndarray - One member's spectral columns. - - Returns - ------- - dict - By axis, the field on its nodes, ``(n_nodes, n_rows)`` with one row per - frequency, polarisation and transverse position (``sqrt(r) E`` radially). + - Returns: by axis, the field on its nodes, ``(n_nodes, n_rows)`` with one row + per frequency, polarisation and transverse position (``sqrt(r) E`` radially). """ space = self.space flat = state.reshape(state.shape[0], -1) @@ -1062,17 +753,6 @@ def strip( A Kaiser(8)-windowed transform along each face's normal, padded twice; the inward energy is that at ``|q| >= 16 / s`` travelling inward, where the window's leakage is about ``1e-6`` (section 5.3). - - Parameters - ---------- - state : numpy.ndarray - One member's spectral columns. - - Returns - ------- - numpy.ndarray - ``(inward, windowed)`` per line, unweighted by the launch; zero on a line - with too few nodes in the strip. """ values = self._axis_values(state) found = [] @@ -1100,16 +780,6 @@ def current(self, state: Complex[np.ndarray, "n_modes n_pol n_omega"]) -> float: ``J = Im(E* dE/dn) / k`` on the requested size's boundary, summed over its frequencies and lines, per metre of propagation, counting inward flow only (section 5.3). - - Parameters - ---------- - state : numpy.ndarray - One member's spectral columns. - - Returns - ------- - float - The current, unweighted by the launch. """ values = self._axis_values(state) total = 0.0 @@ -1221,13 +891,7 @@ def __post_init__(self) -> None: ) def _edge_tables(self) -> _EdgeTables: - """Return the weights a state's node energies are summed with. - - Returns - ------- - solprop.transverse_monitor._EdgeTables - The tables, over the grid's nodes (or rows). - """ + """Return the weights a state's node energies are summed with.""" weights = self._weights faces, clip = np.zeros((2, 4, weights.size)) for index, face in enumerate(self._faces): @@ -1248,13 +912,7 @@ def _edge_tables(self) -> _EdgeTables: return _EdgeTables(faces, clip, totals, self._rows_read()) def _rows_read(self) -> np.ndarray: - """Return the nodes (or rows) a read uses: the faces' and a collar's. - - Returns - ------- - numpy.ndarray - A mask over the nodes. - """ + """Return the nodes (or rows) a read uses: the faces' and a collar's.""" rows = np.zeros(self._weights.size, dtype=bool) for face in self._faces: rows |= face.edge @@ -1266,10 +924,7 @@ def _rows_read(self) -> np.ndarray: def _node_rate(self) -> Float[np.ndarray, " n_nodes"] | None: """Return a requested collar's power rate on the nodes (or rows), 1/m. - Returns - ------- - numpy.ndarray or None - The rate; ``None`` without a collar. On a slab it depends on y alone. + - Returns: the rate; ``None`` without a collar. On a slab it depends on y alone. """ if self.design is None or self.design.collar_rate is None: return None @@ -1281,18 +936,7 @@ def _node_rate(self) -> Float[np.ndarray, " n_nodes"] | None: def _column_power( self, columns: Float[np.ndarray, " n_columns"] ) -> Float[np.ndarray, " n_columns"]: - """Return each spectral column's energy, in the node reduction's units. - - Parameters - ---------- - columns : numpy.ndarray - Each column's unweighted energy, as a reading gives it. - - Returns - ------- - numpy.ndarray - Weighted ``|E|^2``, scaled so that its sum is the node reduction's total. - """ + """Return each spectral column's energy, in the node reduction's units.""" return self._columns * columns # ------------------------------------------------------------------ the record @@ -1437,22 +1081,7 @@ def _judge_ramp_band( *, declared: bool, ) -> None: - """Judge the noise-free launch's share in the safety ramp's band (5.3). - - Parameters - ---------- - columns : numpy.ndarray - Each column's weighted energy in the launch, without the input noise. - stop, warn : float - The stop and warning levels as energy shares. - declared : bool - Whether content above ``discard_above`` was declared. - - Raises - ------ - BoundaryError - Above the stop, unless declared. - """ + """Judge the noise-free launch's share in the safety ramp's band (5.3).""" if self.design is None: return total = float(np.sum(columns)) @@ -1474,18 +1103,7 @@ def _judge_ramp_band( ) def _collar_rate(self, reading: _EdgeReading) -> float: - """Return a requested collar's removal rate, as a share of the launch per m. - - Parameters - ---------- - reading : solprop.transverse_monitor._EdgeReading - The state's reading. - - Returns - ------- - float - ``sum alpha w |E|^2 / U_launch``; zero without a collar. - """ + """Return a requested collar's removal rate, as a share of the launch per m.""" if self.design is None or self.design.collar_rate is None: return 0.0 assert reading.totals is not None @@ -1494,17 +1112,9 @@ def _collar_rate(self, reading: _EdgeReading) -> float: def _reach(self, columns: Float[np.ndarray, " n_columns"]) -> float: """Return the reach ``v_q`` of the current spectrum. - Parameters - ---------- - columns : numpy.ndarray - One member's column energies (:meth:`_column_power`). - - Returns - ------- - float - The transverse group velocity at ``kappa_q``, the wavenumber above which - the field holds no more than the warning's share of the launch; zero when - nothing is above it. + - Returns: the transverse group velocity at ``kappa_q``, the wavenumber above + which the field holds no more than the warning's share of the launch; zero + when nothing is above it. """ if self._noise_columns is not None: columns = np.maximum(columns - self._noise_columns, 0.0) @@ -1530,13 +1140,6 @@ def _respace( all, starts it again (solprop issue #396). Growth is measured against the reach at that start, not at the last read, so that a creeping spectrum cannot double it forever. - - Parameters - ---------- - columns : numpy.ndarray - One member's column energies (:meth:`_column_power`). - launch : bool - Whether this is the launch (or a resumed one), which starts the backstop. """ reach = self._reach(columns) # A spectrum with no reach (nothing above the warning, so only under a budget @@ -1693,15 +1296,7 @@ def _strip_shares( The larger line's, as shares of the launch (section 5.3). - Parameters - ---------- - strip : numpy.ndarray - ``(inward, windowed)`` per line, as a reading gives it. - - Returns - ------- - tuple of float - The inward share and the windowed share. + - Returns: the inward share and the windowed share. """ best, windowed = 0.0, 0.0 for inward, total in strip: @@ -1763,15 +1358,7 @@ def _launch_return( reading: _EdgeReading, noise: Complex[np.ndarray, "n_modes n_pol n_omega"] | None, ) -> None: - """Record a collar's launch baselines and floors (section 5.3). - - Parameters - ---------- - reading : solprop.transverse_monitor._EdgeReading - The launched state's reading. - noise : numpy.ndarray or None - Its input noise. - """ + """Record a collar's launch baselines and floors (section 5.3).""" assert reading.strip is not None strip, _ = self._strip_shares(reading.strip) self._strip_baseline = strip @@ -1785,17 +1372,7 @@ def _launch_return( def _judge_return(self, reading: _EdgeReading, z: float) -> None: """Judge what a collar returns: the strip and the current (section 5.3). - Parameters - ---------- - reading : solprop.transverse_monitor._EdgeReading - The state's reading. - z : float - Its position, m. - - Raises - ------ - BoundaryError - When the returned share exceeds ``stop_factor`` budgets. + - ``z``: its position, m. """ assert reading.strip is not None strip, windowed = self._strip_shares(reading.strip) @@ -1824,11 +1401,6 @@ def _judge_ramp_removal(self) -> None: The ramp's removal tracks the true error of an under-resolved run within a few times, and the ramp can hide that light from the edge; it is judged at the edge's levels (the maintainer's decision on Q8). - - Raises - ------ - BoundaryError - When the removal exceeds ``stop_factor`` budgets. """ removed = self.ramp_removed stop = (self.stop_factor * self.budget) ** 2 @@ -1847,19 +1419,7 @@ def _judge_ramp_removal(self) -> None: def _stop_message(self, face: str, judged: float, z: float) -> str: """Return the stop's message, naming a size and a sample count to try. - Parameters - ---------- - face : str - The face. - judged : float - Its judged share. - z : float - Where it stopped, m. - - Returns - ------- - str - The message. + - ``z``: where it stopped, m. """ return _stop_message( self.space, @@ -1911,68 +1471,44 @@ def resume(self, record: dict[str, object]) -> None: The ``transverse`` entry of the earlier monitor record. """ - def number(name: str, default: float = 0.0) -> float: - """Return a recorded number, or `default`. - - Parameters - ---------- - name : str - The field. - default : float, optional - What an absent field stands for. - - Returns - ------- - float - The value. - """ + def _number(name: str, default: float = 0.0) -> float: + """Return a recorded number, or `default`.""" value = record.get(name) if isinstance(value, bool) or not isinstance(value, int | float): return default return float(value) - def numbers(name: str) -> list[float] | None: - """Return a recorded per-face list, or ``None`` if it does not fit. - - Parameters - ---------- - name : str - The field. - - Returns - ------- - list of float or None - One value per face. - """ + def _numbers(name: str) -> list[float] | None: + """Return a recorded per-face list, or ``None`` if it does not fit.""" value = record.get(name) if not isinstance(value, list) or len(value) != len(self._faces): return None return [float(cast("float", one)) for one in cast("list[object]", value)] - launch = number("launch_energy", math.nan) - baseline = numbers("edge_baseline") + launch = _number("launch_energy", math.nan) + baseline = _numbers("edge_baseline") if not launch > 0.0 or baseline is None: return self._resumed = True self._launch = launch self._baseline = baseline - self._peak = numbers("edge_max") or self._peak - self._peak_raw = numbers("edge_max_raw") or self._peak_raw - self._allowance = number("noise_allowance") - self._ramp_raw = number("raw_ramp_removed") - self._ramp_noise = number("noise_ramp_removed") - self._noise_share_in_ramp = number("noise_share_in_ramp") - self._declared_share = number("declared_share_in_ramp") - self._spatial_removed = number("spatial_removed") - self._current = number("current") - self._current_baseline = number("current_baseline") - self._strip_baseline = number("strip_baseline") - self._strip_floor = number("strip_floor") - self._strip_peak = number("strip_max") - self._returned_peak = number("returned_max") - self._reads = int(number("reads")) - self._largest_gap = number("largest_gap") - self._warned_at = number("warned_at", math.nan) + self._peak = _numbers("edge_max") or self._peak + self._peak_raw = _numbers("edge_max_raw") or self._peak_raw + self._allowance = _number("noise_allowance") + self._ramp_raw = _number("raw_ramp_removed") + self._ramp_noise = _number("noise_ramp_removed") + self._noise_share_in_ramp = _number("noise_share_in_ramp") + self._declared_share = _number("declared_share_in_ramp") + self._spatial_removed = _number("spatial_removed") + self._current = _number("current") + self._current_baseline = _number("current_baseline") + self._strip_baseline = _number("strip_baseline") + self._strip_floor = _number("strip_floor") + self._strip_peak = _number("strip_max") + self._returned_peak = _number("returned_max") + self._reads = int(_number("reads")) + self._largest_gap = _number("largest_gap") + self._warned_at = _number("warned_at", math.nan) self._warned = math.isfinite(self._warned_at) status = record.get("transverse_status") if isinstance(status, str) and status in ( From de55c2355f738aac43fef6acf6af2b66c609bcc4 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:15:15 +0000 Subject: [PATCH 10/50] Restore conventions, refusals and reasons in solprop's top-level private docs The slimming dropped some Raises and Returns prose that said why a value is refused or what a result means: the reference length's NaN and amplification hazards, the absorber's no-gain rule, the exit status after a late signal, the Taylor phase units, the aligner's refusals, why identities use float.hex, why between-plane statistics are refused at construction, the negative-fluence refusal, the aperture weight's zeros, the mixture-flow refusal, the Gaussian order range and why radial reads skip unused rows. Stokes is capitalised again. Co-Authored-By: Claude Opus 5.5 --- src/solprop/boundaries.py | 4 ++++ src/solprop/cli.py | 2 ++ src/solprop/gasflow.py | 2 ++ src/solprop/linear.py | 3 +++ src/solprop/polarisation.py | 2 +- src/solprop/profiles.py | 6 +++++- src/solprop/pulses.py | 5 +++++ src/solprop/spatial.py | 8 +++++++- src/solprop/stats.py | 7 ++++++- src/solprop/tools.py | 5 ++++- src/solprop/transverse_monitor.py | 7 ++++++- 11 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/solprop/boundaries.py b/src/solprop/boundaries.py index 0d778ae8..b8829bcd 100644 --- a/src/solprop/boundaries.py +++ b/src/solprop/boundaries.py @@ -518,6 +518,10 @@ def _reference_length( ) -> float: """Return the absorber 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. + - ``boundary_length``: an explicit reference length in metres, which wins if given. """ if boundary_length is not None: diff --git a/src/solprop/cli.py b/src/solprop/cli.py index 286332f7..36217aa6 100644 --- a/src/solprop/cli.py +++ b/src/solprop/cli.py @@ -134,6 +134,8 @@ def _stopped(received: list[int], output: Path) -> int: """Report how a run ended and return the exit status. - ``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 diff --git a/src/solprop/gasflow.py b/src/solprop/gasflow.py index c4e0fe11..ddb09283 100644 --- a/src/solprop/gasflow.py +++ b/src/solprop/gasflow.py @@ -237,6 +237,8 @@ class _FlowGas: def of(cls, gas: Composition | Mixture, temperature: float) -> "_FlowGas": """Evaluate the properties of a gas or a shared mixture fill. + A mixture with independent populations is refused: it is not one flow. + - ``temperature``: temperature in K. """ composition = _as_composition(gas) diff --git a/src/solprop/linear.py b/src/solprop/linear.py index 6510836b..47f3cf3d 100644 --- a/src/solprop/linear.py +++ b/src/solprop/linear.py @@ -464,6 +464,9 @@ def _check_absorber( ) -> Float[np.ndarray, " n_omega"]: """Validate a spectral absorber rate against the grid it belongs to. + A negative rate anywhere is refused: an absorber removes energy, and a negative + rate is gain wearing a boundary's clothes. + - ``grid``: the grid it must match. """ values = np.asarray(absorber_rate, dtype=float) diff --git a/src/solprop/polarisation.py b/src/solprop/polarisation.py index 02c57ed7..85bfc109 100644 --- a/src/solprop/polarisation.py +++ b/src/solprop/polarisation.py @@ -88,7 +88,7 @@ def _complex_array( def _stokes_vector(value: ArrayLike) -> Float[np.ndarray, " 4"]: """Validate the Stokes cone with arithmetic roundoff at the surface. - - ``value``: stokes vector of shape ``(4,)`` in squared field units. + - ``value``: Stokes vector of shape ``(4,)`` in squared field units. """ array = np.asarray(value) if np.iscomplexobj(array) or array.shape != (4,) or not np.all(np.isfinite(array)): diff --git a/src/solprop/profiles.py b/src/solprop/profiles.py index 5cbfd3d6..cec65b94 100644 --- a/src/solprop/profiles.py +++ b/src/solprop/profiles.py @@ -3425,7 +3425,11 @@ def as_profile( def _number_identity(value: float) -> str: - """Return the exact hexadecimal literal of a double.""" + """Return the exact hexadecimal literal of a double. + + ``float.hex`` is exact, round-trips, and is independent of + ``numpy.set_printoptions``. + """ return float(value).hex() diff --git a/src/solprop/pulses.py b/src/solprop/pulses.py index 0ad2cde0..e4a0ed2e 100644 --- a/src/solprop/pulses.py +++ b/src/solprop/pulses.py @@ -737,6 +737,9 @@ def of( ) -> "_Aligner | None": """Return a beam's aligner, refusing what the launch cannot carry. + Refused: a radial grid, a y component on a line grid, a tilt outside the grid's + band, and a pulse-front delay longer than the time window. + - ``at``: where the medium is evaluated, m. - Returns: ``None`` for a beam neither displaced nor tilted. """ @@ -1397,6 +1400,8 @@ def scale(rows: slice) -> None: def _frozen_phase(phase: Sequence[float] | np.ndarray) -> Float[np.ndarray, " n_deg"]: """Return the Taylor coefficients as a validated read-only array. + - ``phase``: coefficients about the centre frequency, rad for order 0 and + ``s**n`` for order ``n`` thereafter. - Returns: a read-only copy, shape ``(n_deg,)``. """ coefficients = np.array(phase, dtype=np.float64).reshape(-1) diff --git a/src/solprop/spatial.py b/src/solprop/spatial.py index 034e1a68..f4dd14c3 100644 --- a/src/solprop/spatial.py +++ b/src/solprop/spatial.py @@ -2733,6 +2733,9 @@ def _aperture_weight( - ``k_x``, ``k_y``: the chunk's stored transverse wavevectors in rad/m, shape ``(n_chunk,)``. + - Returns: ``W_a`` of the column's image, exactly zero at and below zero frequency + and wherever the image lies at or beyond the mask plane's distance from the origin + (SEP 23 sections 4.2 and 4.10), shape ``(n_chunk, n_omega)``. """ scale = tables.scale[index] across, along = mask_plane_offsets(scale, k_x, k_y, tables.centres[index]) @@ -3339,9 +3342,12 @@ def _line_fluence_widths( def _beam_widths(spectrum: np.ndarray, flux: SpatialFlux) -> tuple[float, float, float]: """Reduce the signed fluence profile with its spatial measure. + A fluence negative beyond its rounding budget is refused: a width needs a + nonnegative profile, and a signed axial flux is not clipped. + - ``spectrum``: internal radial spectrum in V m or Cartesian spectrum in V/m, shape ``(n_modes, n_pol, n_omega)``. - - Returns: Second-moment radius, central 1/e^2 radius and FWHM in m. + - Returns: second-moment radius, central 1/e^2 radius and FWHM in m. """ _check_flux(spectrum, flux, planar=False) fluence, budget = _signed_fluence(spectrum, flux) diff --git a/src/solprop/stats.py b/src/solprop/stats.py index 075deb90..3a9e3174 100644 --- a/src/solprop/stats.py +++ b/src/solprop/stats.py @@ -2501,7 +2501,12 @@ def _check_names(names: Sequence[str]) -> None: def _check_between_planes(names: Sequence[str]) -> None: - """Check that every periodic statistic is one a backend evaluates between planes.""" + """Check that every periodic statistic is one a backend evaluates between planes. + + The refusal is by name and at construction because evaluating them anyway can cost + a fifth of a step: ``zdw`` is a root search per mode and does not belong on every + accepted step. + """ refused = sorted(name for name in names if not CATALOGUE[name].between_planes) if refused: available = sorted( diff --git a/src/solprop/tools.py b/src/solprop/tools.py index c857f1f7..95701500 100644 --- a/src/solprop/tools.py +++ b/src/solprop/tools.py @@ -538,7 +538,10 @@ def _scattering_angle( def _gaussian_eigenvalue(order: float) -> float: - """Solve the half-line scattering problem for the largest Gaussian eigenvalue.""" + """Solve the half-line scattering problem for the largest Gaussian eigenvalue. + + - ``order``: conventional soliton order, above the first Gaussian threshold. + """ # The omitted field area is <= 1e-13. Moving the boundary with the amplitude # avoids assuming that the Gaussian tail is negligible for every input N. extent = math.sqrt(2.0) * float(erfcinv(1e-13 / (order * math.sqrt(math.pi / 2.0)))) diff --git a/src/solprop/transverse_monitor.py b/src/solprop/transverse_monitor.py index 8b8bbda8..9fdb99a6 100644 --- a/src/solprop/transverse_monitor.py +++ b/src/solprop/transverse_monitor.py @@ -680,7 +680,12 @@ def node_power( *, full: bool = True, ) -> Float[np.ndarray, " n_nodes"]: - """Return each physical node's (or row's) energy, over pol and frequency.""" + """Return each physical node's (or row's) energy, over pol and frequency. + + - ``full``: whether every node is wanted. Otherwise, radially, the rows a read + doesn't use are not synthesised and are zero: a read happens many times a + run, and the full synthesis is an ``n_r``-square product per frequency. + """ space = self.space flat = state.reshape(state.shape[0], -1) if isinstance(space, RadialGrid): From 5621d75f674559ea123221ebd4ba2cd810e561fb Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:15:36 +0000 Subject: [PATCH 11/50] Slim private docstrings in solprop.processing Mechanical: python tools/docstring_diet.py src/solprop/processing --write --check --report --rename-closures The two summaries the tool skipped (D200) are shortened by hand: planes._source_part, scans._Builtin.reads_fields. Co-Authored-By: Claude Opus 5.5 --- src/solprop/processing/_examples.py | 112 +------- src/solprop/processing/axes.py | 174 ++---------- src/solprop/processing/beams.py | 73 +---- src/solprop/processing/compression.py | 387 ++++---------------------- src/solprop/processing/fields.py | 202 +++----------- src/solprop/processing/mixtures.py | 66 +---- src/solprop/processing/modal.py | 248 ++--------------- src/solprop/processing/plane_file.py | 76 +---- src/solprop/processing/planes.py | 194 ++----------- src/solprop/processing/scans.py | 160 ++--------- src/solprop/processing/spectra.py | 63 +---- src/solprop/processing/time.py | 189 ++----------- 12 files changed, 252 insertions(+), 1692 deletions(-) diff --git a/src/solprop/processing/_examples.py b/src/solprop/processing/_examples.py index daba6867..b48e38b6 100644 --- a/src/solprop/processing/_examples.py +++ b/src/solprop/processing/_examples.py @@ -72,21 +72,9 @@ def example_grid(kind: str = "envelope") -> TimeGrid: """Return the small grid the synthetic runs are built on. - Parameters - ---------- - kind : {'envelope', 'field'} - Which spectral layout to build. - - Returns - ------- - solprop.grid.TimeGrid - A grid over 400 nm to 2 um with a 400 fs window, small enough for a doctest. - - Examples - -------- - >>> from solprop.processing._examples import example_grid - >>> example_grid("field").kind - 'field' + - ``kind``: which spectral layout to build. + - Returns: a grid over 400 nm to 2 um with a 400 fs window, small enough for a + doctest. """ if kind == "field": return FieldGrid.from_limits(_LENGTH, _WAVELENGTH, _LIMITS, _TRANGE) @@ -104,27 +92,10 @@ def gvd_phase( duration ``tau0`` then has the closed form ``tau(z) = tau0 sqrt(1 + (beta2 z / tau0^2)^2)``. - Parameters - ---------- - grid : solprop.grid.TimeGrid - The grid the spectrum lives on. - beta2 : float - Group-velocity dispersion, s^2/m. - z : float - Propagation distance, m. - - Returns - ------- - numpy.ndarray - The phasor, shape ``(n_omega,)``, multiplying an internal-convention - spectrum. - - Examples - -------- - >>> from solprop.processing._examples import example_grid, gvd_phase - >>> grid = example_grid() - >>> gvd_phase(grid, 1e-27, 0.0).tolist() == [1] * grid.n_omega - True + - ``beta2``: Group-velocity dispersion, s^2/m. + - ``z``: propagation distance, m. + - Returns: the phasor, shape ``(n_omega,)``, multiplying an internal-convention + spectrum. """ return np.exp(-0.5j * beta2 * (grid.omega - grid.omega0) ** 2 * z) @@ -232,22 +203,6 @@ def example_general_output(n_z: int = 3) -> MemoryOutput: could be checked against. A reconstruction offered this record is refused as incomplete by :mod:`solprop.processing.modal`, which is where that branch is exercised. - - Parameters - ---------- - n_z : int - Number of saved planes, spread evenly from 0 to the grid's length. - - Returns - ------- - solprop.output.memory.MemoryOutput - An opened handler holding ``n_z`` planes of a general modal run. - - Examples - -------- - >>> from solprop.processing._examples import example_general_output - >>> example_general_output().metadata.modal["basis"] - 'general_modal' """ grid = example_grid("field") tau0 = _FWHM / (2.0 * np.sqrt(np.log(2.0))) @@ -323,19 +278,9 @@ def example_general_run() -> tuple[MemoryOutput, ModalTransformTables, ModalGuid ``numpy_ref`` with two saved planes. About a second, most of it the run's first compilation of its plans. - Returns - ------- - tuple - The opened in-memory output, the - :class:`~solprop.translate.modal_tables.ModalTransformTables` rebuilt from - the guide, and the :class:`~solprop.modes.guide.ModalGuide` itself. - - Examples - -------- - >>> from solprop.processing._examples import example_general_run - >>> run, tables, guide = example_general_run() - >>> run.metadata.modal["basis"] - 'general_modal' + - Returns: the opened in-memory output, the + :class:`~solprop.translate.modal_tables.ModalTransformTables` rebuilt from the + guide, and the :class:`~solprop.modes.guide.ModalGuide` itself. """ # Imported here: this module is otherwise backend-free, and only this helper # solves a provider and runs a propagation. @@ -403,25 +348,9 @@ def _example_rows( # noqa: PLR0917 - called positionally is what a real run's channel holds -- there the backend evaluates them inside its own step loop, here the closed-form propagation stands in for one. - Parameters - ---------- - grid : solprop.grid.TimeGrid - The grid the run is on. - spec : solprop.stats.StatsSpec - The statistics to evaluate. - seed : numpy.ndarray - The input plane. - beta2 : float - Group-velocity dispersion, s^2/m. - start : float - Position of the previous plane, m. - end : float - Position of this plane, m. - - Returns - ------- - solprop.output.schema.StepStats - Three rows, the last of them at the plane itself. + - ``beta2``: Group-velocity dispersion, s^2/m. + - ``start``: position of the previous plane, m. + - ``end``: position of this plane, m. """ positions = np.linspace(start, end, _ROWS_PER_PLANE + 1)[1:] rows = [ @@ -444,19 +373,8 @@ def _seed_field( ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: """Return the input plane: one Gaussian per mode and polarisation component. - Parameters - ---------- - grid : solprop.grid.TimeGrid - The grid to build on. - n_modes : int - Number of propagated modes. - n_pol : int - Number of polarisation components. - - Returns - ------- - numpy.ndarray - Stored spectrum, shape ``(n_modes, n_pol, n_omega)``, internal convention. + - Returns: stored spectrum, shape ``(n_modes, n_pol, n_omega)``, internal + convention. """ pulse = GaussianPulse(wavelength=_WAVELENGTH, fwhm=_FWHM, energy=_ENERGY) one = pulse.build(grid) diff --git a/src/solprop/processing/axes.py b/src/solprop/processing/axes.py index 8a61f7a1..0faee6ae 100644 --- a/src/solprop/processing/axes.py +++ b/src/solprop/processing/axes.py @@ -243,20 +243,7 @@ def spatial_grid_of(output: Output) -> RadialGrid | FreeGrid | PlanarGrid: def _space_from_record(record: object) -> RadialGrid | FreeGrid: """Rebuild a transverse grid from the record ``coupling.space_record`` writes. - Parameters - ---------- - record : object - ``{"__class__": "module.Name", ...}``. - - Returns - ------- - solprop.grid.RadialGrid or solprop.grid.FreeGrid - The grid. - - Raises - ------ - ProcessingError - If the record names no grid solprop rebuilds. + - ``record``: ``{"__class__": "module.Name", ...}``. """ fields = dict(cast("Mapping[str, object]", record)) name = cast("str", fields.pop("__class__", "")) @@ -521,22 +508,7 @@ def layout(output: Output) -> tuple[int, int, int, int]: def _nearest_plane(output: Output, z: float) -> int: """Return the index of the saved plane nearest a position. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - z : float - Position along the propagation, m. - - Returns - ------- - int - Index into the saved planes. - - Raises - ------ - solprop.exceptions.OutputError - If no plane has been saved. + - ``z``: position along the propagation, m. """ positions = output["z"] if positions.size == 0: @@ -563,24 +535,6 @@ def _refuse_by_basis(output: Output, name: str) -> None: Nothing changes for a run on an ordinary modal basis, which is every gas-filled guided run. Each public reader calls this once, under its own name; what it reads through internally is the ungated private reading, so no call is gated twice. - - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - name : str - The reader being asked for, which the refusal names. - - Raises - ------ - solprop.exceptions.OutputError - If the stored basis does not admit `name` - (:func:`~solprop.output.capability.reading_refusal`). It is an - :class:`~solprop.exceptions.OutputError` and not a - :class:`~solprop.exceptions.ProcessingError` because it is a statement about - the record, and because one gate raising two classes would make the refusal - harder to catch than to cause: :mod:`solprop.processing.modal` raises the - same class for the same text. """ metadata = output.metadata if metadata is None: @@ -595,29 +549,11 @@ def _plane_field( ) -> Complex[np.ndarray, "*planes n_modes n_pol n_omega"]: """Return the stored spectrum of the selected plane, or of every plane. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - plane : int or None - Plane index, negative from the end, or ``None``. - z : float or None - Plane position, m, or ``None``. - convention : {'optics', 'fft'} - Sign convention wanted. - - Returns - ------- - numpy.ndarray - Shape ``(n_z, n_modes, n_pol, n_omega)`` when no plane was selected, and - ``(n_modes, n_pol, n_omega)`` when one was. - - Raises - ------ - solprop.exceptions.OutputError - If both ``plane`` and ``z`` are given, if no plane has been saved, or if the - index is out of range; or if the selected plane, or for no selection any - plane, kept no field, naming the run's ``fields`` (SEP 22). + - ``plane``: plane index, negative from the end, or ``None``. + - ``z``: plane position, m, or ``None``. + - ``convention``: sign convention wanted. + - Returns: shape ``(n_z, n_modes, n_pol, n_omega)`` when no plane was selected, and + ``(n_modes, n_pol, n_omega)`` when one was. """ if plane is not None and z is not None: msg = f"give a plane index or a position, not both: plane={plane}, z={z}" @@ -646,17 +582,8 @@ def _plane_field( def _keep_index(grid: TimeGrid, band: Band) -> Int[np.ndarray, " n_x"]: """Return the indices of the bins to return, ascending in frequency. - Parameters - ---------- - grid : solprop.grid.TimeGrid - The grid the stored spectrum lives on. - band : {'active', 'all'} - Which part of the axis to keep. - - Returns - ------- - numpy.ndarray - Indices into the stored axis, in order of increasing frequency. + - ``band``: which part of the axis to keep. + - Returns: indices into the stored axis, in order of increasing frequency. """ order = spectral_order(grid) return order[band_mask(grid, band)[order]] @@ -670,27 +597,12 @@ def _apply_modes( ) -> tuple[Inexact[np.ndarray, "*selected"], bool]: """Select or reduce the mode axis, which is the third from the end. - Parameters - ---------- - values : numpy.ndarray - Array whose axes end in ``(n_modes, n_pol, n_x)``. - modes : int, sequence of int, 'sum' or None - The selection. - allow_sum : bool - Whether ``'sum'`` is meaningful for this quantity, which it is only for the ones - that are quadratic in the field. - - Returns - ------- - values : numpy.ndarray - The selection, with the mode axis dropped for a single mode or a sum. - mode_axis : bool - Whether a mode axis is still present. - - Raises - ------ - solprop.exceptions.ModeError - If a sum is asked for where it is meaningless, or an index is out of range. + - ``values``: array whose axes end in ``(n_modes, n_pol, n_x)``. + - ``allow_sum``: whether ``'sum'`` is meaningful for this quantity, which it is only + for the ones that are quadratic in the field. + - Returns ``values``: the selection, with the mode axis dropped for a single mode or + a sum. + - Returns ``mode_axis``: whether a mode axis is still present. """ _check_mode_selection(modes) n_modes = values.shape[-3] @@ -714,20 +626,7 @@ def _apply_modes( def _check_mode_index(mode: int, n_modes: int) -> None: - """Reject a mode index the run does not have. - - Parameters - ---------- - mode : int - The index offered. - n_modes : int - Number of propagated modes. - - Raises - ------ - solprop.exceptions.ModeError - If the index is out of range. - """ + """Reject a mode index the run does not have.""" if not -n_modes <= mode < n_modes: msg = f"mode {mode} is out of range for a run of {n_modes} modes" raise ModeError(msg) @@ -738,18 +637,10 @@ def _squeeze( ) -> Inexact[np.ndarray, "*squeezed"]: """Drop the polarisation axis, and the mode axis when present, if they are trivial. - Parameters - ---------- - values : numpy.ndarray - Array whose axes end in ``(n_modes?, n_pol, n_x)``. - mode_axis : bool - Whether a mode axis is present. - - Returns - ------- - numpy.ndarray - The array with trivial axes removed. The plane axis is never removed: asking for - every plane of a one-plane run still gives a plane axis. + - ``values``: array whose axes end in ``(n_modes?, n_pol, n_x)``. + - ``mode_axis``: whether a mode axis is present. + - Returns: the array with trivial axes removed. The plane axis is never removed: + asking for every plane of a one-plane run still gives a plane axis. """ if values.shape[-2] == 1: values = values[..., 0, :] @@ -766,20 +657,6 @@ def _check_choice(value: str, allowed: tuple[str, ...], what: str) -> None: The parameter is a plain string rather than the enumeration itself, exactly as :func:`solprop.fourier._check_kind` is, so that the refusal stays reachable in production, where no runtime type checker stands in front of it. - - Parameters - ---------- - value : str - The name offered. - allowed : tuple of str - The names accepted. - what : str - Name of the keyword, for the message. - - Raises - ------ - solprop.exceptions.ProcessingError - If the name is not one of ``allowed``. """ if value not in allowed: msg = f"{what} must be one of {allowed}, got {value!r}" @@ -789,15 +666,8 @@ def _check_choice(value: str, allowed: tuple[str, ...], what: str) -> None: def _check_mode_selection(modes: object) -> None: """Reject a mode selection that is none of the four forms. - Parameters - ---------- - modes : object - The selection offered: an index, a sequence of indices, ``'sum'`` or ``None``. - - Raises - ------ - solprop.exceptions.ModeError - If it is none of those. + - ``modes``: the selection offered: an index, a sequence of indices, ``'sum'`` or + ``None``. """ if modes is None or isinstance(modes, int) or modes == "sum": return diff --git a/src/solprop/processing/beams.py b/src/solprop/processing/beams.py index b9e37e8b..bd4a4e36 100644 --- a/src/solprop/processing/beams.py +++ b/src/solprop/processing/beams.py @@ -64,23 +64,7 @@ def _radial_space(output: Output) -> RadialGrid: - """Return a file's radial grid, from its record. - - Parameters - ---------- - output : solprop.output.schema.Output - The run's output. - - Returns - ------- - solprop.grid.RadialGrid - The grid. - - Raises - ------ - solprop.exceptions.ProcessingError - If the run is not on a radial grid. - """ + """Return a file's radial grid, from its record.""" try: space = spatial_grid_of(output) except ProcessingError: @@ -96,24 +80,10 @@ def _measured_plane( ) -> tuple[np.ndarray, SpatialFlux]: """Return one saved plane's spectrum and its measure, from the file alone. - Parameters - ---------- - output : solprop.output.schema.Output - The run's output. - plane : int or None - The plane, negative from the end; the last when neither it nor ``z`` is given. - z : float or None - Its position, m: the nearest saved plane. - - Returns - ------- - tuple - The stored spectrum, ``(n_r, n_pol, n_omega)``, and the plane's measure. - - Raises - ------ - solprop.exceptions.ProcessingError - If the file's record carries no plane measure. + - ``plane``: the plane, negative from the end; the last when neither it nor ``z`` is + given. + - ``z``: its position, m: the nearest saved plane. + - Returns: the stored spectrum, ``(n_r, n_pol, n_omega)``, and the plane's measure. """ positions = np.asarray(output["z"], dtype=float) if z is not None: @@ -145,17 +115,8 @@ def _on_axis( ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """Return the positive frequencies on a spectral axis, ascending. - Parameters - ---------- - omega : numpy.ndarray - The grid's angular frequencies, rad/s. - axis : {'f', 'omega', 'wavelength'} - The abscissa. - - Returns - ------- - tuple - Which bins, the abscissa and the Jacobian from per rad/s, all ascending. + - ``omega``: the grid's angular frequencies, rad/s. + - Returns: which bins, the abscissa and the Jacobian from per rad/s, all ascending. """ bins = np.flatnonzero(omega > 0) x, jacobian = spectral_axis(omega[bins], axis) @@ -313,21 +274,11 @@ def _per_solid_angle( spans :math:`2\pi k_r\,dk_r = k^2\cos\theta\,d\Omega`: its solid angle is :math:`2\pi k_r\,dk_r / (k^2\cos\theta)`, which grows towards grazing angles. - Parameters - ---------- - energy : numpy.ndarray - Each column's energy, J per unit of the spectral axis. - k_r : numpy.ndarray - The columns' transverse wavevectors, 1/m, each below ``omega / C``. - widths : numpy.ndarray - Their widths in ``k_r``, 1/m. - omega : float - The angular frequency, rad/s. - - Returns - ------- - tuple of numpy.ndarray - The angles, rad, and the energy per steradian there. + - ``energy``: each column's energy, J per unit of the spectral axis. + - ``k_r``: the columns' transverse wavevectors, 1/m, each below ``omega / C``. + - ``widths``: their widths in ``k_r``, 1/m. + - ``omega``: the angular frequency, rad/s. + - Returns: the angles, rad, and the energy per steradian there. """ sine = C * k_r / omega cosine = np.sqrt(1.0 - sine**2) diff --git a/src/solprop/processing/compression.py b/src/solprop/processing/compression.py index 916d2506..a6ac0ec5 100644 --- a/src/solprop/processing/compression.py +++ b/src/solprop/processing/compression.py @@ -152,13 +152,7 @@ class Compression(Displayed): table: tuple[tuple[int, float, float], ...] = () def _found_text(self) -> str: - """Return what the search found, in the units it is quoted in. - - Returns - ------- - str - The Taylor coefficients, the thickness, or the reflections and thickness. - """ + """Return what the search found, in the units it is quoted in.""" if self.kind == "taylor": return phase_text(self.coefficients) if isinstance(self.transfer, GratingPair): @@ -202,10 +196,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]] = [ ("found", self._found_text()), @@ -293,15 +284,8 @@ def __repr__(self) -> str: class _Pulse: """One plane of a run, held ready for repeated trial compressions. - Parameters - ---------- - grid : solprop.grid.TimeGrid - The grid. - stored : numpy.ndarray - Stored spectrum, internal convention, shape ``(n_modes, n_pol, n_omega)``, - already band-passed. - oversampling : int - Time-domain resampling factor used for every power evaluated. + - ``stored``: stored spectrum, internal convention, shape ``(n_modes, n_pol, + n_omega)``, already band-passed. """ def __init__( @@ -324,17 +308,7 @@ def _power( ) -> tuple[Float[np.ndarray, " n_t"], Float[np.ndarray, " n_t"]]: """Return the time axis and the power summed over modes and polarisation. - Parameters - ---------- - transfer : TransferFunction or None - Element applied first. - transform_limit : bool - Whether to discard the spectral phase first. - - Returns - ------- - tuple of numpy.ndarray - Time in s and power in W. + - Returns: time in s and power in W. """ t, amplitude = _to_time( self.grid, @@ -349,31 +323,16 @@ def _power( def power(self, transfer: TransferFunction | None) -> Float[np.ndarray, " n_t"]: """Return the summed power after an element. - Parameters - ---------- - transfer : TransferFunction or None - The element; ``None`` leaves the pulse as it is. - - Returns - ------- - numpy.ndarray - Power in W on :attr:`t`. + - ``transfer``: the element; ``None`` leaves the pulse as it is. + - Returns: power in W on :attr:`t`. """ return self._power(transfer)[1] def objective(self, transfer: TransferFunction) -> float: """Return the transform-limited peak power over the peak power after an element. - Parameters - ---------- - transfer : TransferFunction - The trial element. - - Returns - ------- - float - At least one for a phase-only element, and one at the transform limit; - infinite if the element leaves no power in the window. + - Returns: at least one for a phase-only element, and one at the transform + limit; infinite if the element leaves no power in the window. """ self.evaluations += 1 peak = float(np.max(self.power(transfer))) @@ -393,29 +352,7 @@ def result( ) -> Compression: """Assemble a result, measuring the pulse before and after. - Parameters - ---------- - kind : str - Which compressor. - transfer : TransferFunction - The optimal element. - coefficients : tuple of float, optional - Taylor coefficients found. - thickness : float, optional - Thickness found, m. - reflections : int, optional - Reflections found. - success : bool - Whether the search converged inside its bounds. - message : str - What to tell the user about the search. - table : tuple of tuple, optional - Per-count results of a mirror search. - - Returns - ------- - Compression - The result. A failed search is also logged at ``WARNING``. + - ``thickness``: thickness found, m. """ before, after = self.power(None), self.power(transfer) if not success: @@ -445,22 +382,7 @@ def _select_modes( ) -> Complex[np.ndarray, "n_selected n_pol n_omega"]: """Return the modes a compressor works on. - Parameters - ---------- - stored : numpy.ndarray - One plane, shape ``(n_modes, n_pol, n_omega)``. - modes : int, sequence of int or None - The selection. - - Returns - ------- - numpy.ndarray - The selected modes, mode axis kept. - - Raises - ------ - solprop.exceptions.ProcessingError - If an index is out of range. + - Returns: the selected modes, mode axis kept. """ if modes is None: return stored @@ -482,17 +404,6 @@ def _require_modal(output: Output) -> None: (:func:`solprop.output.capability.capability_of`). A general modal or spatial run's coordinates are not that, so it is refused by name rather than optimised on a quantity that is not a power. - - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - - Raises - ------ - solprop.exceptions.ProcessingError - If the output carries no metadata, or declares a ``general_modal`` or - ``spatial`` capability. """ if output.metadata is None: raise ProcessingError("output has no metadata") @@ -517,34 +428,8 @@ def _pulse_of( ) -> _Pulse: """Return the pulse a compressor works on, from either input form. - Parameters - ---------- - output : solprop.output.schema.Output or None - An opened output handler. - grid : solprop.grid.TimeGrid or None - The grid of a bare spectrum. - spectrum : numpy.ndarray or None - A stored spectrum in the internal convention, as a pulse's ``build(grid)`` - returns it: shape ``(n_omega,)`` or ``(n_modes, n_pol, n_omega)``. - plane : int - Plane of the output to take. - modes : int, sequence of int or None - Modes to work on. - bandpass : tuple of float, Passband, numpy.ndarray or None - Window applied to the spectrum first. - oversampling : int - Time-domain resampling factor. - - Returns - ------- - _Pulse - The plane, ready. - - Raises - ------ - solprop.exceptions.ProcessingError - Unless exactly one input form is given, or if the output is not an - orthogonal modal one (:func:`_require_modal`). + - ``spectrum``: a stored spectrum in the internal convention, as a pulse's + ``build(grid)`` returns it: shape ``(n_omega,)`` or ``(n_modes, n_pol, n_omega)``. """ mixed = "give an output, or a grid together with its stored spectrum, not both" if output is not None: @@ -673,26 +558,7 @@ def transform_limit( def _scan_then_refine( objective: Callable[[float], float], low: float, high: float ) -> tuple[float, bool, str]: - """Minimise a function of one bounded parameter that may have several minima. - - Parameters - ---------- - objective : callable - The function. - low : float - Lower bound. - high : float - Upper bound, above `low`. - - Returns - ------- - best : float - The minimiser. - success : bool - Whether the refinement converged with the minimiser inside the bounds. - message : str - What happened, in words. - """ + """Minimise a function of one bounded parameter that may have several minima.""" samples = np.linspace(low, high, _SCAN_POINTS) values = np.array([objective(float(sample)) for sample in samples]) centre = int(np.argmin(values)) @@ -730,17 +596,9 @@ def _gaussian_width( half-maximum width and a large RMS one, and the search bounds built on this width must contain the chirp of the whole pulse, not of its spike. - Parameters - ---------- - t : numpy.ndarray - Time axis, s. - power : numpy.ndarray - Power on it, W. - - Returns - ------- - float - ``tau0`` of ``exp(-t**2 / tau0**2)`` in s. + - ``t``: time axis, s. + - ``power``: power on it, W. + - Returns: ``tau0`` of ``exp(-t**2 / tau0**2)`` in s. """ from_fwhm = time.fwhm(t, power) / (2.0 * math.sqrt(math.log(2.0))) centre = time.moment(t, power) @@ -840,24 +698,13 @@ def compress_taylor( scale = math.sqrt(max(chirp, width_limit**2)) bounds = [bound_factor * scale**n for n in range(2, order + 1)] - def objective(dispersion: Sequence[float] | np.ndarray) -> float: - """Return the objective for trial dispersion coefficients. - - Parameters - ---------- - dispersion : sequence of float - Orders two and above. - - Returns - ------- - float - The transform-limited peak power over the trial's. - """ + def _objective(dispersion: Sequence[float] | np.ndarray) -> float: + """Return the objective for trial dispersion coefficients.""" return pulse.objective(TaylorPhase((0.0, 0.0, *dispersion), reference)) if order == 2: best, success, message = _scan_then_refine( - lambda gdd: objective([gdd]), -bounds[0], bounds[0] + lambda gdd: _objective([gdd]), -bounds[0], bounds[0] ) found = [best] else: @@ -865,10 +712,10 @@ def objective(dispersion: Sequence[float] | np.ndarray) -> float: edge = min(chirp, bounds[0]) start = min( ([-edge], [edge]), - key=lambda guess: objective([*guess, *([0.0] * (order - 2))]), + key=lambda guess: _objective([*guess, *([0.0] * (order - 2))]), ) result = differential_evolution( - objective, + _objective, [(-bound, bound) for bound in bounds], x0=[*start, *([0.0] * (order - 2))], seed=seed, @@ -975,26 +822,18 @@ def compress_material( ) reference = pulse.grid.reference_wavelength if wavelength is None else wavelength - def element(thickness: float) -> Material: + def _element(thickness: float) -> Material: """Return the material at one thickness. - Parameters - ---------- - thickness : float - Thickness in m. - - Returns - ------- - Material - The element. + - ``thickness``: thickness in m. """ return Material(medium, thickness, reference, pressure=pressure) best, success, message = _scan_then_refine( - lambda thickness: pulse.objective(element(thickness)), low, high + lambda thickness: pulse.objective(_element(thickness)), low, high ) return pulse.result( - "material", element(best), thickness=best, success=success, message=message + "material", _element(best), thickness=best, success=success, message=message ) @@ -1007,23 +846,8 @@ def _behind_mirrors( ) -> float: """Return the objective for one insertion of material behind some reflections. - Parameters - ---------- - pulse : _Pulse - The pulse being compressed. - mirrors : ChirpedMirror - The reflections. - glass : Medium - The material. - reference : float - Wavelength at which the material's group delay is removed, m. - thickness : float - Thickness in m. - - Returns - ------- - float - The transform-limited peak power over the trial's. + - ``reference``: wavelength at which the material's group delay is removed, m. + - ``thickness``: thickness in m. """ insertion = Material(glass, thickness, reference) return pulse.objective(Chain((mirrors, insertion))) @@ -1209,18 +1033,11 @@ def optimise_cep( working = pulse.grid reference = working.reference_wavelength - def peak(phase: float) -> float: + def _peak(phase: float) -> float: """Return minus the largest value of the real field at one added phase. - Parameters - ---------- - phase : float - Carrier-envelope phase added, rad. - - Returns - ------- - float - Minus the peak of the real field, in the square root of W. + - ``phase``: Carrier-envelope phase added, rad. + - Returns: minus the peak of the real field, in the square root of W. """ t, amplitude = _to_time( working, @@ -1235,9 +1052,11 @@ def peak(phase: float) -> float: return -float(np.max(np.real(amplitude[0, 0]))) samples = np.linspace(-math.pi, math.pi, _CEP_SCAN, endpoint=False) - start = float(samples[int(np.argmin([peak(float(sample)) for sample in samples]))]) + start = float(samples[int(np.argmin([_peak(float(sample)) for sample in samples]))]) step = 2.0 * math.pi / _CEP_SCAN - found = minimize_scalar(peak, bounds=(start - step, start + step), method="bounded") + found = minimize_scalar( + _peak, bounds=(start - step, start + step), method="bounded" + ) phase = math.remainder(float(found.x), 2.0 * math.pi) return CepOptimum( phase=phase, @@ -1249,17 +1068,8 @@ def peak(phase: float) -> float: def _pair_range(bounds: tuple[float, float], name: str) -> tuple[float, float]: """Require explicit finite, ordered nonnegative motor travel in metres. - Parameters - ---------- - bounds : tuple of float - Lower and upper stage coordinates in m. - name : str - Coordinate name for an error message. - - Returns - ------- - tuple of float - Validated lower and upper stage coordinates in m. + - ``bounds``: lower and upper stage coordinates in m. + - Returns: validated lower and upper stage coordinates in m. """ if ( len(bounds) != 2 @@ -1285,29 +1095,8 @@ def _pair_pulse( ) -> _Pulse: """Select the same pulse for every trial, before forming any power objective. - Parameters - ---------- - output : solprop.output.schema.Output or None - Orthogonal modal saved output. - grid : solprop.grid.TimeGrid or None - Grid of a raw spectrum, alternative to output. - spectrum : numpy.ndarray or None - Stored internal spectrum, shape (n_omega,) or (n_modes, n_pol, n_omega). - pair : GratingPair or PrismPair - Fixed optical settings including the selected working band. - plane : int - Saved plane index, negative from the end. - modes : int, sequence of int or None - Modes whose orthogonal powers are summed. - bandpass : tuple of float, Passband, numpy.ndarray or None - Additional fixed spectral window. - oversampling : int - Positive temporal resampling factor. - - Returns - ------- - _Pulse - The selected pulse, with all metrics using the same selection. + - ``spectrum``: stored internal spectrum, shape (n_omega,) or (n_modes, n_pol, + n_omega). """ if output is not None: _require_modal(output) @@ -1367,19 +1156,8 @@ def _pair_trial( ) -> float: """Count every trial, including expected geometric failures only. - Parameters - ---------- - pulse : _Pulse - Fixed selected pulse and objective evaluation counter. - element : callable - Map normalised stage coordinates to a physical pair. - coordinates : sequence of float - Normalised coordinates in the unit search interval or square. - - Returns - ------- - float - Transform-limited peak divided by trial peak, or infinity if infeasible. + - ``element``: map normalised stage coordinates to a physical pair. + - Returns: Transform-limited peak divided by trial peak, or infinity if infeasible. """ pulse.evaluations += 1 try: @@ -1394,15 +1172,10 @@ def _feasible_scan( ) -> tuple[float, float, bool]: """Scan a unit interval and refine only brackets with feasible endpoints. - Parameters - ---------- - objective : callable - Objective on normalised coordinates; infinity for infeasible trials. - - Returns - ------- - tuple - Best coordinate, objective value and convergence flag; infinity if all invalid. + - ``objective``: objective on normalised coordinates; infinity for infeasible + trials. + - Returns: best coordinate, objective value and convergence flag; infinity if all + invalid. """ samples = np.linspace(0.0, 1.0, _SCAN_POINTS) values = np.array([objective(float(sample)) for sample in samples]) @@ -1446,21 +1219,9 @@ def _pair_edge( ) -> float: """Evaluate one fixed edge of the normalised motor box. - Parameters - ---------- - objective : callable - Objective on two normalised coordinates. - fixed_axis : int - Index of the coordinate held fixed on this edge, zero or one. - edge : float - Fixed normalised coordinate, zero or one. - variable : float - Free coordinate on the fixed boundary, in [0, 1]. - - Returns - ------- - float - Objective on the fixed edge. + - ``objective``: objective on two normalised coordinates. + - ``edge``: fixed normalised coordinate, zero or one. + - ``variable``: free coordinate on the fixed boundary, in [0, 1]. """ point = [edge, variable] if fixed_axis == 0 else [variable, edge] return objective(point) @@ -1471,17 +1232,9 @@ def _pair_search( ) -> tuple[tuple[float, ...], bool, str]: """Search one or two normalised stage coordinates including their boundaries. - Parameters - ---------- - objective : callable - Objective on normalised coordinates; infinity for infeasible trials. - names : sequence of str - Names of the one or two fitted stage coordinates. - - Returns - ------- - tuple - Normalised coordinates, convergence flag and boundary/search message. + - ``objective``: objective on normalised coordinates; infinity for infeasible + trials. + - Returns: normalised coordinates, convergence flag and boundary/search message. """ if len(names) == 1: point, value, success = _feasible_scan(lambda value: objective([value])) @@ -1604,26 +1357,15 @@ def compress_gratings( oversampling=oversampling, ) - def element(coordinates: Sequence[float]) -> GratingPair: - """Map a normalised separation to its physical grating pair. - - Parameters - ---------- - coordinates : sequence of float - Normalised coordinates in the unit search interval or square. - - Returns - ------- - GratingPair or PrismPair - Pair at the requested physical stage position. - """ + def _element(coordinates: Sequence[float]) -> GratingPair: + """Map a normalised separation to its physical grating pair.""" return replace(pair, separation=low + (high - low) * coordinates[0]) coordinates, success, message = _pair_search( - lambda point: _pair_trial(pulse, element, point), ["separation"] + lambda point: _pair_trial(pulse, _element, point), ["separation"] ) return pulse.result( - "gratings", element(coordinates), success=success, message=message + "gratings", _element(coordinates), success=success, message=message ) @@ -1708,19 +1450,8 @@ def compress_prisms( oversampling=oversampling, ) - def element(coordinates: Sequence[float]) -> PrismPair: - """Move only the requested second-prism stage coordinates. - - Parameters - ---------- - coordinates : sequence of float - Normalised coordinates in the unit search interval or square. - - Returns - ------- - GratingPair or PrismPair - Pair at the requested physical stage position. - """ + def _element(coordinates: Sequence[float]) -> PrismPair: + """Move only the requested second-prism stage coordinates.""" settings = { name: low + (high - low) * coordinate for name, (low, high), coordinate in zip( @@ -1730,8 +1461,8 @@ def element(coordinates: Sequence[float]) -> PrismPair: return replace(pair, geometry=replace(pair.geometry, **settings)) coordinates, success, message = _pair_search( - lambda point: _pair_trial(pulse, element, point), names + lambda point: _pair_trial(pulse, _element, point), names ) return pulse.result( - "prisms", element(coordinates), success=success, message=message + "prisms", _element(coordinates), success=success, message=message ) diff --git a/src/solprop/processing/fields.py b/src/solprop/processing/fields.py index b7d4029d..64266e25 100644 --- a/src/solprop/processing/fields.py +++ b/src/solprop/processing/fields.py @@ -325,38 +325,9 @@ def _spectral_density( a spatial file each column's value is ``|E|^2`` in its own units, not a density in J. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - axis : {'wavelength', 'f', 'omega'} - The abscissa, in m, Hz or rad/s. - plane : int or None - Index of the plane wanted, negative from the end. - z : float or None - Position of the plane wanted, m. - modes : int, sequence of int, 'sum' or None - Which columns to return; ``'sum'`` adds their values. - band : {'active', 'all'} - Which part of the stored axis to use. - spec_range : tuple of float or None - Limits on the abscissa, in its own units. - resolution : float or None - Instrument resolution, in the units of the abscissa. - noise_floor : {'keep', 'subtract'} - Whether to remove the input noise's seed. - - Returns - ------- - x : numpy.ndarray - The abscissa, shape ``(n_x,)``, ascending. - density : numpy.ndarray - The value per unit of ``x``. - - Raises - ------ - solprop.exceptions.ProcessingError - As :func:`get_spectral_density`. + - ``axis``: the abscissa, in m, Hz or rad/s. + - ``z``: position of the plane wanted, m. + - ``band``: which part of the stored axis to use. """ _check_choice(noise_floor, NOISE_FLOORS, "noise_floor") grid = grid_of(output) @@ -403,19 +374,7 @@ def _check_spec_range( Without it the density came back empty, and a plot failed later inside NumPy with a message that named neither the range nor its units. - Parameters - ---------- - x : numpy.ndarray - The abscissa, ascending, in the units of ``axis``. - spec_range : tuple of float - The requested limits, in either order. - axis : {'wavelength', 'f', 'omega'} - The abscissa's name. - - Raises - ------ - solprop.exceptions.ProcessingError - If no sample of ``x`` lies within the limits. + - ``x``: the abscissa, ascending, in the units of ``axis``. """ low, high = min(spec_range), max(spec_range) if np.any((x >= low) & (x <= high)): @@ -626,31 +585,12 @@ def _power( file: for an internal caller that has already been gated under its own name, or that knows the basis is modal. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - plane : int or None - Index of the plane wanted, negative from the end. - z : float or None - Position of the plane wanted, m. - modes : int, sequence of int, 'sum' or None - Which columns to return; ``'sum'`` adds them. - oversampling : int - Factor by which to resample the time axis, at least 1. - bandpass : tuple of float, Passband, numpy.ndarray or None - A window applied before the transform. - transfer : TransferFunction or None - An optics-convention element applied to the spectrum. - transform_limit : bool - Whether to discard the spectral phase first. - - Returns - ------- - t : numpy.ndarray - Time axis, s. - power : numpy.ndarray - The squared modulus summed over polarisation, W on a modal file. + - ``z``: position of the plane wanted, m. + - ``oversampling``: factor by which to resample the time axis, at least 1. + - ``transfer``: an optics-convention element applied to the spectrum. + - Returns ``t``: time axis, s. + - Returns ``power``: the squared modulus summed over polarisation, W on a modal + file. """ _, t, amplitude = _plane_amplitude( output, @@ -728,22 +668,7 @@ def _seed_density( column measure of a spatial one. A modulus is convention-free, so the record's internal convention needs no conjugation. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - grid : solprop.grid.TimeGrid - The run's grid. - - Returns - ------- - numpy.ndarray - The density in J/(rad/s), shape ``(n_modes, n_pol, n_omega)``. - - Raises - ------ - solprop.exceptions.ProcessingError - If the run carries no noise record, or one of the ``'modified'`` model. + - Returns: the density in J/(rad/s), shape ``(n_modes, n_pol, n_omega)``. """ metadata = output.metadata record = None if metadata is None else metadata.noise @@ -769,16 +694,6 @@ def _fft_norm(grid: TimeGrid) -> float: The square root of the prefactor of :func:`solprop.fourier.spectral_density`, so that the modulus squared of the scaled spectrum is the energy density in J/(rad/s). - - Parameters - ---------- - grid : solprop.grid.TimeGrid - The grid the spectrum lives on. - - Returns - ------- - float - ``dt sqrt(2 / pi)`` on a field grid, ``dt / sqrt(2 pi)`` on an envelope grid. """ if grid.kind == "field": return grid.dt * np.sqrt(2.0 / np.pi) @@ -845,35 +760,16 @@ def _to_time( carrier is the caller's business, because doing it unconditionally would cost a complex multiply and the last bit of every power. - Parameters - ---------- - grid : solprop.grid.TimeGrid - The grid the spectrum lives on. - stored : numpy.ndarray - Stored spectrum, internal convention, shape ``(..., n_omega)``. - oversampling : int - Factor by which to resample the time axis, at least 1. - bandpass : tuple of float, Passband, numpy.ndarray or None - Wavelength limits, m, or a :class:`~solprop.processing.spectra.Passband` on any - axis, of an explicit window applied before the transform, or a ready-made window - on the stored axis. - transfer : TransferFunction or None - An optics-convention element applied to the spectrum, after the band pass and - the transform limit. - transform_limit : bool - Whether to discard the spectral phase first. - - Returns - ------- - t : numpy.ndarray - Time axis, s, centred on zero. - amplitude : numpy.ndarray - The complex amplitude, internal convention, in the square root of watts. - - Raises - ------ - solprop.exceptions.ProcessingError - If ``oversampling`` is below one. + - ``stored``: stored spectrum, internal convention, shape ``(..., n_omega)``. + - ``oversampling``: factor by which to resample the time axis, at least 1. + - ``bandpass``: wavelength limits, m, or a + :class:`~solprop.processing.spectra.Passband` on any axis, of an explicit window + applied before the transform, or a ready-made window on the stored axis. + - ``transfer``: an optics-convention element applied to the spectrum, after the band + pass and the transform limit. + - Returns ``t``: time axis, s, centred on zero. + - Returns ``amplitude``: the complex amplitude, internal convention, in the square + root of watts. """ if oversampling < 1: msg = f"oversampling must be at least 1, got {oversampling}" @@ -910,34 +806,16 @@ def _plane_amplitude( The one path from a saved run to the time domain, so that a power, a field and an autocorrelation all come from the same transform with the same normalisation. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - plane : int or None - Plane index, negative from the end, or ``None`` for every plane. - z : float or None - Plane position, m, or ``None``. - oversampling : int - Factor by which to resample the time axis, at least 1. - bandpass : tuple of float, Passband, numpy.ndarray or None - Wavelength limits, m, or a :class:`~solprop.processing.spectra.Passband` on any - axis, of an explicit window applied before the transform, or a ready-made window - on the stored axis. - transfer : TransferFunction or None - An optics-convention element applied to the spectrum. - transform_limit : bool - Whether to discard the spectral phase first. - - Returns - ------- - grid : solprop.grid.TimeGrid - The rebuilt grid. - t : numpy.ndarray - Time axis, s, centred on zero. - amplitude : numpy.ndarray - Complex amplitude with every axis kept, shape ``(..., n_modes, n_pol, n_t)``, in - the internal convention and the square root of watts. + - ``plane``: plane index, negative from the end, or ``None`` for every plane. + - ``z``: plane position, m, or ``None``. + - ``oversampling``: factor by which to resample the time axis, at least 1. + - ``bandpass``: wavelength limits, m, or a + :class:`~solprop.processing.spectra.Passband` on any axis, of an explicit window + applied before the transform, or a ready-made window on the stored axis. + - ``transfer``: an optics-convention element applied to the spectrum. + - Returns ``t``: time axis, s, centred on zero. + - Returns ``amplitude``: complex amplitude with every axis kept, shape ``(..., + n_modes, n_pol, n_t)``, in the internal convention and the square root of watts. """ grid = grid_of(output) stored = _plane_field(output, plane, z, convention="fft") @@ -1189,21 +1067,9 @@ def _gate_transform( ) -> "ShortTimeFFT": """Return the short-time transform of a Gaussian gate of the given width. - Parameters - ---------- - dt : float - Sample spacing of the field, s. - width : float - Full width at half maximum of the gate, s. - span : float - Time range the gate positions cover, s. - positions : int - How many positions to place across that range. - - Returns - ------- - scipy.signal.ShortTimeFFT - The transform, with its hop chosen to give about ``positions`` slices. + - ``dt``: sample spacing of the field, s. + - ``width``: full width at half maximum of the gate, s. + - ``span``: time range the gate positions cover, s. """ # scipy.signal takes half a second to import: only a spectrogram needs it. from scipy.signal import ShortTimeFFT # noqa: PLC0415 diff --git a/src/solprop/processing/mixtures.py b/src/solprop/processing/mixtures.py index c5a22ab5..1a56dfcf 100644 --- a/src/solprop/processing/mixtures.py +++ b/src/solprop/processing/mixtures.py @@ -221,19 +221,8 @@ def _modal_fluence( ) -> np.ndarray: """Reconstruct physical electric and magnetic fields from true-power spectra. - Parameters - ---------- - output : solprop.output.schema.Output - Saved planes in true-power units with reconstruction metadata. - record : collections.abc.Mapping - Saved per-plane power weights, physical synthesis and taper. - grid : solprop.grid.TimeGrid - Temporal grid defining frequency and Parseval weights. - - Returns - ------- - numpy.ndarray - Integrated Poynting flux, shape (n_z, n_nodes), in J/m^2. + - ``record``: saved per-plane power weights, physical synthesis and taper. + - Returns: integrated Poynting flux, shape (n_z, n_nodes), in J/m^2. """ weights = get_power_weight(output) indices = _plane_indices(output, record) @@ -267,17 +256,7 @@ def _modal_fluence( def _statistic(output: Output, name: str) -> np.ndarray: """Require an explicitly recorded statistic, keeping every population axis. - Parameters - ---------- - output : Output - Opened saved run. - name : str - Statistic name whose units and dimensions follow the catalogue. - - Returns - ------- - numpy.ndarray - Recorded values, including all population and node axes. + - Returns: recorded values, including all population and node axes. """ if name not in output.stat_names: raise ProcessingError(f"the run did not record {name}") @@ -285,18 +264,7 @@ def _statistic(output: Output, name: str) -> np.ndarray: def _record(output: Output) -> Mapping[str, object]: - """Return the run's mixture reconstruction data, refusing a run without it. - - Parameters - ---------- - output : Output - Opened saved run. - - Returns - ------- - Mapping - Material processing data in the units the record declares. - """ + """Return the run's mixture reconstruction data, refusing a run without it.""" metadata = output.metadata record = ( None if metadata is None else metadata.configuration.get("mixture_processing") @@ -309,17 +277,8 @@ def _record(output: Output) -> Mapping[str, object]: def _array(record: Mapping[str, object], key: str) -> np.ndarray: """Read either a live array or its JSON representation from a reloaded file. - Parameters - ---------- - record : Mapping - Material processing metadata. - key : str - Array name, including units where applicable. - - Returns - ------- - numpy.ndarray - Float64 values with their recorded shape. + - ``key``: array name, including units where applicable. + - Returns: Float64 values with their recorded shape. """ value = record[key] if isinstance(value, Mapping): @@ -330,17 +289,8 @@ def _array(record: Mapping[str, object], key: str) -> np.ndarray: def _plane_indices(output: Output, record: Mapping[str, object]) -> np.ndarray: """Locate exact saved planes without interpolating data from another position. - Parameters - ---------- - output : Output - Opened saved run with z in m. - record : Mapping - Material processing data with the requested save positions in m. - - Returns - ------- - numpy.ndarray - Integer indices into the recorded material positions. + - ``output``: opened saved run with z in m. + - ``record``: material processing data with the requested save positions in m. """ recorded = _array(record, "z_m") positions = np.asarray(output["z"]) diff --git a/src/solprop/processing/modal.py b/src/solprop/processing/modal.py index 30601e37..8bdff95f 100644 --- a/src/solprop/processing/modal.py +++ b/src/solprop/processing/modal.py @@ -83,22 +83,7 @@ def _require_general(output: Output, name: str) -> RunMetadata: """Return the record of a general modal run, refusing any other basis by name. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - name : str - The accessor being asked for, which the refusal names. - - Returns - ------- - solprop.output.schema.RunMetadata - The record, whose ``modal`` identity is not ``None``. - - Raises - ------ - solprop.exceptions.OutputError - If the handler holds no record, or its basis does not admit `name`. + - Returns: the record, whose ``modal`` identity is not ``None``. """ metadata = output.metadata if metadata is None: @@ -122,17 +107,7 @@ def _in_convention( optics convention by default, and this one does too, through the one sanctioned boundary (:func:`solprop.fourier.spectrum_to_optics`). - Parameters - ---------- - signal : numpy.ndarray - The analytic signal in the internal convention. - convention : {'optics', 'fft'} - What the caller asked for. - - Returns - ------- - numpy.ndarray - The same signal in that convention. + - Returns: the same signal in that convention. """ return fourier.to_optics(signal) if convention == "optics" else signal @@ -147,19 +122,7 @@ def _identity_leaves(value: object, prefix: str = "") -> Iterator[tuple[str, obj also what makes a stored block that is a subset of the expected one visible as the missing paths it is. - Parameters - ---------- - value : object - A mapping, a sequence or a scalar, as - :func:`solprop.translate.general.modal_identity` writes them and JSON - reads them back. - prefix : str, optional - The path of `value` itself; empty at the root. - - Yields - ------ - tuple - The dotted path and the leaf value at it. + - ``prefix``: the path of `value` itself; empty at the root. """ if isinstance(value, Mapping): for key, item in value.items(): @@ -189,19 +152,7 @@ def _identity_refusal( on the few it has: the file must say everything the rebuilt reference says, or what was not compared was not checked. - Parameters - ---------- - block : {'tables', 'guide'} - Which entry of ``metadata.modal`` is being checked, for the message. - expected : collections.abc.Mapping - The identity of what the caller supplied, rebuilt now. - stored : object - The identity the file recorded, read back from JSON. - - Returns - ------- - str or None - The refusal, or ``None`` when the two agree path for path. + - Returns: the refusal, or ``None`` when the two agree path for path. """ root = f"modal.{block}" if not isinstance(stored, Mapping): @@ -260,27 +211,9 @@ def _checked_tables( identities are always checked**: the guide is required and the gate has no narrower mode. - Parameters - ---------- - metadata : solprop.output.schema.RunMetadata - The record of a general modal run. - tables : solprop.translate.modal_tables.ModalTransformTables - The reference the caller rebuilt from the guide. - guide : solprop.modes.guide.ModalGuide - The guide it was rebuilt from. It is **required**: a caller that rebuilt - the tables has the guide, because that is what they were rebuilt from, and - a check that closes the hole only when it is asked for is not a gate. - - Returns - ------- - solprop.translate.modal_tables.ModalTransformTables - The same tables. - - Raises - ------ - solprop.exceptions.OutputError - Naming the first identity path that disagrees, in the tables or in the - guide. + - ``guide``: the guide it was rebuilt from. It is **required**: a caller that + rebuilt the tables has the guide, because that is what they were rebuilt from, and + a check that closes the hole only when it is asked for is not a gate. """ modal = metadata.modal assert modal is not None @@ -306,24 +239,7 @@ def _amplitudes( ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: """Return one plane's channel amplitudes in the internal convention. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - plane : int or None - Plane index, negative from the end. - z : float or None - Plane position, m. - - Returns - ------- - numpy.ndarray - The amplitudes, shape ``(n_modes, n_pol, n_omega)``, square root of watts. - - Raises - ------ - solprop.exceptions.OutputError - If neither a plane index nor a position selects exactly one plane. + - ``z``: plane position, m. """ if plane is None and z is None: msg = ( @@ -337,20 +253,9 @@ def _amplitudes( def _plane_position(output: Output, plane: int | None, z: float | None) -> float: """Return the position of the one saved plane a selection names, in m. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - plane : int or None - Plane index, negative from the end. - z : float or None - Plane position, m; the nearest saved plane is the one meant, as - :func:`_amplitudes` reads it. - - Returns - ------- - float - The stored position of that plane, m. + - ``z``: plane position, m; the nearest saved plane is the one meant, as + :func:`_amplitudes` reads it. + - Returns: the stored position of that plane, m. """ positions = output["z"] if z is not None: @@ -381,15 +286,7 @@ class _PlaneReference: def _moves(tables: ModalTransformTables) -> bool: """Return whether a reference's maps are expanded along z (a moving guide). - Parameters - ---------- - tables : solprop.translate.modal_tables.ModalTransformTables - The run's reference. - - Returns - ------- - bool - True when the synthesis has more than one interval or coefficient in z. + - Returns: True when the synthesis has more than one interval or coefficient in z. """ synthesis = tables.synthesis return synthesis is not None and synthesis.shape[0] * synthesis.shape[1] > 1 @@ -398,26 +295,9 @@ def _moves(tables: ModalTransformTables) -> bool: def _moved_rule(guide: ModalGuide, position: float) -> tuple[float, ModalNodeRule]: """Return a moving guide's rule at a plane, and the position it was built at. - Parameters - ---------- - guide : solprop.modes.guide.ModalGuide - The moving guide the reference was rebuilt from. - position : float - The plane's own position, m. - - Returns - ------- - tuple - The position, snapped onto the path's end when within the edge slack, and - the provider's own rule there - (:func:`solprop.translate.modal_path.path_rule`). - - Raises - ------ - solprop.exceptions.OutputError - If the guide records no path. - solprop.exceptions.ModeError - If the provider refuses the position; the caller says which plane. + - ``position``: the plane's own position, m. + - Returns: the position, snapped onto the path's end when within the edge slack, and + the provider's own rule there (:func:`solprop.translate.modal_path.path_rule`). """ sampling = guide.sampling if sampling is None or sampling.path is None: @@ -442,24 +322,8 @@ def _plane_rule( ) -> tuple[Float[np.ndarray, "n_nodes 2"], Float[np.ndarray, " n_nodes"]]: """Return the nodes and area weights a plane at `position` is reconstructed on. - Parameters - ---------- - tables : solprop.translate.modal_tables.ModalTransformTables - The run's reference, already checked against the file. - guide : solprop.modes.guide.ModalGuide - The guide it was rebuilt from. - position : float - The plane's own position, m. - - Returns - ------- - tuple of numpy.ndarray - Node positions, shape ``(n_nodes, 2)`` in m, and weights in m^2. - - Raises - ------ - solprop.exceptions.OutputError - If a moving guide records no path, or its provider refuses the position. + - ``position``: the plane's own position, m. + - Returns: node positions, shape ``(n_nodes, 2)`` in m, and weights in m^2. """ if not _moves(tables): return tables.node_xy[0], tables.weights[0] @@ -488,25 +352,7 @@ def _plane_reference( path's held-out accuracy, and H comes from the same solve; the expansion carries no H at all, which is why the solve is repeated rather than read. - Parameters - ---------- - tables : solprop.translate.modal_tables.ModalTransformTables - The run's reference, already checked against the file. - guide : solprop.modes.guide.ModalGuide - The guide it was rebuilt from. - position : float - The plane's own position, m. - - Returns - ------- - _PlaneReference - The reference and weights at that plane. - - Raises - ------ - solprop.exceptions.OutputError - If the guide records no path to solve along, or the provider refuses the - position. + - ``position``: the plane's own position, m. """ if not _moves(tables): return _PlaneReference( @@ -561,34 +407,9 @@ def _spectra( # noqa: PLR0917 - called positionally On a moving guide they are the plane's own modes at that plane's own nodes (:func:`_plane_reference`). - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler holding a general modal run. - tables : solprop.translate.modal_tables.ModalTransformTables - The run's own reference. - plane : int or None - Plane index, negative from the end. - z : float or None - Plane position, m. - name : str - The accessor being asked for, which a refusal names. - guide : solprop.modes.guide.ModalGuide - The guide the reference was rebuilt from, whose identity is checked - against the file's own. - - Returns - ------- - tuple of numpy.ndarray - The electric and magnetic spectra, each ``(n_nodes, 3, n_omega)``, and the - area weights of the nodes they live on, m^2. - - Raises - ------ - solprop.exceptions.OutputError - If the basis, the tables, the guide or the plane selection is wrong, or - the stored spectrum and the reference do not live on the same frequency - axis. + - ``z``: plane position, m. + - Returns: the electric and magnetic spectra, each ``(n_nodes, 3, n_omega)``, and + the area weights of the nodes they live on, m^2. """ metadata = _require_general(output, name) _checked_tables(metadata, tables, guide) @@ -782,21 +603,8 @@ def _longitudinal( ) -> Float[np.ndarray, "n_nodes n_t"]: """Return the longitudinal Poynting flux density of one E/H pair, in W/m^2. - Parameters - ---------- - electric : numpy.ndarray - The electric field or its analytic signal, shape ``(n_nodes, 3, n_t)``. - magnetic : numpy.ndarray - The magnetic field or its analytic signal, same shape. - cycle_averaged : bool - Whether to take ``Re(E x conj(H))/2``, which drops the sum-frequency term, - or the real product ``E x H``, which keeps it. - - Returns - ------- - numpy.ndarray - The flux density, shape ``(n_nodes, n_t)``, W/m^2. Negative values are - interference and are never clipped. + - Returns: the flux density, shape ``(n_nodes, n_t)``, W/m^2. Negative values are + interference and are never clipped. """ if cycle_averaged: cross = electric[:, 0] * np.conj(magnetic[:, 1]) - electric[:, 1] * np.conj( @@ -1160,16 +968,6 @@ def _require_identity_metric(metadata: RunMetadata) -> None: statistic carries the weighted value, and :func:`reconstructed_flux` here reconstructs the physical flux from the guide's stored reference, which needs no metric at all. - - Parameters - ---------- - metadata : solprop.output.schema.RunMetadata - The record of a general modal run. - - Raises - ------ - solprop.exceptions.OutputError - If the recorded basis metric is not the identity. """ modal = metadata.modal metric = modal.get("metric") if isinstance(modal, dict) else None diff --git a/src/solprop/processing/plane_file.py b/src/solprop/processing/plane_file.py index aaa52295..3d1614b1 100644 --- a/src/solprop/processing/plane_file.py +++ b/src/solprop/processing/plane_file.py @@ -56,15 +56,7 @@ def _record(plane: FieldPlane) -> dict[str, object]: """Return a plane as a tree of mappings, arrays and JSON-ready values. - Parameters - ---------- - plane : solprop.coupling.plane.FieldPlane - The plane, holding no live object. - - Returns - ------- - dict - One entry per part; a part the plane does not have is None. + - Returns: one entry per part; a part the plane does not have is None. """ metric = plane.metric basis = plane.basis @@ -116,23 +108,7 @@ def _record(plane: FieldPlane) -> dict[str, object]: def _plain(value: object) -> object: - """Return a NumPy scalar or array as the Python value JSON writes. - - Parameters - ---------- - value : object - A value `json` has no form for. - - Returns - ------- - object - The value as a Python scalar or nested list. - - Raises - ------ - TypeError - If it is not a NumPy value, which is what `json` expects of this hook. - """ + """Return a NumPy scalar or array as the Python value JSON writes.""" if isinstance(value, np.generic | np.ndarray): return value.tolist() msg = f"{type(value).__name__} has no form in a field-plane file" @@ -142,13 +118,8 @@ def _plain(value: object) -> object: def _write(group: h5py.Group, record: Mapping[str, object]) -> None: """Write a tree: arrays as datasets, mappings as groups, the rest as JSON. - Parameters - ---------- - group : h5py.Group - Where to write. - record : collections.abc.Mapping - The tree. A None is recorded with the scalars, so a part left out of a damaged - file is told apart from a part the plane does not have. + - ``record``: the tree. A None is recorded with the scalars, so a part left out of a + damaged file is told apart from a part the plane does not have. """ scalars: dict[str, object] = {} for name, value in record.items(): @@ -163,18 +134,7 @@ def _write(group: h5py.Group, record: Mapping[str, object]) -> None: def _read(group: h5py.Group) -> dict[str, object]: - """Read a tree :func:`_write` wrote. - - Parameters - ---------- - group : h5py.Group - Where to read. - - Returns - ------- - dict - The tree. - """ + """Read a tree :func:`_write` wrote.""" record = cast( "dict[str, object]", json.loads(cast("str", group.attrs[_SCALARS_KEY])) ) @@ -187,32 +147,10 @@ def _read(group: h5py.Group) -> dict[str, object]: def _plane(record: Mapping[str, object]) -> FieldPlane: - """Rebuild a plane from its tree. - - Parameters - ---------- - record : collections.abc.Mapping - As :func:`_record` returns it. - - Returns - ------- - solprop.coupling.plane.FieldPlane - The plane, validated by its constructor. - """ + """Rebuild a plane from its tree.""" def part(name: str) -> dict[str, object]: - """Return one mapping of the tree. - - Parameters - ---------- - name : str - Its name. - - Returns - ------- - dict - A copy, to take apart. - """ + """Return one mapping of the tree.""" return dict(cast("Mapping[str, object]", record[name])) basis = part("basis") diff --git a/src/solprop/processing/planes.py b/src/solprop/processing/planes.py index 5147b5f6..0241db10 100644 --- a/src/solprop/processing/planes.py +++ b/src/solprop/processing/planes.py @@ -71,25 +71,7 @@ def _source_part(source: object, name: str) -> object: - """Return an attribute of a ``source=`` simulation, or refuse an object without it. - - Parameters - ---------- - source : object - The :class:`solprop.driver.Simulation` handed in. - name : str - The attribute. - - Returns - ------- - object - Its value. - - Raises - ------ - ProcessingError - If `source` has no such attribute. - """ + """Return an attribute of a ``source=`` simulation, refusing one without it.""" if not hasattr(source, name): msg = f"source= is a Simulation; {type(source).__name__} has no {name}" raise ProcessingError(msg) @@ -99,24 +81,8 @@ def _source_part(source: object, name: str) -> object: def _selected(output: Output, plane: int, z: float | None) -> tuple[int, float]: """Return the index and stored position of the plane a selection names. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output. - plane : int - Plane index, negative from the end. - z : float or None - Position that must match a stored plane, m. - - Returns - ------- - tuple - The index counted from the start, and its stored position in m. - - Raises - ------ - ProcessingError - If the index is out of range, or no stored plane is at `z`. + - ``z``: position that must match a stored plane, m. + - Returns: the index counted from the start, and its stored position in m. """ positions = np.asarray(output["z"], dtype=float) if positions.size == 0: @@ -167,24 +133,7 @@ class FluenceMap: def _recorded_flux(output: Output, space: object, z: float) -> SpatialFlux: """Return the plane measure a spatial record holds, at one position. - Parameters - ---------- - output : solprop.output.schema.Output - A spatial run, opened. - space : object - Its transverse grid. - z : float - Position of the plane in m. - - Returns - ------- - solprop.spatial.SpatialFlux - The measure, with no medium: the record holds the coefficients it built. - - Raises - ------ - ProcessingError - If the record carries no plane measure. + - ``z``: position of the plane in m. """ metadata = cast("RunMetadata", output.metadata) spatial = metadata.spatial @@ -216,21 +165,8 @@ def _plane_fluence( The plane is paired with the measure its own record rebuilds at its position, and measured by the functions that measure a live one. - Parameters - ---------- - output : solprop.output.schema.Output - A spatial run, opened. - index : int - Index of the stored plane, from the start. - z : float - Its position in m. - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid or PlanarGrid - The run's transverse grid. - - Returns - ------- - numpy.ndarray - Fluence in J/m^2, shape ``(n_nodes,)``, flattened as the run's nodes are. + - ``z``: its position in m. + - Returns: fluence in J/m^2, shape ``(n_nodes,)``, flattened as the run's nodes are. """ field = _plane_field(output, index, None, convention="fft") flux = _recorded_flux(output, space, z) @@ -318,15 +254,8 @@ def fluence_map( def _lineage(metadata: RunMetadata) -> tuple[str, ...]: """Return the configuration hashes a run descends from, oldest first. - Parameters - ---------- - metadata : solprop.output.schema.RunMetadata - The run's record. - - Returns - ------- - tuple of str - Its parent's lineage and its parent, or empty for a run launched directly. + - Returns: its parent's lineage and its parent, or empty for a run launched + directly. """ reference = cast("Mapping[str, object]", metadata.reference) parent = reference.get("parent") @@ -343,20 +272,11 @@ def _lineage(metadata: RunMetadata) -> tuple[str, ...]: def _recorded_basis(metadata: RunMetadata, z: float) -> tuple[str, str]: """Return a run's route and the basis identity it recorded at a plane. - Parameters - ---------- - metadata : solprop.output.schema.RunMetadata - The run's record. - z : float - The stored plane's position, m. - - Returns - ------- - tuple of str - The route and the identity. A spatial or general basis is the same at every - plane; an ordinary mode set's plane that was not planned (a natural-step - save) has no recorded basis, and gets an identity that names it as - unrecorded, which no port matches. + - ``z``: the stored plane's position, m. + - Returns: the route and the identity. A spatial or general basis is the same at + every plane; an ordinary mode set's plane that was not planned (a natural-step + save) has no recorded basis, and gets an identity that names it as unrecorded, + which no port matches. """ record = cast("Mapping[str, object]", metadata.reference)["basis"] bases = cast("Mapping[str, object]", record) @@ -382,29 +302,9 @@ def _spatial_metric( ) -> tuple[PlaneMetric, SpatialMedium | None, AdiabaticAdmission | None]: """Return a spatial plane's admittance metric, medium and path certificate. - Parameters - ---------- - metadata : solprop.output.schema.RunMetadata - The run's record. - grid : solprop.grid.TimeGrid - Its temporal grid. - space : solprop.grid.RadialGrid or solprop.grid.FreeGrid - Its transverse grid. - z : float - The plane's position, m. - source : Simulation or None - The simulation that ran it, for its medium. - - Returns - ------- - tuple - The metric (with a resolved measure when the medium is at hand), the - medium or None, and the path certificate the record rebuilds, or None. - - Raises - ------ - ProcessingError - If the record has no plane measure, or `source` is another run. + - ``z``: the plane's position, m. + - Returns: the metric (with a resolved measure when the medium is at hand), the + medium or None, and the path certificate the record rebuilds, or None. """ spatial = cast("Mapping[str, object]", metadata.spatial) if "flux_plan" not in spatial: @@ -437,18 +337,6 @@ def _check_source(metadata: RunMetadata, record: RunMetadata) -> None: """Refuse a ``source=`` simulation that did not write this output. As a resume does (ADR-066): the two records' configuration hashes must be equal. - - Parameters - ---------- - metadata : solprop.output.schema.RunMetadata - The output's record. - record : solprop.output.schema.RunMetadata - The source simulation's. - - Raises - ------ - ProcessingError - If the hashes differ; the message names what changed. """ if metadata.configuration_hash() != record.configuration_hash(): msg = ( @@ -463,27 +351,9 @@ def _modal_profiles( ) -> ScalarModeBasis | None: """Return an ordinary modal plane's scalar profiles from its source simulation. - Parameters - ---------- - metadata : solprop.output.schema.RunMetadata - The output's record. - grid : solprop.grid.TimeGrid - Its temporal grid. - position : float - The plane's position, m. - source : Simulation - The simulation that wrote the output. - - Returns - ------- - solprop.coupling.ScalarModeBasis or None - The profiles; None for a mode family with angular structure, which has - none. - - Raises - ------ - ProcessingError - If `source` is not the run that wrote the output. + - ``position``: the plane's position, m. + - Returns: the profiles; None for a mode family with angular structure, which has + none. """ record = cast( "RunMetadata", @@ -506,29 +376,9 @@ def _general_parts( ) -> tuple[PlaneMetric, NodalField | None]: """Return a general modal plane's metric and, with its source, its E/H. - Parameters - ---------- - output : solprop.output.schema.Output - The opened output. - metadata : solprop.output.schema.RunMetadata - Its record. - index : int - The plane's index. - z : float - The plane's position, m. - source : Simulation or None - The simulation that ran it. - - Returns - ------- - tuple - The metric -- the identity, the source's dense Gram at the plane, or - unknown without a source -- and the nodal field or None. - - Raises - ------ - ProcessingError - If `source` carries no general guide or its reference is not the file's. + - ``z``: the plane's position, m. + - Returns: the metric -- the identity, the source's dense Gram at the plane, or + unknown without a source -- and the nodal field or None. """ modal = cast("Mapping[str, object]", metadata.modal) declared = cast("Mapping[str, object]", modal.get("metric") or {}).get("kind") diff --git a/src/solprop/processing/scans.py b/src/solprop/processing/scans.py index 0e3a20b2..7b07f603 100644 --- a/src/solprop/processing/scans.py +++ b/src/solprop/processing/scans.py @@ -159,18 +159,7 @@ class _Builtin: dtype: np.dtype = field(default_factory=lambda: np.dtype(np.float64)) def shape(self, metadata: RunMetadata) -> tuple[int, ...]: - """Return the result dimensions promised by this reduction. - - Parameters - ---------- - metadata : RunMetadata - Complete single-run metadata. - - Returns - ------- - tuple of int - Declared result dimensions. - """ + """Return the result dimensions promised by this reduction.""" if self.operation == "spectrum": return (len(_band_wavelength(metadata.grid, self.bounds)),) if self.operation == "field": @@ -190,50 +179,21 @@ def shape(self, metadata: RunMetadata) -> tuple[int, ...]: @property def reads_fields(self) -> bool: - """Return whether the reduction reads its plane's field, not statistics alone. + """Return whether the reduction reads its plane's field. - Returns - ------- - bool - False for the reductions of a statistic (``on_axis_intensity``, - ``beam_radius``), whose plane a scan need not keep the field of. + False for the reductions of a statistic (``on_axis_intensity``, + ``beam_radius``), whose plane a scan need not keep the field of. """ return self.operation not in _STATISTIC_OPERATIONS def __call__(self, output: Output) -> np.ndarray: - """Execute the declared worker calculation. - - Parameters - ---------- - output : Output - Output handler for reductions, or collected destination path for execution. - - Returns - ------- - object - Completed calculation or captured point failure. - """ + """Execute the declared worker calculation.""" return self.evaluate(output, self.plane) def evaluate(self, output: Output, plane: int) -> np.ndarray: """Evaluate the existing processing definition at one saved plane. - Parameters - ---------- - output : Output - Output handler for reductions, or collected destination path for execution. - plane : int - Saved plane in the internal Fourier convention, or its index. - - Returns - ------- - numpy.ndarray - Reduction value in its declared units. - - Raises - ------ - ProcessingError - If a reduction defined on modal amplitudes is asked of a spatial run. + - ``plane``: saved plane in the internal Fourier convention, or its index. """ if ( self.operation in _MODAL_OPERATIONS @@ -301,50 +261,17 @@ class _Custom: reads_fields: bool = True def shape(self, metadata: RunMetadata) -> tuple[int, ...]: - """Return the result dimensions promised by this reduction. - - Parameters - ---------- - metadata : RunMetadata - Complete single-run metadata. - - Returns - ------- - tuple of int - Declared result dimensions. - """ + """Return the result dimensions promised by this reduction.""" del metadata return self.dimensions def __call__(self, output: Output) -> np.ndarray: - """Execute the declared worker calculation. - - Parameters - ---------- - output : Output - Output handler for reductions, or collected destination path for execution. - - Returns - ------- - object - Completed calculation or captured point failure. - """ + """Execute the declared worker calculation.""" return self.function(output) def _metadata(output: Output) -> RunMetadata: - """Require the complete record needed to interpret a field. - - Parameters - ---------- - output : Output - Output handler for reductions, or collected destination path for execution. - - Returns - ------- - RunMetadata - Complete output metadata. - """ + """Require the complete record needed to interpret a field.""" if output.metadata is None: raise ProcessingError("a reduction requires output metadata") return output.metadata @@ -359,17 +286,8 @@ def _band_wavelength( with ``band='active'`` and ``spec_range=bounds``, computed by the same functions, so the abscissa matches the stored samples one for one. - Parameters - ---------- - record : GridRecord - The point's grid record. - bounds : tuple of float - Closed wavelength band, m. - - Returns - ------- - numpy.ndarray - Wavelengths of the retained samples, ascending, m. + - ``bounds``: closed wavelength band, m. + - Returns: wavelengths of the retained samples, ascending, m. """ grid = grid_from_record(record) # The active band excludes the zero and Nyquist bins of a field grid, where a @@ -382,19 +300,8 @@ def _band_wavelength( def _bounds(lo: float, hi: float, *, positive: bool) -> tuple[float, float]: """Validate ordered finite integration limits. - Parameters - ---------- - lo : float - Finite lower limit in the reducer units. - hi : float - Finite upper limit in the reducer units. - positive : bool - Whether the lower bound must also be positive. - - Returns - ------- - tuple of float - Validated lower and upper limits. + - ``positive``: whether the lower bound must also be positive. + - Returns: validated lower and upper limits. """ if not np.isfinite(lo) or not np.isfinite(hi) or lo >= hi or (positive and lo <= 0): raise ProcessingError("reduction bounds must be finite and increasing") @@ -626,17 +533,8 @@ def _provenance( ) -> dict[str, object]: """Record a reducer definition and its source availability. - Parameters - ---------- - reduction : Reduction - Reduction declaration including shape, dtype and units. - metadata : RunMetadata or None, optional - Point metadata used to resolve modal, radial or Cartesian field units. - - Returns - ------- - dict - Reducer source, parameters, units and dtype. + - ``reduction``: reduction declaration including shape, dtype and units. + - Returns: reducer source, parameters, units and dtype. """ function = reduction.function if isinstance(reduction, _Custom) else type(reduction) try: @@ -682,16 +580,9 @@ def _provenance( def _custom_parameters(reduction: _Custom) -> dict[str, object]: """Return a custom reduction's recorded parameters. - Parameters - ---------- - reduction : _Custom - The declaration. - - Returns - ------- - dict - Its shape, and ``reads_fields`` where it declares that it reads none: left - out at its default, so the provenance of every earlier reduction is unchanged. + - Returns: its shape, and ``reads_fields`` where it declares that it reads none: + left out at its default, so the provenance of every earlier reduction is + unchanged. """ parameters: dict[str, object] = {"shape": reduction.dimensions} if not reduction.reads_fields: @@ -702,15 +593,7 @@ def _custom_parameters(reduction: _Custom) -> dict[str, object]: def _callable_hash(reduction: Reduction) -> str | None: """Fingerprint serialised reducer parameters when standard pickle supports them. - Parameters - ---------- - reduction : Reduction - Callable with its declared storage contract. - - Returns - ------- - str or None - SHA-256 of its pickle, or None for Local-only callables. + - Returns: SHA-256 of its pickle, or None for Local-only callables. """ try: data = pickle.dumps(reduction) @@ -763,10 +646,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. """ # The file is not opened: reading a scan takes a shared lock, which blocks # while a controller is still writing it, and printing must never block. diff --git a/src/solprop/processing/spectra.py b/src/solprop/processing/spectra.py index 697815e5..045873d9 100644 --- a/src/solprop/processing/spectra.py +++ b/src/solprop/processing/spectra.py @@ -282,22 +282,8 @@ def _output_limits( ) -> tuple[float, float]: """Return the limits of the smoothed axis, clipped to the data. - Parameters - ---------- - x : numpy.ndarray - Input abscissa, increasing. - spec_range : tuple of float or None - Requested limits, in either order, or ``None`` for the whole axis. - - Returns - ------- - tuple of float - The lower and upper limits. - - Raises - ------ - solprop.exceptions.ProcessingError - If the requested range does not overlap the data. + - ``spec_range``: requested limits, in either order, or ``None`` for the whole axis. + - Returns: the lower and upper limits. """ if spec_range is None: return float(x[0]), float(x[-1]) @@ -319,15 +305,7 @@ def _output_limits( def _check_width(width: float) -> None: """Refuse a taper width that is not positive. - Parameters - ---------- - width : float - Width of each taper edge, rad/s. - - Raises - ------ - solprop.exceptions.ProcessingError - If the width is not positive. + - ``width``: width of each taper edge, rad/s. """ if width <= 0.0: msg = f"the taper width must be positive, in rad/s, got {width!r}" @@ -337,19 +315,8 @@ def _check_width(width: float) -> None: def _check_limits(limits: tuple[float, ...], axis: SpectralAxis) -> None: """Refuse limits that name no light on their axis. - Parameters - ---------- - limits : tuple of float - The pass band, in m, Hz or rad/s as ``axis`` says. - axis : {'wavelength', 'f', 'omega'} - The axis of ``spec_range``. - - Raises - ------ - solprop.exceptions.ProcessingError - If ``limits`` does not hold two values, a wavelength limit is not positive, or - a frequency limit is negative; zero frequency is allowed, as the lower edge of - a low-pass band. + - ``limits``: the pass band, in m, Hz or rad/s as ``axis`` says. + - ``axis``: the axis of ``spec_range``. """ if len(limits) != 2: unit = _LIMIT_UNITS[axis] @@ -370,23 +337,9 @@ def _as_window(grid: TimeGrid, spec: Bandpass) -> Float[np.ndarray, " n_omega"]: taper; an array is taken as it stands, which is how a caller uses a different profile, or a window it built once and reuses. - Parameters - ---------- - grid : solprop.grid.TimeGrid - The grid whose ``omega`` axis the window lives on. - spec : tuple of float, Passband or numpy.ndarray - The pass band in wavelength, m, the pass band on any axis, or the window - itself, shape ``(n_omega,)``. - - Returns - ------- - numpy.ndarray - The window, shape ``(n_omega,)``, in storage order. - - Raises - ------ - solprop.exceptions.ProcessingError - If an array is offered whose length is not the grid's. + - ``grid``: the grid whose ``omega`` axis the window lives on. + - ``spec``: the pass band in wavelength, m, the pass band on any axis, or the window + itself, shape ``(n_omega,)``. """ if isinstance(spec, np.ndarray): if spec.shape != (grid.n_omega,): diff --git a/src/solprop/processing/time.py b/src/solprop/processing/time.py index 8676e610..d67deaf4 100644 --- a/src/solprop/processing/time.py +++ b/src/solprop/processing/time.py @@ -763,23 +763,8 @@ def _energy( ) -> Float[np.ndarray, "*shape"]: """Return :func:`energy` without its gate, for a caller already gated. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - plane : int or None - Index of the plane wanted, negative from the end. - z : float or None - Position of the plane wanted, m. - modes : int, sequence of int, 'sum' or None - Which modes to report. - bandpass : tuple of float, Passband, numpy.ndarray or None - A window applied before the measurement. - - Returns - ------- - numpy.ndarray - The energy, J. + - ``z``: position of the plane wanted, m. + - Returns: the energy, J. """ grid, planes, has_z = _planes(output, plane, z, bandpass) per_mode = np.stack([np.sum(stats.energy(grid, one), axis=-1) for one in planes]) @@ -797,25 +782,8 @@ def _fwhm_t( ) -> Float[np.ndarray, "*shape"]: """Return :func:`fwhm_t` without its gate, for a caller already gated. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - plane : int or None - Index of the plane wanted, negative from the end. - z : float or None - Position of the plane wanted, m. - modes : int, sequence of int, 'sum' or None - Which modes to report. - bandpass : tuple of float, Passband, numpy.ndarray or None - A window applied before the measurement. - outer : bool - Whether to span every satellite rather than the central feature alone. - - Returns - ------- - numpy.ndarray - The width, s. + - ``z``: position of the plane wanted, m. + - Returns: the width, s. """ measure = stats.fwhm_t_outer if outer else stats.fwhm_t grid, planes, has_z = _planes(output, plane, z, bandpass) @@ -836,23 +804,8 @@ def _fwhm_f( ) -> Float[np.ndarray, "*shape"]: """Return :func:`fwhm_f` without its gate, for a caller already gated. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - plane : int or None - Index of the plane wanted, negative from the end. - z : float or None - Position of the plane wanted, m. - modes : int, sequence of int, 'sum' or None - Which modes to report. - outer : bool - Whether to span every satellite rather than the central feature alone. - - Returns - ------- - numpy.ndarray - The width, Hz. + - ``z``: position of the plane wanted, m. + - Returns: the width, Hz. """ frequency, density = _spectral_density(output, "f", plane=plane, z=z, modes=modes) return _along_last(density, lambda trace: fwhm(frequency, trace, outer=outer)) @@ -868,25 +821,9 @@ def _field_autocorrelation( ) -> tuple[Float[np.ndarray, " n_t"], Inexact[np.ndarray, "*shape n_t"]]: """Return :func:`field_autocorrelation` without its gate, for a caller gated. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - plane : int or None - Index of the plane wanted, negative from the end. - z : float or None - Position of the plane wanted, m. - modes : int, sequence of int, 'sum' or None - Which modes to report. - bandpass : tuple of float, Passband, numpy.ndarray or None - A window applied before the transform. - - Returns - ------- - delay : numpy.ndarray - Delay axis, s, centred on zero. - correlation : numpy.ndarray - The autocorrelation, J. + - ``z``: position of the plane wanted, m. + - Returns ``delay``: delay axis, s, centred on zero. + - Returns ``correlation``: the autocorrelation, J. """ _, t, amplitude = _plane_amplitude( output, plane=plane, z=z, oversampling=1, bandpass=bandpass @@ -907,16 +844,8 @@ def _wiener_khinchin( ) -> Inexact[np.ndarray, "*rest n_t"]: """Return the circular autocorrelation of a trace, as a sample sum. - Parameters - ---------- - values : numpy.ndarray - The trace, shape ``(..., n_t)``. - - Returns - ------- - numpy.ndarray - ``sum_t conj(v(t)) v(t + tau)``, shape ``(..., n_t)``, with zero delay at the - centre of the axis. + - Returns: ``sum_t conj(v(t)) v(t + tau)``, shape ``(..., n_t)``, with zero delay at + the centre of the axis. """ spectrum = np.fft.fft(values, axis=-1) return np.fft.fftshift(np.fft.ifft(np.abs(spectrum) ** 2, axis=-1), axes=-1) @@ -930,27 +859,11 @@ def _planes( ) -> tuple[TimeGrid, list[np.ndarray], bool]: """Return the grid, the selected planes and whether a plane axis was asked for. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - plane : int or None - Plane index, negative from the end, or ``None``. - z : float or None - Plane position, m, or ``None``. - bandpass : tuple of float, Passband, numpy.ndarray or None - Wavelength limits, m, or a :class:`~solprop.processing.spectra.Passband` on any - axis, of an explicit window applied to every plane, or a ready-made window on - the stored axis. - - Returns - ------- - grid : solprop.grid.TimeGrid - The rebuilt grid. - planes : list of numpy.ndarray - One ``(n_modes, n_pol, n_omega)`` array per selected plane. - has_z : bool - Whether the caller asked for every plane. + - ``plane``: plane index, negative from the end, or ``None``. + - ``z``: plane position, m, or ``None``. + - ``bandpass``: wavelength limits, m, or a + :class:`~solprop.processing.spectra.Passband` on any axis, of an explicit window + applied to every plane, or a ready-made window on the stored axis. """ grid = grid_of(output) values = _plane_field(output, plane, z, convention="fft") @@ -962,25 +875,7 @@ def _planes( def _mode_groups(n_modes: int, modes: ModeSelection) -> list[list[int]]: - """Return the groups of modes a metric is evaluated over, one result each. - - Parameters - ---------- - n_modes : int - Number of propagated modes. - modes : int, sequence of int, 'sum' or None - The selection. - - Returns - ------- - list of list of int - Index lists; a single list of every mode for ``'sum'``. - - Raises - ------ - solprop.exceptions.ModeError - If an index is out of range or the selection is not recognised. - """ + """Return the groups of modes a metric is evaluated over, one result each.""" _check_mode_selection(modes) if modes is None: return [[index] for index in range(n_modes)] @@ -999,17 +894,7 @@ def _take_modes( ) -> Float[np.ndarray, "*shape"]: """Select or sum the trailing mode axis of an already-additive quantity. - Parameters - ---------- - values : numpy.ndarray - One value per plane and mode, shape ``(n_z, n_modes)``. - modes : int, sequence of int, 'sum' or None - The selection. - - Returns - ------- - numpy.ndarray - The selection, with a trivial mode axis squeezed away. + - Returns: the selection, with a trivial mode axis squeezed away. """ if modes == "sum": return np.asarray(np.sum(values, axis=-1)) @@ -1022,15 +907,7 @@ def _squeeze_modes( ) -> Float[np.ndarray, "*shape"]: """Drop the mode axis of a per-mode result when the run has one mode. - Parameters - ---------- - values : numpy.ndarray - One value per plane and selected mode, shape ``(n_z, n_selected)``. - - Returns - ------- - numpy.ndarray - The same values without a trailing axis of length one. + - Returns: the same values without a trailing axis of length one. """ return values[:, 0] if values.shape[-1] == 1 else values @@ -1040,17 +917,8 @@ def _finish( ) -> Float[np.ndarray, "*shape"]: """Drop the plane axis when a single plane was selected. - Parameters - ---------- - values : numpy.ndarray - Results with a leading plane axis. - has_z : bool - Whether the caller asked for every plane. - - Returns - ------- - numpy.ndarray - The results, without the plane axis when only one plane was asked for. + - ``values``: results with a leading plane axis. + - Returns: the results, without the plane axis when only one plane was asked for. """ # np.asarray keeps a single-plane scalar a zero-dimensional array, as every # scalar statistic in solprop.stats is. @@ -1060,20 +928,7 @@ def _finish( def _along_last( values: Float[np.ndarray, "*rest n"], measure: Callable[[np.ndarray], float] ) -> Float[np.ndarray, "*shape"]: - """Apply a scalar measurement to every trace along the last axis. - - Parameters - ---------- - values : numpy.ndarray - Traces, shape ``(..., n)``. - measure : callable - A function of one trace returning a float. - - Returns - ------- - numpy.ndarray - The results, shape ``(...)``, zero-dimensional for a single trace. - """ + """Apply a scalar measurement to every trace along the last axis.""" flat = values.reshape(-1, values.shape[-1]) out = np.asarray([measure(trace) for trace in flat]) return out.reshape(values.shape[:-1]) From 58ba5637322edc92df7fb4cb64fcf693a27e147d Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:15:41 +0000 Subject: [PATCH 12/50] Restore conventions, ranges and refusal reasons in solprop.modes private docs Co-Authored-By: Claude Opus 5.5 --- src/solprop/modes/_stepindex_vector.py | 8 +++++++- src/solprop/modes/antiresonant.py | 3 +++ src/solprop/modes/capillary.py | 10 ++++++++++ src/solprop/modes/connection.py | 1 + src/solprop/modes/fill.py | 13 +++++++++++-- src/solprop/modes/graded_index.py | 5 ++++- src/solprop/modes/guide.py | 3 +++ src/solprop/modes/quadrature.py | 1 + src/solprop/modes/rectangular.py | 2 ++ src/solprop/modes/stepindex.py | 12 ++++++++++-- src/solprop/modes/tabulated.py | 2 ++ 11 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/solprop/modes/_stepindex_vector.py b/src/solprop/modes/_stepindex_vector.py index c58a5bd9..a4ec654d 100644 --- a/src/solprop/modes/_stepindex_vector.py +++ b/src/solprop/modes/_stepindex_vector.py @@ -37,6 +37,7 @@ def _radial( """Evaluate the scaled radial function, slope and axis limit. - ``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: @@ -293,6 +294,8 @@ def _power( - ``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 @@ -302,7 +305,10 @@ 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.""" + """Evaluate the angularly integrated radial flux at ``x = r / a``. + + 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) return float( diff --git a/src/solprop/modes/antiresonant.py b/src/solprop/modes/antiresonant.py index c330b400..a03764a2 100644 --- a/src/solprop/modes/antiresonant.py +++ b/src/solprop/modes/antiresonant.py @@ -575,6 +575,7 @@ def _cutoffs( - ``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)``. @@ -769,6 +770,8 @@ def _spectral_model( - ``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. diff --git a/src/solprop/modes/capillary.py b/src/solprop/modes/capillary.py index d27e4943..0201c0e5 100644 --- a/src/solprop/modes/capillary.py +++ b/src/solprop/modes/capillary.py @@ -2289,6 +2289,14 @@ def _wall_block_modes( And the condensation inverts the **full** model's transverse eigenvalue, so ``model="reduced"``, whose leading-order expansion of ``n_eff`` is a different carrier, would be overridden by the block it sits beside. + + - ``modes``: typed as the protocol rather than as :class:`MarcatiliMode` so that a + set carrying another kind of mode reaches the refusal here and is named, instead + of failing the runtime type check the test session installs. + - ``wall``: whether the block is a coherent wall's. The taper coupling alone + (:func:`taper_connection`) reads the radial orders and the radius, never the loss + or the capillary model, so ``False`` admits any loss and either model and keeps + every other condition. """ block = tuple(modes) if not block: @@ -2373,6 +2381,8 @@ def _check_wall_channels( """Refuse a block whose channels do not declare one guide, by name. - ``block``: the contiguous ``HE_11`` to ``HE_1N`` set, in channel order. + - ``wall``: ``False`` (the taper coupling alone) skips the loss and capillary-model + conditions, which only the wall needs, and names the taper coupling in the rest. """ owner = "a coherent wall block" if wall else "the taper coupling" for index, mode in enumerate(block): diff --git a/src/solprop/modes/connection.py b/src/solprop/modes/connection.py index 2849a335..9ec06c63 100644 --- a/src/solprop/modes/connection.py +++ b/src/solprop/modes/connection.py @@ -839,6 +839,7 @@ def _flux_pairing( ``X(A, B)_mn = int (A_n x conj(B_m)).zhat dA``. Taking the Gram with the same two einsums is what makes ``S + S^H`` the derivative of the Gram itself. + - ``sample``: the centre sample, whose fields fill the left-hand slot. - ``electric``, ``magnetic``: the derivatives ``dE/dz`` and ``dH/dz`` in V/(m sqrt(W) m) and A/(m sqrt(W) m), shape ``(n_nodes, 3, n_modes)``. - Returns: ``S`` in 1/m, shape ``(n_modes, n_modes)``. diff --git a/src/solprop/modes/fill.py b/src/solprop/modes/fill.py index 489e4369..35696f04 100644 --- a/src/solprop/modes/fill.py +++ b/src/solprop/modes/fill.py @@ -77,7 +77,8 @@ def _sqrt1p_minus_one( ``expm1(log1p(x)/2)`` is not equivalent for a complex argument: NumPy's complex ``log1p`` forms ``1 + x`` and rounds there (1.3e-12 relative at ``1e-4 + 1e-7j`` on NumPy 2.5). - Every gas-filled guide forms its refractivity here. + Every gas-filled guide forms its refractivity here. The result is complex, on the + principal branch. """ value = np.asarray(argument, dtype=complex) return np.asarray(value / (np.sqrt(1.0 + value) + 1.0)) @@ -291,7 +292,12 @@ def absorption_by_species( return np.zeros((1, omega.size)) def _fill_paths(self) -> Mapping[str, ZProfile]: - """Return the fill's scalar paths, including constant declarations.""" + """Return the fill's scalar paths, including constant declarations. + + A shared fill whose temperature varies along z is declared by its labelled + densities: one pressure does not fix them, so it takes one parameter per + population. + """ paths: dict[str, ZProfile] = {} if self.mixture is None or len(self.mixture.components) == 1: paths["density"] = self.density @@ -345,6 +351,9 @@ def _sampled_susceptibility( - ``omega``: positive angular frequencies in rad/s, already checked by the mode. - Returns: susceptibility in m³ and its derivative in m³ s/rad, one spectral row for a legacy gas and one row per population for an explicit mixture. + - Warns :class:`DiluteScalingWarning` once per gas where an optmat-selected + population's linear density scaling departs from optmat's finite-density law + beyond its bound. """ # One evaluation context: each material's range warning is raised once for # the susceptibility and its derivative together. diff --git a/src/solprop/modes/graded_index.py b/src/solprop/modes/graded_index.py index 9ffee472..f90e578b 100644 --- a/src/solprop/modes/graded_index.py +++ b/src/solprop/modes/graded_index.py @@ -560,7 +560,8 @@ def _eps( def _weak_guidance_contrast(self, omega: float) -> float: """Return the axis-to-exterior relative permittivity contrast. - - ``omega``: positive angular frequency in rad/s. + ``eps_axis / eps_exterior - 1``: the quantity the LP weak-guidance approximation + is declared valid over. """ axis = float(self._eps(omega, np.zeros(1))[0]) exterior = float(self._eps(omega, np.full(1, 2.0))[0]) @@ -1082,6 +1083,8 @@ def _tail_integral( - ``omega``: positive angular frequency in rad/s. - ``radius``: core radius in m. + - ``lower``: lower integration radius divided by the core radius. + - Returns: dimensionless radial integrals, shape (n_modes,). """ exterior = float( np.real(self.exterior.permittivity(np.array([omega], dtype=float))[0]) diff --git a/src/solprop/modes/guide.py b/src/solprop/modes/guide.py index 212cc690..051a8382 100644 --- a/src/solprop/modes/guide.py +++ b/src/solprop/modes/guide.py @@ -1744,6 +1744,9 @@ def __call__(self, z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: def _profile_defect(interpolated: ModalSample, solved: ModalSample) -> float: """Return the relative maximum-norm defect between two aligned samples' fields. + ``solved`` is a fresh provider solve, already rotated onto the table's frequency + interpolation by :func:`solprop.translate.modal_tables.align_modal_sample`. + - Returns: the larger of the electric and magnetic relative defects, dimensionless and non-negative. """ diff --git a/src/solprop/modes/quadrature.py b/src/solprop/modes/quadrature.py index 01c7b9b0..a1fbdfb8 100644 --- a/src/solprop/modes/quadrature.py +++ b/src/solprop/modes/quadrature.py @@ -359,6 +359,7 @@ def _radial_rule( ]: """Return the radial nodes on ``(0, 1)`` with their fine and coarse weights. + - ``n_r``: requested node count, rounded up to ``2 n + 1`` for a Kronrod rule. - Returns: ``xi``, ``w`` and ``w_coarse`` on ``xi = r / a``. Without a Kronrod extension there is no embedded rule and the coarse weights equal the fine ones. """ diff --git a/src/solprop/modes/rectangular.py b/src/solprop/modes/rectangular.py index 9526b18d..a9c9cfb5 100644 --- a/src/solprop/modes/rectangular.py +++ b/src/solprop/modes/rectangular.py @@ -205,6 +205,8 @@ def _wall_coefficients( ]: """Return the TE-like and TM-like wall coefficients and their slopes. + - ``terms``: core and wall samples; a holomorphically continued (complex) + frequency is allowed. - Returns: ``q_TE = 1/s``, its derivative, ``q_TM = eps_cl/(eps_co s)`` and its derivative, with ``s`` the filled-core contrast; derivatives in s/rad. """ diff --git a/src/solprop/modes/stepindex.py b/src/solprop/modes/stepindex.py index 7afbbabf..52918337 100644 --- a/src/solprop/modes/stepindex.py +++ b/src/solprop/modes/stepindex.py @@ -100,7 +100,7 @@ def _k_ratio( def _log_slope( n: int, w: Float[np.ndarray, "*shape"] | float ) -> Float[np.ndarray, "*shape"]: - """Return the logarithmic derivative K_n'(w)/K_n(w), finite at any w > 0. + """Return the logarithmic derivative K_n'(w)/K_n(w), negative and finite at w > 0. ``K_n' = -(K_(n-1) + K_(n+1))/2`` from scaled functions where ``kve(n + 1, w)`` is finite, and ``K_n'/K_n = -K_(n-1)/K_n - n/w`` from :func:`_k_ratio` where @@ -257,6 +257,7 @@ def _family(u: float, v: float, ratio: float, n: int) -> _Kind: def _zero_block(order: int, size: int) -> Float[np.ndarray, " n_zeros"]: """Cache one block of the first zeros of one Bessel order. + - ``size``: a power of two (:func:`_zeros` rounds up). - Returns: the first `size` zeros of J_order, dimensionless, read-only. """ block = jn_zeros(order, size) @@ -279,7 +280,7 @@ def _zeros(order: int, count: int) -> Float[np.ndarray, " n_zeros"]: @cache def _bessel_nodes(n: int, count: int) -> Float[np.ndarray, " n_zeros"]: - """Cache frequency-independent Bessel-zero scan nodes.""" + """Cache the Bessel-zero scan nodes: the zeros of orders n-1, n and n+1.""" return np.concatenate([_zeros(abs(order), count) for order in (n - 1, n, n + 1)]) @@ -332,7 +333,10 @@ def _refined_roots( # noqa: PLR0917 - called positionally ) -> dict[str, list[float]]: """Refine every sign change on a node set and label each root by family. + The hybrid families share one product, so one scan serves HE and EH. + - ``ratio``: squared real cladding/core index ratio, between zero and one. + - Returns: family name to its roots u in increasing order. """ def equation(one: float) -> float: @@ -415,6 +419,9 @@ def _recovered( - ``scan``: v, the squared index ratio, the azimuthal order n and the family (HE or EH). + - Raises :class:`ModeError` if the factor changes sign more than once in an empty + cutoff interval: one guided root per interval rules that out, so which candidate + is the root cannot be decided. """ v, ratio, n, kind = scan bounds = np.asarray(limits) @@ -606,6 +613,7 @@ class _Profile: def _cutoff(kind: _Kind, n: int, m: int, ratio: float) -> float: """Return the exact cutoff at a fixed squared material-index ratio. + - ``m``: positive radial order, counted from the highest effective index. - ``ratio``: squared real cladding/core index ratio, between zero and one. """ if kind in ("TE", "TM"): diff --git a/src/solprop/modes/tabulated.py b/src/solprop/modes/tabulated.py index 548708fe..34b4fa09 100644 --- a/src/solprop/modes/tabulated.py +++ b/src/solprop/modes/tabulated.py @@ -751,6 +751,8 @@ class _SampledIndexTable(IndexTable): - ``omega``: positive finite angular frequencies in rad/s, shape ``(n_nodes,)``. - ``area``: positive finite effective areas in m^2 at the same samples. + - ``loss_interpolation``: ``linear`` by default here, because a lossless source has + no logarithm. - ``derivative_values``: source ``dneff_domega`` in s/rad at the same frequencies, stored sorted and read-only, shape ``(n_sampled_derivatives,)``. - ``source_identity``: the sampled mode's type, repr, snapshot position, frequency From 011f287a26056d1c178d4705981ca4aca103ec1e Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:15:53 +0000 Subject: [PATCH 13/50] Slim private docstrings in solprop.coupling Mechanical: python tools/docstring_diet.py src/solprop/coupling --write --check --report --rename-closures Co-Authored-By: Claude Opus 5.5 --- src/solprop/coupling/coupler.py | 822 +++--------------------------- src/solprop/coupling/elements.py | 787 +++------------------------- src/solprop/coupling/models.py | 128 +---- src/solprop/coupling/plane.py | 168 +----- src/solprop/coupling/remap.py | 120 +---- src/solprop/coupling/transport.py | 105 +--- 6 files changed, 205 insertions(+), 1925 deletions(-) diff --git a/src/solprop/coupling/coupler.py b/src/solprop/coupling/coupler.py index 3b4567e0..e013676f 100644 --- a/src/solprop/coupling/coupler.py +++ b/src/solprop/coupling/coupler.py @@ -361,10 +361,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 +488,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 +522,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 +566,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 +575,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 +584,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 +594,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 +612,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 +635,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 +681,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 +731,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 +818,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 +845,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 +906,7 @@ 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. + - 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 +945,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 +1055,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 +1073,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 +1149,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 +1164,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 +1181,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 +1268,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 +1280,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 +1307,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 +1319,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 +1340,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 +1366,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 +1399,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 +1429,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 +1458,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 +1490,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 +1565,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 +1616,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 +1704,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 +1769,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 +1821,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 +1852,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 +1872,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 +1901,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 +1921,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 +1976,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 +1991,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 +2028,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 +2052,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 +2070,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 +2103,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 +2127,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 +2155,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 +2194,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 f81a2942..f99a0dea 100644 --- a/src/solprop/coupling/elements.py +++ b/src/solprop/coupling/elements.py @@ -246,20 +246,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 +461,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]) @@ -509,20 +474,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. - """ + """Return whether two media have the same index on the band.""" 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) @@ -613,18 +530,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. - """ + """Resolve the axial-flux measure of the state's plane.""" return SpatialFlux.resolve(state.grid, state.space, state.medium, z=0.0) @@ -647,17 +553,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 +583,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 +597,7 @@ 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. + - Returns: the split content, J. """ grid = state.grid weights = fourier.energy_weights(grid.n_omega, grid.dt, kind=grid.kind) @@ -753,21 +634,7 @@ 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. + - 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 +646,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) @@ -807,24 +664,7 @@ def _node_content( index: Float[np.ndarray, " n_omega"], samples: Complex[np.ndarray, "n_modes n_pol n_omega"], ) -> 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,)``. - """ + """Return each node's reference-measure content, up to a constant.""" weights = fourier.energy_weights(grid.n_omega, grid.dt, kind=grid.kind) density = np.sum(np.abs(samples) ** 2, axis=1) radial = isinstance(space, RadialGrid) @@ -835,16 +675,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 +695,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 +744,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 +776,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 +788,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 +798,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 +813,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 +849,12 @@ 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). + - ``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 +923,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 +959,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 +985,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 +1212,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 @@ -1806,26 +1470,6 @@ def _uniform_result( column keeps its obliquity, the loss per bin is the energy change per bin and 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. """ measured_in = _measure(before, _flux(before)) measured_out = _measure(after, _flux(after)) @@ -1858,17 +1502,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 +1519,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 +1587,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 +1641,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 +1723,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 +1832,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 +1887,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] @@ -2339,19 +1904,7 @@ 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. - """ + """Return whether a known optics element is a reconstruction operation.""" if isinstance(transfer, Chain): return any(_reconstruction(member) for member in transfer.elements) if isinstance(transfer, Material): @@ -2397,18 +1950,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 +2142,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 +2178,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 +2260,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 +2285,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 +2314,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 +2332,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 +2402,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 +2497,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 +2536,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 +2546,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 +2566,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 +2583,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 +2626,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 +2659,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 8551ad2e..b69b0706 100644 --- a/src/solprop/coupling/models.py +++ b/src/solprop/coupling/models.py @@ -312,19 +312,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 +343,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 +358,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 +368,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 +382,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 @@ -602,12 +547,9 @@ def n_modes(self) -> int: 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 +746,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 +792,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 e38f6c8e..8cd5fe90 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: @@ -147,19 +128,7 @@ 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. - """ + """Return the canonical JSON text an identity is a digest of.""" return json.dumps(record, sort_keys=True, separators=(",", ":")) @@ -204,18 +173,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)] @@ -581,23 +539,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]))) @@ -1651,19 +1594,8 @@ 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. """ if isinstance(space, RadialGrid): moved = np.moveaxis(values, 0, -1) @@ -1679,18 +1611,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 +1620,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 +1630,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 +1650,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 +1670,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/remap.py b/src/solprop/coupling/remap.py index eed58e05..0b89735b 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,9 @@ 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. + - 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 +439,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 ef5f6143..af2536ba 100644 --- a/src/solprop/coupling/transport.py +++ b/src/solprop/coupling/transport.py @@ -123,25 +123,7 @@ def ledger_residual(self) -> float: 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 +151,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 +174,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 +208,7 @@ 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. + - Returns: the last state and the sequential report. """ current = state reports: list[ElementReport] = [] @@ -310,18 +251,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 +280,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])) From 485295f69647dac541628e4973f49d2ad45ed40b Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:16:02 +0000 Subject: [PATCH 14/50] Restore the zero-frequency limit rule in solprop.processing private docs The slimming dropped the note that a frequency limit may be zero, as the lower edge of a low-pass band, while a wavelength limit must be positive. Co-Authored-By: Claude Opus 5.5 --- src/solprop/processing/compression.py | 2 +- src/solprop/processing/spectra.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/solprop/processing/compression.py b/src/solprop/processing/compression.py index a6ac0ec5..302c6eb3 100644 --- a/src/solprop/processing/compression.py +++ b/src/solprop/processing/compression.py @@ -1036,7 +1036,7 @@ def optimise_cep( def _peak(phase: float) -> float: """Return minus the largest value of the real field at one added phase. - - ``phase``: Carrier-envelope phase added, rad. + - ``phase``: carrier-envelope phase added, rad. - Returns: minus the peak of the real field, in the square root of W. """ t, amplitude = _to_time( diff --git a/src/solprop/processing/spectra.py b/src/solprop/processing/spectra.py index 045873d9..d2a5a5d9 100644 --- a/src/solprop/processing/spectra.py +++ b/src/solprop/processing/spectra.py @@ -315,6 +315,9 @@ def _check_width(width: float) -> None: def _check_limits(limits: tuple[float, ...], axis: SpectralAxis) -> None: """Refuse limits that name no light on their axis. + A wavelength limit must be positive; a frequency limit may be zero, as the lower + edge of a low-pass band. + - ``limits``: the pass band, in m, Hz or rad/s as ``axis`` says. - ``axis``: the axis of ``spec_range``. """ From ce0574cc2ec86b3e01816e92b565778ac6aa546a Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:16:05 +0000 Subject: [PATCH 15/50] Restore resume, retention and unit notes in solprop.driver private docs Co-Authored-By: Claude Opus 5.5 --- src/solprop/driver/run.py | 12 +++++++++++- src/solprop/driver/service.py | 2 ++ src/solprop/driver/simulation.py | 20 ++++++++++++++++++-- src/solprop/driver/tdse.py | 4 ++++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/solprop/driver/run.py b/src/solprop/driver/run.py index 846cc56b..3f621051 100644 --- a/src/solprop/driver/run.py +++ b/src/solprop/driver/run.py @@ -943,7 +943,11 @@ class _FailedObservation: def _method_history( request: RunRequest, ) -> Literal["adams", "rk", "low_storage"]: - """Return which kind of method history a run's march keeps.""" + """Return which kind of method history a run's march keeps. + + 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): return "adams" @@ -991,6 +995,9 @@ class _MarchKeeper: execution record carries the latest decision (:meth:`record`). - ``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__( @@ -1040,6 +1047,9 @@ def keep(self, z: float, *, required: bool = False) -> None: # noqa: PLR0911, P """Keep the march's state if it stands at `z`, the position just saved. - ``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 diff --git a/src/solprop/driver/service.py b/src/solprop/driver/service.py index 97df3789..ec906250 100644 --- a/src/solprop/driver/service.py +++ b/src/solprop/driver/service.py @@ -306,6 +306,8 @@ def add( at the cadence of the session's yields rather than from inside its march. - ``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: diff --git a/src/solprop/driver/simulation.py b/src/solprop/driver/simulation.py index 145d839a..4f38226e 100644 --- a/src/solprop/driver/simulation.py +++ b/src/solprop/driver/simulation.py @@ -3722,6 +3722,8 @@ def _start( 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( @@ -8099,7 +8101,10 @@ def _open_output( def _check_resume_choice(resume: object) -> None: - """Refuse a ``resume`` value that is not one of its three.""" + """Refuse a ``resume`` value that is not one of its three. + + A mistyped word would otherwise read as ``True``. + """ if resume is True or resume is False or resume == "interpolated": return msg = f"resume must be False, True or 'interpolated', got {resume!r}" @@ -8107,7 +8112,12 @@ 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.""" + """Return whether a file has anything left to compute, by position. + + 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 or next_plane < request.observation.save_planes.size @@ -10361,6 +10371,9 @@ def _bound_rates( - ``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)) @@ -10463,6 +10476,9 @@ def _check_selection_carrier( ) -> None: """Refuse a carrier that is not the run's: one carrier sets the Kerr coefficient. + 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. """ diff --git a/src/solprop/driver/tdse.py b/src/solprop/driver/tdse.py index e39db0b0..e61d2417 100644 --- a/src/solprop/driver/tdse.py +++ b/src/solprop/driver/tdse.py @@ -278,6 +278,7 @@ def _hacc_fields( # noqa: PLR0917 - called positionally - ``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): @@ -904,6 +905,9 @@ class _HaccModel: The model is entered into the caller's stack first, so it closes after every 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. + + - ``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__( From 9b30ab120dd74710bb7d35d2900a9e7383f0bc3c Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:16:16 +0000 Subject: [PATCH 16/50] Slim private docstrings in solprop.plotting Mechanical: python tools/docstring_diet.py src/solprop/plotting --write --check --report --rename-closures The summary the tool skipped (D200) is shortened by hand: geometry._planar_grid. Co-Authored-By: Claude Opus 5.5 --- src/solprop/plotting/geometry.py | 193 ++++----------------------- src/solprop/plotting/propagation.py | 134 +++---------------- src/solprop/plotting/spectrograms.py | 17 +-- src/solprop/plotting/statistics.py | 50 +------ src/solprop/plotting/style.py | 58 +------- 5 files changed, 62 insertions(+), 390 deletions(-) diff --git a/src/solprop/plotting/geometry.py b/src/solprop/plotting/geometry.py index 6ecf4b5a..6067a2d4 100644 --- a/src/solprop/plotting/geometry.py +++ b/src/solprop/plotting/geometry.py @@ -287,17 +287,9 @@ class Box: def _at(value: float | ZProfile, z: float) -> float: """Return a constant or a z path at one position. - Parameters - ---------- - value : float or ZProfile - A length, in m, constant or along z. - z : float - Position along the guide, in m. - - Returns - ------- - float - The length at ``z``, in m. + - ``value``: a length, in m, constant or along z. + - ``z``: position along the guide, in m. + - Returns: the length at ``z``, in m. """ return float(as_profile(value)(np.array([z], dtype=float))[0]) @@ -305,15 +297,7 @@ def _at(value: float | ZProfile, z: float) -> float: def _capillary(radius: float) -> tuple[Shape, ...]: """Return a gas-filled bore in a wall of the conventional thickness. - Parameters - ---------- - radius : float - Bore radius, in m. - - Returns - ------- - tuple of Shape - The wall, then the bore. + - ``radius``: bore radius, in m. """ return ( Ring(_ORIGIN, radius, radius * (1 + WALL_FRACTION), "wall"), @@ -328,17 +312,7 @@ def _antiresonant(radius: float, lattice: TubeLattice) -> tuple[Shape, ...]: thickness the lattice does not model. The first tube sits at an angle of ``pi / N``, so that a gap lies on the x axis. - Parameters - ---------- - radius : float - Core radius, the inscribed radius of the tubes, in m. - lattice : solprop.modes.antiresonant.TubeLattice - The tubes. - - Returns - ------- - tuple of Shape - The jacket, the gas inside it, then each tube. + - ``radius``: core radius, the inscribed radius of the tubes, in m. """ tube, count = lattice.tube_radius, lattice.n_tubes jacket = radius + 2 * tube @@ -356,17 +330,9 @@ def _antiresonant(radius: float, lattice: TubeLattice) -> tuple[Shape, ...]: def _fibre(radius: float, extent: float | None) -> tuple[Shape, ...]: """Return a solid core in its cladding, drawn to a finite radius. - Parameters - ---------- - radius : float - Core radius, in m. - extent : float or None - Drawn cladding radius, in m; :data:`DRAWN_CLADDING` core radii by default. - - Returns - ------- - tuple of Shape - The cladding, then the core. + - ``radius``: core radius, in m. + - ``extent``: drawn cladding radius, in m; :data:`DRAWN_CLADDING` core radii by + default. """ outer = DRAWN_CLADDING * radius if extent is None else extent if outer <= radius: @@ -380,15 +346,7 @@ def _fibre(radius: float, extent: float | None) -> tuple[Shape, ...]: def _rectangle(half_x: float, half_y: float) -> tuple[Shape, ...]: """Return a rectangular bore in a wall of the conventional thickness. - Parameters - ---------- - half_x, half_y : float - The bore's half-widths, in m. - - Returns - ------- - tuple of Shape - The wall, then the bore. + - ``half_x``, ``half_y``: the bore's half-widths, in m. """ wall = WALL_FRACTION * max(half_x, half_y) return ( @@ -400,17 +358,8 @@ def _rectangle(half_x: float, half_y: float) -> tuple[Shape, ...]: def _plates(half_gap: float, height: float) -> tuple[Shape, ...]: """Return two plates at ``x = +-a`` and the gap between them, over ``|y| < h``. - Parameters - ---------- - half_gap : float - The plates' half separation ``a``, in m. - height : float - The drawn half-height ``h`` in y, in m. - - Returns - ------- - tuple of Shape - The two plates, left then right. + - ``half_gap``: the plates' half separation ``a``, in m. + - ``height``: the drawn half-height ``h`` in y, in m. """ plate = WALL_FRACTION * half_gap return ( @@ -422,18 +371,9 @@ def _plates(half_gap: float, height: float) -> tuple[Shape, ...]: def _slab(half_gap: float, extent: float | None) -> tuple[Shape, ...]: """Return a planar guide: two plates and the gas between them. - Parameters - ---------- - half_gap : float - The plates' half separation, in m. - extent : float or None - Drawn half-height in y, in m; the plates' outer half-width by default, so - that the drawing is square. - - Returns - ------- - tuple of Shape - The plates, then the gap. + - ``half_gap``: the plates' half separation, in m. + - ``extent``: drawn half-height in y, in m; the plates' outer half-width by default, + so that the drawing is square. """ height = half_gap * (1 + WALL_FRACTION) if extent is None else extent return ( @@ -443,18 +383,10 @@ def _slab(half_gap: float, extent: float | None) -> tuple[Shape, ...]: def _planar_grid(grid: PlanarGrid) -> tuple[Shape, ...]: - """Return a planar guide resolved in y: plates, the periodic y cell and its collar. - - Parameters - ---------- - grid : solprop.modes.slab.PlanarGrid - The grid. + """Return a planar guide resolved in y. - Returns - ------- - tuple of Shape - The plates, the collar over the whole periodic cell, then the medium inside - the collar. + The plates, the collar over the whole periodic y cell, then the medium inside the + collar. """ half_gap = grid.half_gap inner = grid.radius_y @@ -469,15 +401,7 @@ def _planar_grid(grid: PlanarGrid) -> tuple[Shape, ...]: def _radial_grid(grid: RadialGrid) -> tuple[Shape, ...]: """Return radial free space: the medium to ``R``, the collar beyond it. - Parameters - ---------- - grid : solprop.grid.RadialGrid - The grid. - - Returns - ------- - tuple of Shape - The medium, then the collar when there is one. + - Returns: the medium, then the collar when there is one. """ shapes: list[Shape] = [Disc(_ORIGIN, grid.radius, "medium")] if grid.collar_fraction > 0: @@ -490,18 +414,9 @@ def _free_grid(grid: FreeGrid, extent: float | None) -> tuple[Shape, ...]: A line grid is uniform in y, so its y extent is drawn to ``extent``. - Parameters - ---------- - grid : solprop.grid.FreeGrid - The grid; a :class:`~solprop.grid.LineGrid` is drawn as a band. - extent : float or None - A line grid's drawn half-height in y, in m; its periodic half-width in x by - default. - - Returns - ------- - tuple of Shape - The collar when there is one, then the medium inside it. + - ``extent``: a line grid's drawn half-height in y, in m; its periodic half-width in + x by default. + - Returns: the collar when there is one, then the medium inside it. """ factor = 1 + grid.collar_fraction outer_x = grid.radius_x * factor @@ -517,34 +432,15 @@ def _free_grid(grid: FreeGrid, extent: float | None) -> tuple[Shape, ...]: def _unbounded(guide: object) -> bool: - """Return whether a geometry leaves a direction unbounded, so takes an extent. - - Parameters - ---------- - guide : object - The guide or grid, after unwrapping a collection or a modal guide. - - Returns - ------- - bool - True for a solid fibre, a planar guide and a line grid. - """ + """Return whether a geometry leaves a direction unbounded, so takes an extent.""" return isinstance(guide, StepIndexMode | GradedIndexProvider | SlabMode | LineGrid) def _unwrap(guide: object) -> object: """Return the object whose geometry a collection or a modal guide carries. - Parameters - ---------- - guide : object - What was passed to :func:`outline`. - - Returns - ------- - object - A collection's first mode, which shares its guide with the rest; a modal - guide's provider, or a step-index provider's first mode; else ``guide``. + - Returns: a collection's first mode, which shares its guide with the rest; a modal + guide's provider, or a step-index provider's first mode; else ``guide``. """ if isinstance(guide, ModeCollection): return _unwrap(guide.modes[0]) @@ -636,19 +532,9 @@ def _shapes( # noqa: PLR0911 - one return per kind of geometry ) -> tuple[Shape, ...] | None: """Return the shapes of an unwrapped guide or grid, or None for an unknown kind. - Parameters - ---------- - target : object - The guide or grid, after :func:`_unwrap`. - z : float - Position along the guide, in m. - extent : float or None - How far to draw an unbounded direction, in m, already validated. - - Returns - ------- - tuple of Shape or None - The regions, back to front; None when this module does not draw the kind. + - ``z``: position along the guide, in m. + - ``extent``: how far to draw an unbounded direction, in m, already validated. + - Returns: the regions, back to front; None when this module does not draw the kind. """ match target: case VincettiMode(): @@ -677,17 +563,7 @@ def _shapes( # noqa: PLR0911 - one return per kind of geometry def _patch(shape: Shape, palette: Palette) -> Patch: """Return the Matplotlib patch of one shape, in micrometres. - Parameters - ---------- - shape : Shape - The shape, in m. - palette : Palette - Its colours. - - Returns - ------- - matplotlib.patches.Patch - A circle, an annulus or a rectangle, filled and edged by its region. + - ``shape``: the shape, in m. """ style = { "facecolor": palette.fill(shape.region), @@ -773,18 +649,7 @@ def draw( @functools.cache def _example(route: str, representation: str) -> object: - """Build the representative guide or grid a thumbnail draws. - - Parameters - ---------- - route, representation : str - A key of :data:`THUMBNAILS`. - - Returns - ------- - object - The guide or grid, built once per session. - """ + """Build the representative guide or grid a thumbnail draws.""" builders: dict[tuple[str, str], Callable[[], object]] = { ("gnlse", "mode_averaged"): lambda: StepIndexMode( 1.25e-6, core=1.45, cladding=1.0, profile_wavelength=835e-9 diff --git a/src/solprop/plotting/propagation.py b/src/solprop/plotting/propagation.py index 0d125100..6f9261d2 100644 --- a/src/solprop/plotting/propagation.py +++ b/src/solprop/plotting/propagation.py @@ -532,17 +532,7 @@ def plot_energy( def _axis(ax: Axes | None, figsize: tuple[float, float]) -> Axes: """Return the axis to draw on, making a figure when none was given. - Parameters - ---------- - ax : matplotlib.axes.Axes or None - The caller's axis, or ``None``. - figsize : tuple of float - Size of the new figure, inches. - - Returns - ------- - matplotlib.axes.Axes - The axis to draw on. + - ``ax``: the caller's axis, or ``None``. """ if ax is not None: return ax @@ -556,17 +546,9 @@ def _with_mode_axis( ) -> Float[np.ndarray, "*with_modes"]: """Restore the mode axis an accessor squeezed away. - Parameters - ---------- - values : numpy.ndarray - What an accessor returned, with or without a mode axis. - leading : bool - Whether a plane axis comes before the mode axis. - - Returns - ------- - numpy.ndarray - The same values with a mode axis, of length one when there was none. + - ``values``: what an accessor returned, with or without a mode axis. + - ``leading``: whether a plane axis comes before the mode axis. + - Returns: the same values with a mode axis, of length one when there was none. """ wanted = 3 if leading else 2 if values.ndim == wanted: @@ -575,22 +557,7 @@ def _with_mode_axis( def _drawn_labels(output: Output, count: int, modes: ModeSelection) -> list[str]: - """Return the label of each drawn trace. - - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - count : int - How many traces are drawn. - modes : int, sequence of int, 'sum' or None - The selection they came from. - - Returns - ------- - list of str - One label per trace: the mode's name, or ``'sum'`` for a modal sum. - """ + """Return the label of each drawn trace.""" if modes == "sum": return ["sum"] every = mode_labels(output, max(field_modes(output), count)) @@ -603,17 +570,7 @@ def _drawn_labels(output: Output, count: int, modes: ModeSelection) -> list[str] def _plane_indices(output: Output, z: float | Sequence[float] | None) -> list[int]: """Return the plane indices a slice plot draws. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - z : float, sequence of float or None - Positions, m; ``None`` means the last saved plane. - - Returns - ------- - list of int - Indices into the saved planes, one per position. + - ``z``: positions, m; ``None`` means the last saved plane. """ if z is None: return [output.n_saved - 1] @@ -624,38 +581,16 @@ def _plane_indices(output: Output, z: float | Sequence[float] | None) -> list[in def _legend_entry(z: float, name: str, n_labels: int) -> str: """Return the legend entry of one trace. - Parameters - ---------- - z : float - Position of the plane, m. - name : str - Label of the mode. - n_labels : int - How many modes are drawn; the mode is named only when there is more than one. - - Returns - ------- - str - The entry. + - ``z``: position of the plane, m. + - ``n_labels``: how many modes are drawn; the mode is named only when there is more + than one. """ entry = f"{z * Z_TO_CM:.2f} cm" return entry if n_labels == 1 else f"{entry} ({name})" def _dashes(index: int) -> tuple[float, ...]: - """Return the dash pattern of a mode, so that modes differ within one colour. - - Parameters - ---------- - index : int - Index of the mode among those drawn. - - Returns - ------- - tuple of float - A dash pattern, solid for the first mode. The patterns repeat past the last - one. - """ + """Return the dash pattern of a mode, so that modes differ within one colour.""" patterns: tuple[tuple[float, ...], ...] = ( (), (4.0, 2.0), @@ -669,18 +604,8 @@ def _dashes(index: int) -> tuple[float, ...]: def _density_label(axis: SpectralAxis, width: str = "") -> str: """Return the ordinate label of a spectral energy density. - Parameters - ---------- - axis : {'wavelength', 'f', 'omega'} - The abscissa it is quoted against. - width : str - :func:`solprop.plotting.style.per_width` of the run: ``"/m"`` makes the - density one per metre of y as well. - - Returns - ------- - str - The label, with its unit. + - ``width``: :func:`solprop.plotting.style.per_width` of the run: ``"/m"`` makes the + density one per metre of y as well. """ units = {"wavelength": "J/m", "f": "J/Hz", "omega": "J/(rad/s)"} per_y = " per m of y" if width else "" @@ -699,22 +624,7 @@ def _spectral_map( ) -> None: """Draw one spectral density map in decibels. - Parameters - ---------- - ax : matplotlib.axes.Axes - The axis to draw on. - x : numpy.ndarray - Spectral abscissa in display units, shape ``(n_x,)``. - z : numpy.ndarray - Distance in centimetres, shape ``(n_z,)``. - density : numpy.ndarray - Spectral energy density, shape ``(n_z, n_x)``. - reference : float - The density that is 0 dB. - db_min : float - Bottom of the colour scale, dB. - cmap : str, matplotlib.colors.Colormap or None - Colour map. + - ``ax``: the axis to draw on. """ floor = reference * 10.0 ** ((db_min - _DB_FLOOR_MARGIN) / 10.0) decibels = 10.0 * np.log10(np.maximum(density, floor) / reference) @@ -735,20 +645,10 @@ def _temporal_map( ) -> None: """Draw one instantaneous-power map. - Parameters - ---------- - ax : matplotlib.axes.Axes - The axis to draw on. - t : numpy.ndarray - Time in femtoseconds, shape ``(n_t,)``. - z : numpy.ndarray - Distance in centimetres, shape ``(n_z,)``. - power : numpy.ndarray - Instantaneous power, W, shape ``(n_z, n_t)``. - cmap : str, matplotlib.colors.Colormap or None - Colour map. - width : str - ``"/m"`` for a power per metre of y (:func:`solprop.plotting.style.per_width`). + - ``ax``: the axis to draw on. + - ``power``: instantaneous power, W, shape ``(n_z, n_t)``. + - ``width``: ``"/m"`` for a power per metre of y + (:func:`solprop.plotting.style.per_width`). """ scale, unit = power_unit(power) mesh = ax.pcolormesh( diff --git a/src/solprop/plotting/spectrograms.py b/src/solprop/plotting/spectrograms.py index 5ed12d67..77e2c50c 100644 --- a/src/solprop/plotting/spectrograms.py +++ b/src/solprop/plotting/spectrograms.py @@ -162,21 +162,8 @@ def _ordinate( ) -> tuple[Float[np.ndarray, "n_f n_slices"], Float[np.ndarray, " n_f"]]: """Return the map and its ordinate on the requested spectral abscissa. - Parameters - ---------- - axis : {'wavelength', 'f', 'omega'} - The abscissa. - frequency : numpy.ndarray - Positive frequencies of the transform, Hz, shape ``(n_f,)``, increasing. - intensity : numpy.ndarray - The map on those frequencies, shape ``(n_f, n_slices)``. - - Returns - ------- - values : numpy.ndarray - The map, reordered so that the ordinate increases. - ordinate : numpy.ndarray - The abscissa in display units, increasing. + - ``frequency``: positive frequencies of the transform, Hz, shape ``(n_f,)``, + increasing. """ factor, _ = spectral_display(axis) if axis == "wavelength": diff --git a/src/solprop/plotting/statistics.py b/src/solprop/plotting/statistics.py index 4c511eef..43c4d5a0 100644 --- a/src/solprop/plotting/statistics.py +++ b/src/solprop/plotting/statistics.py @@ -143,17 +143,7 @@ def plot_stats(output: Output, **kwargs: Any) -> list[Figure]: def _trace(output: Output, name: str) -> tuple[np.ndarray, np.ndarray]: """Return the abscissa and the values of one statistic, channel first. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - name : str - Name of the statistic. - - Returns - ------- - tuple of numpy.ndarray - Distance in cm, shape ``(n,)``, and the values reshaped to ``(n, -1)``. + - Returns: distance in cm, shape ``(n,)``, and the values reshaped to ``(n, -1)``. """ if name in output.step_stat_names: z = np.asarray(output["step_stats/z"]) @@ -172,16 +162,8 @@ def _panel( ) -> None: """Draw one statistic against distance, from the channel where there is one. - Parameters - ---------- - ax : matplotlib.axes.Axes - The axis to draw on. - output : solprop.output.schema.Output - An opened output handler. - name : str - Name of the statistic. - kwargs : dict - Passed to :meth:`matplotlib.axes.Axes.plot`. + - ``ax``: the axis to draw on. + - ``kwargs``: passed to :meth:`matplotlib.axes.Axes.plot`. """ z, values = _trace(output, name) factor, label = _DISPLAY[name] @@ -209,19 +191,9 @@ def _panel( def _labels(output: Output, count: int) -> list[str]: """Return the legend entries of a statistic's columns. - Parameters - ---------- - output : solprop.output.schema.Output - An opened output handler. - count : int - How many columns the statistic has once flattened. - - Returns - ------- - list of str - The mode names when the columns are the modes, and an index otherwise: a - windowed statistic, or one that keeps its polarisation axis, has more columns - than the run has modes. + - Returns: the mode names when the columns are the modes, and an index otherwise: a + windowed statistic, or one that keeps its polarisation axis, has more columns than + the run has modes. """ n_modes = field_modes(output) if count == n_modes: @@ -232,15 +204,7 @@ def _labels(output: Output, count: int) -> list[str]: def _should_log(values: Float[np.ndarray, "n_z n_columns"]) -> bool: """Return whether a panel's ordinate should be logarithmic. - Parameters - ---------- - values : numpy.ndarray - The values to be drawn, in display units. - - Returns - ------- - bool - Whether they span more than a decade and are everywhere positive. + - Returns: whether they span more than a decade and are everywhere positive. """ if not np.all(values > 0.0): return False diff --git a/src/solprop/plotting/style.py b/src/solprop/plotting/style.py index d0079e19..cd9febfb 100644 --- a/src/solprop/plotting/style.py +++ b/src/solprop/plotting/style.py @@ -361,19 +361,10 @@ def _spec_range( ) -> tuple[float, float]: """Return the spectral range of a plot, in SI units of its abscissa, ascending. - Parameters - ---------- - axis : {'wavelength', 'f', 'omega'} - The abscissa. - spec_range : tuple of float or None - The range asked for, in m, Hz or rad/s as ``axis`` says, in either order. - default : tuple of float - The wavelength range, m, to show when none was asked for. - - Returns - ------- - tuple of float - The lower and upper limits in the units of ``axis``. + - ``spec_range``: the range asked for, in m, Hz or rad/s as ``axis`` says, in either + order. + - ``default``: the wavelength range, m, to show when none was asked for. + - Returns: the lower and upper limits in the units of ``axis``. """ if spec_range is None: return wavelength_to_axis(axis, default) @@ -385,17 +376,7 @@ def _display_limits( ) -> tuple[float, float]: """Return SI limits on a spectral abscissa in the display units of the figure. - Parameters - ---------- - axis : {'wavelength', 'f', 'omega'} - The abscissa. - spec_range : tuple of float - The ascending limits, in m, Hz or rad/s. - - Returns - ------- - tuple of float - The limits in the units of :func:`spectral_display`. + - ``spec_range``: the ascending limits, in m, Hz or rad/s. """ factor, _ = spectral_display(axis) return float(factor * spec_range[0]), float(factor * spec_range[1]) @@ -589,15 +570,7 @@ def mode_labels(output: Output, n_modes: int) -> list[str]: def _angle_label(phi: object) -> str: """Return the polarisation suffix of a mode at a given angle. - Parameters - ---------- - phi : object - The angle as the metadata records it, rad, or anything else. - - Returns - ------- - str - ``'(x)'``, ``'(y)'`` or the angle as a fraction of pi. + - ``phi``: the angle as the metadata records it, rad, or anything else. """ if not isinstance(phi, float): return "(?)" @@ -609,24 +582,7 @@ def _angle_label(phi: object) -> str: def _figure_of(ax: Axes) -> Figure: - """Return the figure an axis belongs to, narrowed to a root figure. - - Parameters - ---------- - ax : matplotlib.axes.Axes - The axis. - - Returns - ------- - matplotlib.figure.Figure - Its root figure, so that a function drawing into a caller's sub-figure still - returns something a caller can save. - - Raises - ------ - solprop.exceptions.ProcessingError - If the axis belongs to no figure. - """ + """Return the figure an axis belongs to, narrowed to a root figure.""" figure = ax.get_figure(root=True) if figure is None: # pragma: no cover - an axis always belongs to a figure msg = "the axis to draw on does not belong to a figure" From ed8c44eb00b65ab4849e8a1b39bd0b684f2c0141 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:16:18 +0000 Subject: [PATCH 17/50] Slim private docstrings in solprop.output Mechanical: uv run python tools/docstring_diet.py src/solprop/output --write --check --report --rename-closures Co-Authored-By: Claude Opus 5.5 --- src/solprop/output/_file_lock.py | 40 +- src/solprop/output/_scan_lock.py | 46 +- src/solprop/output/_writer_lock.py | 16 +- src/solprop/output/callback.py | 13 +- src/solprop/output/capability.py | 17 +- src/solprop/output/hdf5.py | 672 +++-------------------- src/solprop/output/memory.py | 199 +------ src/solprop/output/resumable.py | 71 +-- src/solprop/output/scan.py | 169 +----- src/solprop/output/schema.py | 852 ++++------------------------- src/solprop/output/snapshots.py | 165 +----- src/solprop/output/tdse_archive.py | 418 ++------------ 12 files changed, 291 insertions(+), 2387 deletions(-) diff --git a/src/solprop/output/_file_lock.py b/src/solprop/output/_file_lock.py index 9fb9252c..ca482d0b 100644 --- a/src/solprop/output/_file_lock.py +++ b/src/solprop/output/_file_lock.py @@ -22,22 +22,7 @@ def lock(descriptor: int, *, shared: bool = False, blocking: bool = True) -> None: - """Acquire the advisory lock on an open descriptor. - - Parameters - ---------- - descriptor : int - Open file descriptor of the lock file. - shared : bool - Whether a shared (reader) lock suffices; exclusive on Windows regardless. - blocking : bool - Whether to wait for the lock rather than fail at once. - - Raises - ------ - BlockingIOError - If ``blocking`` is false and another process holds a conflicting lock. - """ + """Acquire the advisory lock on an open descriptor.""" if sys.platform == "win32": _windows_lock(descriptor, blocking=blocking) return @@ -48,13 +33,7 @@ def lock(descriptor: int, *, shared: bool = False, blocking: bool = True) -> Non def unlock(descriptor: int) -> None: - """Release the advisory lock taken by :func:`lock`. - - Parameters - ---------- - descriptor : int - Open file descriptor of the lock file. - """ + """Release the advisory lock taken by :func:`lock`.""" if sys.platform == "win32": import msvcrt # noqa: PLC0415 - Windows only @@ -67,20 +46,7 @@ def unlock(descriptor: int) -> None: def _windows_lock(descriptor: int, *, blocking: bool) -> None: - """Lock the first byte exclusively, retrying until it is free when blocking. - - Parameters - ---------- - descriptor : int - Open file descriptor of the lock file. - blocking : bool - Whether to wait for the lock rather than fail at once. - - Raises - ------ - BlockingIOError - If ``blocking`` is false and another process holds the lock. - """ + """Lock the first byte exclusively, retrying until it is free when blocking.""" import msvcrt # noqa: PLC0415 - Windows only while True: diff --git a/src/solprop/output/_scan_lock.py b/src/solprop/output/_scan_lock.py index a4007a78..338efd6a 100644 --- a/src/solprop/output/_scan_lock.py +++ b/src/solprop/output/_scan_lock.py @@ -18,35 +18,13 @@ def lock_path(path: Path | str) -> Path: - """Locate the persistent advisory lock beside a collected scan. - - Parameters - ---------- - path : Path | str - Queue directory or record filename used by this operation. - - Returns - ------- - pathlib.Path - Persistent sidecar filename beside the collected scan. - """ + """Locate the persistent advisory lock beside a collected scan.""" path = Path(path) return path.with_name(path.name + ".lock") def _sidecar(path: Path | str) -> int: - """Open the persistent sidecar, creating it so a reader can find the lock. - - Parameters - ---------- - path : Path | str - Queue directory or record filename used by this operation. - - Returns - ------- - int - Open file descriptor for the sidecar; the caller closes it. - """ + """Open the persistent sidecar, creating it so a reader can find the lock.""" return os.open(lock_path(path), os.O_CREAT | os.O_RDWR, 0o600) @@ -57,16 +35,6 @@ def write_scan(path: Path | str) -> Iterator[None]: Ownership is the kernel's, so a killed controller releases the file rather than stranding it. HDF5 is not opened here: the caller owns the file for as long as it holds this lock. - - Parameters - ---------- - path : Path | str - Queue directory or record filename used by this operation. - - Yields - ------ - None - Control while the exclusive advisory lock is held. """ descriptor = _sidecar(path) try: @@ -85,15 +53,7 @@ def read_scan(path: Path | str) -> Iterator[h5py.File]: creates it too, and a reader that skipped the lock because no sidecar existed yet would open a live non-SWMR file under an active writer. - Parameters - ---------- - path : Path | str - Queue directory or record filename used by this operation. - - Yields - ------ - h5py.File - Open read-only scan while a shared advisory lock is held. + - Yields: open read-only scan while a shared advisory lock is held. """ if not Path(path).is_file(): # Refused before the sidecar exists, so a mistyped path leaves nothing behind. diff --git a/src/solprop/output/_writer_lock.py b/src/solprop/output/_writer_lock.py index 809ca5d5..03480475 100644 --- a/src/solprop/output/_writer_lock.py +++ b/src/solprop/output/_writer_lock.py @@ -13,26 +13,14 @@ class WriterLock: - """Own a persistent sidecar's advisory lock until explicitly released. - - Parameters - ---------- - path : pathlib.Path - File or store whose writers share this lock. - """ + """Own a persistent sidecar's advisory lock until explicitly released.""" def __init__(self, path: Path) -> None: self.path = path.resolve() self._descriptor: int | None = None def acquire(self) -> None: - """Acquire ownership without waiting for another writer. - - Raises - ------ - OutputError - If the destination belongs to a published snapshot or another writer. - """ + """Acquire ownership without waiting for another writer.""" if self._descriptor is not None: return for parent in self.path.parents: diff --git a/src/solprop/output/callback.py b/src/solprop/output/callback.py index c3362f0e..506df907 100644 --- a/src/solprop/output/callback.py +++ b/src/solprop/output/callback.py @@ -39,18 +39,7 @@ def _not_held(what: str) -> OutputError: - """Return the refusal of a read, naming what was asked for. - - Parameters - ---------- - what : str - What the caller asked to read. - - Returns - ------- - solprop.exceptions.OutputError - The error to raise. - """ + """Return the refusal of a read, naming what was asked for.""" return OutputError( f"a CallbackOutput does not hold {what}: it gives each plane to its callable " "and keeps nothing. Keep what you need in the callable, or run into a " diff --git a/src/solprop/output/capability.py b/src/solprop/output/capability.py index a745ca19..4ee004f9 100644 --- a/src/solprop/output/capability.py +++ b/src/solprop/output/capability.py @@ -427,22 +427,7 @@ def reading_refusal( def _column_refusal(metadata: RunMetadata, name: str, recorded: Sequence[str]) -> str: - """Return a spatial file's refusal of a column reader, with its route's advice. - - Parameters - ---------- - metadata : solprop.output.schema.RunMetadata - The spatial record. - name : str - A name of :data:`COLUMN_READINGS`. - recorded : collections.abc.Sequence - The statistics the file holds. - - Returns - ------- - str - The refusal. - """ + """Return a spatial file's refusal of a column reader, with its route's advice.""" kind = metadata.simulation_kind if kind == "free": # A line grid runs as the free transform with one y sample, and is the only diff --git a/src/solprop/output/hdf5.py b/src/solprop/output/hdf5.py index 029e9b11..78d2155b 100644 --- a/src/solprop/output/hdf5.py +++ b/src/solprop/output/hdf5.py @@ -139,15 +139,7 @@ def _read_outcome(file: h5py.File) -> RunOutcome | None: """Read the execution status a file records, if it records one. - Parameters - ---------- - file : h5py.File - Open saved output. - - Returns - ------- - RunOutcome or None - Validated execution status; ``None`` when the file records none. + - Returns: validated execution status; ``None`` when the file records none. """ if _OUTCOME_KEY not in file: return None @@ -163,15 +155,6 @@ def _overwrite(group: h5py.Group, name: str, data: np.ndarray) -> None: In place, a snapshot rewritten at every plane does not grow the file: HDF5 does not reclaim the space of a deleted dataset. - - Parameters - ---------- - group : h5py.Group - Its group. - name : str - Its name. - data : numpy.ndarray - What it holds. """ if name in group: existing = group[name] @@ -194,15 +177,6 @@ def _overwrite_text(file: h5py.File, name: str, text: str) -> None: saved step by step. Written in place, HDF5 frees the string it replaces, and the file holds one (measured with h5py 3.15 and 3.16, HDF5 1.14.6 and 2.0.0). A dataset of another shape or type is replaced. - - Parameters - ---------- - file : h5py.File - The open file. - name : str - The dataset's path. - text : str - What it holds. """ if name in file: existing = file[name] @@ -219,15 +193,7 @@ def _overwrite_text(file: h5py.File, name: str, text: str) -> None: def _write_march(group: h5py.Group, snapshot: MarchSnapshot) -> None: - """Store a march snapshot, its position last, so that a torn write reads as none. - - Parameters - ---------- - group : h5py.Group - The snapshot's group, possibly holding an earlier snapshot. - snapshot : solprop.output.schema.MarchSnapshot - The snapshot. - """ + """Store a march snapshot, its position last, so that a torn write reads as none.""" group.attrs["z"] = math.nan # The dataset keeps the leading axis of one it has always had, so that every # reader of the format reads it (SEP 47 section 6.7). @@ -258,18 +224,9 @@ def _write_march(group: h5py.Group, snapshot: MarchSnapshot) -> None: def _read_march(file: h5py.File, z: float) -> MarchSnapshot | None: """Read the march snapshot a file holds at `z`, or ``None``. - Parameters - ---------- - file : h5py.File - Open saved output. - z : float - The position the run resumes from, m: a snapshot taken elsewhere, or one - whose write was cut short, is not returned. - - Returns - ------- - solprop.output.schema.MarchSnapshot or None - The snapshot taken at `z`, if the file holds one. + - ``z``: the position the run resumes from, m: a snapshot taken elsewhere, or one + whose write was cut short, is not returned. + - Returns: the snapshot taken at `z`, if the file holds one. """ if _MARCH_KEY not in file: return None @@ -308,25 +265,7 @@ def _read_march(file: h5py.File, z: float) -> MarchSnapshot | None: def _trajectory_state( stored: Complex[np.ndarray, "*shape"], ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: - """Return a stored march state without the leading axis of one its file keeps. - - Parameters - ---------- - stored : numpy.ndarray - The ``state`` dataset: ``(1, n_modes, n_pol, n_omega)`` as every file has - written it, or ``(n_modes, n_pol, n_omega)``. - - Returns - ------- - numpy.ndarray - The trajectory's state, ``(n_modes, n_pol, n_omega)``. - - Raises - ------ - OutputError - If the state has neither layout: a leading axis longer than one is a stacked - group, which a single trajectory's snapshot never is, and is never squeezed. - """ + """Return a stored march state without the leading axis of one its file keeps.""" if stored.ndim == 4 and stored.shape[0] == 1: return stored[0] if stored.ndim == 3: @@ -343,14 +282,8 @@ def _write_checkpoint( ) -> None: """Store a terminal field using the same Fourier boundary as regular saves. - Parameters - ---------- - group : h5py.Group - Empty destination for the terminal record. - checkpoint : TerminalCheckpoint - Accepted field and available history, in internal FFT convention. - metadata : RunMetadata - Grid and output amplitude conventions. + - ``checkpoint``: accepted field and available history, in internal FFT convention. + - ``metadata``: grid and output amplitude conventions. """ plane = checkpoint.plane # A checkpoint always carries its field (TerminalCheckpoint refuses one without). @@ -384,17 +317,8 @@ def _read_checkpoint( ) -> TerminalCheckpoint | None: """Read a separate terminal field in internal FFT convention. - Parameters - ---------- - file : h5py.File - Open saved output. - metadata : RunMetadata - Grid and output amplitude conventions. - - Returns - ------- - TerminalCheckpoint or None - Validated accepted field, absent when no terminal state is stored. + - ``metadata``: grid and output amplitude conventions. + - Returns: validated accepted field, absent when no terminal state is stored. """ if _CHECKPOINT_KEY not in file: return None @@ -449,18 +373,7 @@ def _read_checkpoint( def _check_compression(compression: str | None, compression_opts: int | None) -> None: """Check a compression request against what h5py can do without a plugin. - Parameters - ---------- - compression : {None, 'gzip', 'lzf'} - The filter. - compression_opts : int or None - Level for ``'gzip'``, 0 to 9; ``None`` for the others. - - Raises - ------ - OutputError - If the filter is unknown, or the level is given for a filter that has none, or - is outside the range gzip accepts. + - ``compression_opts``: level for ``'gzip'``, 0 to 9; ``None`` for the others. """ if compression not in COMPRESSIONS: msg = f"compression must be one of {COMPRESSIONS}, got {compression!r}" @@ -479,58 +392,23 @@ def _dataset(parent: h5py.Group, name: str) -> h5py.Dataset: h5py types every lookup as a group, a dataset or a named type; solprop's files hold only the first two and every name below is one this module created, so the cast is total. - - Parameters - ---------- - parent : h5py.Group - The group to look in. - name : str - Name of the dataset. - - Returns - ------- - h5py.Dataset - The dataset. """ return cast("h5py.Dataset", parent[name]) def _group(parent: h5py.Group, name: str) -> h5py.Group: - """Return one subgroup of a group, narrowed for the type checker. - - Parameters - ---------- - parent : h5py.Group - The group to look in. - name : str - Name of the subgroup. - - Returns - ------- - h5py.Group - The subgroup. - """ + """Return one subgroup of a group, narrowed for the type checker.""" return cast("h5py.Group", parent[name]) def _kind_of(key: str, *, every_field: bool = True) -> str: """Return which count a dataset's leading axis follows. - Parameters - ---------- - key : str - The dataset's accessor key: ``'z'``, ``'field'``, ``'noise_phase'``, or a - name prefixed by ``'stats/'``, ``'step_history/'`` or ``'step_stats/'``. - every_field : bool, optional - Whether the run keeps its field on every plane; if not, the field and the - noise phase have one entry per plane that keeps its field. - - Returns - ------- - str - ``'plane'`` for one entry per saved plane (the per-plane ``counts`` columns - among them), ``'field'`` for one per kept field, ``'history'`` for a - step-history column and ``'rows'`` for a between-planes statistics column. + - ``every_field``: whether the run keeps its field on every plane; if not, the field + and the noise phase have one entry per plane that keeps its field. + - Returns: ``'plane'`` for one entry per saved plane (the per-plane ``counts`` + columns among them), ``'field'`` for one per kept field, ``'history'`` for a + step-history column and ``'rows'`` for a between-planes statistics column. """ group, _, name = key.rpartition("/") if group == "" and name in ("field", "noise_phase") and not every_field: @@ -548,15 +426,8 @@ def _stored_counts(file: h5py.File) -> tuple[int, int, int]: ``step_stats/counts`` over them; any rows past those were allocated ahead or cut off by a hard stop, and are not part of the run. - Parameters - ---------- - file : h5py.File - An open file holding at least one plane's datasets. - - Returns - ------- - tuple of int - The planes, the step-history attempts and the between-planes rows. + - ``file``: an open file holding at least one plane's datasets. + - Returns: the planes, the step-history attempts and the between-planes rows. """ history = _group(file, "step_history") rows = _group(file, "step_stats") @@ -567,18 +438,7 @@ def _stored_counts(file: h5py.File) -> tuple[int, int, int]: def _stored_retention(file: h5py.File) -> FieldRetention: - """Return which planes of a file keep their field. - - Parameters - ---------- - file : h5py.File - An open file. - - Returns - ------- - solprop.output.schema.FieldRetention - The rule its ``fields`` attribute and ``field_planes`` dataset record. - """ + """Return which planes of a file keep their field.""" fields = file.attrs["fields"] planes = ( tuple(int(index) for index in np.asarray(_dataset(file, "field_planes")[()])) @@ -591,29 +451,14 @@ def _stored_retention(file: h5py.File) -> FieldRetention: def _stored_shape(file: h5py.File) -> tuple[int, ...]: """Return the shape of one plane's field in a file, stored or not. - Parameters - ---------- - file : h5py.File - An open file holding at least one plane. - - Returns - ------- - tuple of int - ``(n_modes, n_pol, n_omega)``, from the ``field_shape`` attribute. + - ``file``: an open file holding at least one plane. + - Returns: ``(n_modes, n_pol, n_omega)``, from the ``field_shape`` attribute. """ return tuple(int(size) for size in np.asarray(file.attrs["field_shape"])) def _write_record(file: h5py.File, metadata: RunMetadata) -> None: - """Write, or replace, the run record of an open file and the arrays beside it. - - Parameters - ---------- - file : h5py.File - The open file. - metadata : RunMetadata - The record. - """ + """Write, or replace, the run record of an open file and the arrays beside it.""" for key in (_METADATA_KEY, _RECORD_ARRAYS_KEY): if key in file: del file[key] @@ -625,23 +470,7 @@ def _write_record(file: h5py.File, metadata: RunMetadata) -> None: def _read_record(file: h5py.File) -> RunMetadata: - """Return the run record of an open file, with the arrays beside it. - - Parameters - ---------- - file : h5py.File - The open file. - - Returns - ------- - RunMetadata - The record. - - Raises - ------ - OutputError - If the file carries no metadata, or the record cannot be read. - """ + """Return the run record of an open file, with the arrays beside it.""" held = file.get(_RECORD_ARRAYS_KEY) return RunMetadata.from_json( _read_json(file), held if isinstance(held, h5py.Group) else None @@ -655,15 +484,7 @@ def _read_record(file: h5py.File) -> RunMetadata: def _text(stored: object) -> str: """Return a stored string dataset's value as text. - Parameters - ---------- - stored : object - What h5py read: bytes or str. - - Returns - ------- - str - The text. + - ``stored``: what h5py read: bytes or str. """ return stored.decode("utf-8") if isinstance(stored, bytes) else str(stored) @@ -738,15 +559,7 @@ def write_recipe(file: h5py.File, record: RecipeRecord) -> None: def _read_annotations(file: h5py.File) -> dict[str, JsonValue]: """Return the post-run annotations an open file carries. - Parameters - ---------- - file : h5py.File - The open file. - - Returns - ------- - dict - Each annotation's JSON values, by name; empty when the file has none. + - Returns: each annotation's JSON values, by name; empty when the file has none. """ if _ANNOTATIONS_KEY not in file: return {} @@ -760,23 +573,7 @@ def _read_annotations(file: h5py.File) -> dict[str, JsonValue]: def _read_json(file: h5py.File) -> str: - """Return the metadata document of an open file. - - Parameters - ---------- - file : h5py.File - The open file. - - Returns - ------- - str - The document. - - Raises - ------ - OutputError - If the file carries no metadata, so it is not a solprop output file. - """ + """Return the metadata document of an open file.""" if _METADATA_KEY not in file: msg = ( f"{file.filename} carries no {_METADATA_KEY!r} dataset, so it is not a " @@ -803,27 +600,7 @@ def _current_checkpoint( not inconsistent, and is ignored. A closing (non-running) record that disagrees with its checkpoint is refused, as before. - Parameters - ---------- - file : h5py.File - The open file. - checkpoint : solprop.output.schema.TerminalCheckpoint or None - The checkpoint it holds. - outcome : solprop.output.schema.RunOutcome or None - Its outcome. - refuse : bool - Whether to refuse an inconsistent state rather than keep it as read. - - Returns - ------- - solprop.output.schema.TerminalCheckpoint or None - The checkpoint to resume from, or ``None``. - - Raises - ------ - OutputError - If `refuse` and a checkpoint precedes the last plane or is unnamed by a - closing outcome. + - Returns: the checkpoint to resume from, or ``None``. """ if checkpoint is None: return None @@ -928,10 +705,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 f"ResumeState: {self.n_saved} planes saved", [ ("last plane", repr(self.plane)), @@ -1062,10 +836,7 @@ def path(self) -> Path: def _state_text(self) -> str: """Return the state of the file, for printing, without raising. - Returns - ------- - str - Open, closed or read-only, or a note that the file is gone. + - Returns: open, closed or read-only, or a note that the file is gone. """ if self._file is None and not self._path.exists(): return ( @@ -1094,10 +865,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, the file's path first. + - Returns: the title and ``(key, value)`` rows, the file's path first. """ title, rows = super()._describe() return title, [("file", tail(str(self._path))), *rows] @@ -1155,12 +923,8 @@ def _store(self, plane: SavedPlane, exported: StoredField | None) -> None: copy, and an uncompressed chunk no copy of HDF5's own either (a compressed one is filtered through HDF5's buffer). - Parameters - ---------- - plane : SavedPlane - The plane. - exported : solprop.output.schema.StoredField or None - Its field in the stored form, or ``None`` to convert the plane's own. + - ``exported``: its field in the stored form, or ``None`` to convert the plane's + own. """ self._refuse_readonly() super()._store(plane, exported) @@ -1205,13 +969,7 @@ def save_march(self, snapshot: MarchSnapshot) -> None: self._file.flush() def _refuse_readonly(self) -> None: - """Refuse to write through a handler that only reads. - - Raises - ------ - OutputError - If the handler was built with ``readonly=True``. - """ + """Refuse to write through a handler that only reads.""" if self._readonly: msg = ( f"{self._path} is open for reading only; build a handler with " @@ -1220,13 +978,7 @@ def _refuse_readonly(self) -> None: raise OutputError(msg) def _open_readonly(self) -> None: - """Open the file for reading, adopting its state and its record. - - Raises - ------ - OutputError - If the file cannot be opened, or carries no metadata. - """ + """Open the file for reading, adopting its state and its record.""" try: file = h5py.File(self._path, "r") except OSError as error: @@ -1243,18 +995,7 @@ def _open_readonly(self) -> None: self._recipe = read_recipe(file) def _open_file(self) -> h5py.File: - """Open the file once: fresh, or read-write to continue what it holds. - - Returns - ------- - h5py.File - The open handle, which the handler keeps until it is closed. - - Raises - ------ - OutputError - If appending and the file does not exist or cannot be opened. - """ + """Open the file once: fresh, or read-write to continue what it holds.""" self._path.parent.mkdir(parents=True, exist_ok=True) self._writer_lock.acquire() try: @@ -1265,13 +1006,7 @@ def _open_file(self) -> h5py.File: raise def _open_owned_file(self) -> h5py.File: - """Open a file after acquiring its independent writer lease. - - Returns - ------- - h5py.File - Owned live handle. - """ + """Open a file after acquiring its independent writer lease.""" if self._append_mode: try: file = h5py.File(self._path, "r+") @@ -1366,13 +1101,7 @@ def reopen(self) -> None: raise OutputError(f"cannot reopen optical writer: {error}") from error def _adopt_state(self, file: h5py.File) -> None: - """Take the plane count, the history length and the layout from the file. - - Parameters - ---------- - file : h5py.File - The file being continued. - """ + """Take the plane count, the history length and the layout from the file.""" metadata = _read_record(file) self._outcome = _read_outcome(file) self._resume_outcome_pending = self._append_mode and self._outcome is not None @@ -1409,15 +1138,7 @@ def _adopt_state(self, file: h5py.File) -> None: def _write_outcome( self, outcome: RunOutcome | None, checkpoint: TerminalCheckpoint | None ) -> None: - """Flush execution status and a separate accepted checkpoint to disk. - - Parameters - ---------- - outcome : RunOutcome or None - Execution status and accounting, or no status to write. - checkpoint : TerminalCheckpoint or None - Accepted field to store, or removal of an existing terminal state. - """ + """Flush execution status and a separate accepted checkpoint to disk.""" self._refuse_readonly() file = self._require_file() if checkpoint is not None: @@ -1439,18 +1160,7 @@ def _write_outcome( file.flush() def _require_file(self) -> h5py.File: - """Return the open file, refusing once the handler is closed. - - Returns - ------- - h5py.File - The open handle. - - Raises - ------ - OutputError - If the handler was never opened, or has been closed. - """ + """Return the open file, refusing once the handler is closed.""" if self._file is None: action = "reopen it" if self._closed else "call open(metadata) first" msg = f"the output file {self._path} is not open; {action}" @@ -1462,17 +1172,6 @@ def _write_metadata(self, metadata: RunMetadata) -> None: When appending, the record the file already carries is kept -- its creation time included -- and becomes the session's; the one offered must have its hash. - - Parameters - ---------- - metadata : RunMetadata - The record to store. - - Raises - ------ - OutputError - If appending and the two records' hashes differ; the message names what - changed. """ if self._file is None: self._file = self._open_file() @@ -1548,23 +1247,9 @@ def _new_dataset( ) -> h5py.Dataset: """Create one extendable dataset with the handler's compression, and keep it. - Parameters - ---------- - parent : h5py.Group - Where to create it. - key : str - Its accessor key: the dataset's name, prefixed by its group's. - shape : tuple of int - Shape of one entry, without the leading plane or attempt axis. - dtype : object - NumPy dtype of the entries. - room : int - Entries to allocate along the leading axis, zero-filled until written. - - Returns - ------- - h5py.Dataset - The extendable dataset, also kept as the handle the writer fills. + - ``shape``: shape of one entry, without the leading plane or attempt axis. + - ``room``: entries to allocate along the leading axis, zero-filled until + written. """ chunks = (1, *shape) if shape else (_COLUMN_CHUNK,) dataset = parent.create_dataset( @@ -1582,10 +1267,7 @@ def _new_dataset( def _adopt_handles(self, file: h5py.File) -> None: """Keep the datasets of a file being continued. - Parameters - ---------- - file : h5py.File - The file, open for writing, holding at least one plane's datasets. + - ``file``: the file, open for writing, holding at least one plane's datasets. """ for key in ("z", "field", "noise_phase"): if key in file: @@ -1610,14 +1292,9 @@ def _create_datasets( ) -> None: """Lay out every dataset from the shapes of the first plane. - Parameters - ---------- - plane : SavedPlane - The first plane, which fixes the statistics. - stored : numpy.ndarray or None - Its field in the optics convention, which fixes the field's shape and - whether a noise phase is stored; ``None`` on a field-free plane, whose - field datasets wait for the first plane that keeps one. + - ``stored``: its field in the optics convention, which fixes the field's shape + and whether a noise phase is stored; ``None`` on a field-free plane, whose + field datasets wait for the first plane that keeps one. """ file = self._require_file() # The count goes first: a file whose datasets exist always says how many of @@ -1661,12 +1338,7 @@ def _create_field_datasets( Sized for the planes the run reserved when it keeps every field, and growing with them; for exactly the named planes when it keeps some. - Parameters - ---------- - plane : SavedPlane - The first plane that keeps its field. - stored : numpy.ndarray - Its field in the optics convention. + - ``stored``: its field in the optics convention. """ file = self._require_file() retention = self.retention @@ -1688,21 +1360,12 @@ def _create_field_datasets( def _grow(self, kind: str, needed: int, room: int) -> int: """Make room for `needed` entries in every dataset of one kind, by doubling. - Parameters - ---------- - kind : {'plane', 'history', 'rows'} - Which datasets: the plane-indexed ones, the step-history columns or the - between-planes statistics columns. - needed : int - Entries the datasets must hold. - room : int - Entries they hold now. - - Returns - ------- - int - The entries they hold after growing: `room` when it was enough, otherwise - the largest of `needed`, twice `room` and, for planes, the reserved count. + - ``kind``: which datasets: the plane-indexed ones, the step-history columns or + the between-planes statistics columns. + - ``needed``: entries the datasets must hold. + - Returns: the entries they hold after growing: `room` when it was enough, + otherwise the largest of `needed`, twice `room` and, for planes, the reserved + count. """ if needed <= room: return room @@ -1721,11 +1384,6 @@ def _field_room(self, slot: int) -> None: are laid out for all of them at once, but a clean close trims them to the rows written, so a run continued after a close grows them back here, to every named plane, before its next kept field. - - Parameters - ---------- - slot : int - The row the next kept field is written to. """ if self.retention.fields == "all": return @@ -1746,17 +1404,13 @@ def _append( flushed before the file's ``n_saved`` count is raised, so that a stop part-way leaves the count at the last complete plane. - Parameters - ---------- - plane : SavedPlane - The plane: its field in the internal convention, or ``None`` where the - field was exported straight into the stored form - (:meth:`~solprop.output.schema.PlaneStore.save_exported`) and `stored` - alone holds it. A subclass whose own ``_append`` reads the plane's field - does not take exports (``PlaneStore.takes_exports``). - stored : numpy.ndarray or None - That field converted to the optics convention; ``None`` on a field-free - plane, which stores none. + - ``plane``: the plane: its field in the internal convention, or ``None`` where + the field was exported straight into the stored form + (:meth:`~solprop.output.schema.PlaneStore.save_exported`) and `stored` alone + holds it. A subclass whose own ``_append`` reads the plane's field does not + take exports (``PlaneStore.takes_exports``). + - ``stored``: that field converted to the optics convention; ``None`` on a + field-free plane, which stores none. """ file = self._require_file() if not self._handles: @@ -1793,22 +1447,7 @@ def _append( self._step_stats_length = rows_end def _append_rows(self, file: h5py.File, rows: StepStats, index: int) -> int: - """Write one plane's between-planes statistics rows into the open file. - - Parameters - ---------- - file : h5py.File - The open file. - rows : solprop.output.schema.StepStats - The rows of the plane being appended, possibly none. - index : int - The plane's index. - - Returns - ------- - int - The rows the file holds with this plane's. - """ + """Write one plane's between-planes statistics rows into the open file.""" group = _group(file, "step_stats") handles = self._handles for name in rows.names: @@ -1840,11 +1479,6 @@ def _trim(self, file: h5py.File) -> None: datasets' extents are the counts. The lengths are the file's own (:func:`_stored_counts`), so a plane whose write failed part-way, and was never counted, is cut off with the rest. - - Parameters - ---------- - file : h5py.File - The open file. """ planes, attempts, entries = _stored_counts(file) lengths = { @@ -1862,22 +1496,7 @@ def _trim(self, file: h5py.File) -> None: def _read(self, group: str, name: str) -> np.ndarray: """Return one stored array, read out of the open file. - Parameters - ---------- - group : str - ``''``, ``'stats'``, ``'step_history'`` or ``'step_stats'``. - name : str - Name within the group. - - Returns - ------- - numpy.ndarray - The stored array. - - Raises - ------ - OutputError - If the file is not open, or holds nothing of that name. + - ``group``: ``''``, ``'stats'``, ``'step_history'`` or ``'step_stats'``. """ file = self._require_file() if group == "step_stats" and ( @@ -1912,26 +1531,12 @@ def _read(self, group: str, name: str) -> np.ndarray: def _read_field(self, slot: int) -> np.ndarray: """Return one stored field by its row, reading that row alone. - Parameters - ---------- - slot : int - The row in the ``field`` dataset. - - Returns - ------- - numpy.ndarray - The stored field, optics convention. + - Returns: the stored field, optics convention. """ return np.asarray(_dataset(self._require_file(), "field")[slot]) def _write_recipe(self, record: RecipeRecord) -> None: - """Write the recipe group into the open file (SEP 41, S2). - - Parameters - ---------- - record : RecipeRecord - The recipe. - """ + """Write the recipe group into the open file (SEP 41, S2).""" self._refuse_readonly() write_recipe(self._require_file(), record) @@ -1940,18 +1545,6 @@ def _write_annotation(self, name: str, text: str) -> None: A closed handler reopens its file for the write and closes it again, so a run's driver can annotate the file after the run has closed it. - - Parameters - ---------- - name : str - The annotation's name. - text : str - Its JSON document. - - Raises - ------ - OutputError - If the handler only reads, or the file cannot be opened for writing. """ self._refuse_readonly() if self._suspended_identity is not None: @@ -2013,17 +1606,7 @@ def _release(self) -> None: def _write_annotation(file: h5py.File, name: str, text: str) -> None: - """Write, or replace, one post-run annotation of an open file. - - Parameters - ---------- - file : h5py.File - The file, open for writing. - name : str - The annotation's name. - text : str - Its JSON document. - """ + """Write, or replace, one post-run annotation of an open file.""" group = file.require_group(_ANNOTATIONS_KEY) if name in group: del group[name] @@ -2033,22 +1616,9 @@ def _write_annotation(file: h5py.File, name: str, text: str) -> None: def _stored_metadata(path: Path | str) -> tuple[RunMetadata, str]: """Return the record a file carries and the digest stored beside it. - Parameters - ---------- - path : pathlib.Path or str - The file to read. - - Returns - ------- - tuple - The :class:`solprop.output.schema.RunMetadata` and the ``configuration_hash`` - attribute the writer stored, which are checked against each other. - - Raises - ------ - OutputError - If the file cannot be opened, carries no metadata, or its stored digest does - not match the record beside it. + - Returns: the :class:`solprop.output.schema.RunMetadata` and the + ``configuration_hash`` attribute the writer stored, which are checked against each + other. """ try: with h5py.File(path, "r") as file: @@ -2069,22 +1639,7 @@ def _stored_metadata(path: Path | str) -> tuple[RunMetadata, str]: def _resume_refusal( path: Path | str, stored: RunMetadata, requested: RunMetadata ) -> str: - """Return the message refusing a resume, naming what changed. - - Parameters - ---------- - path : pathlib.Path or str - The file. - stored : RunMetadata - Its record. - requested : RunMetadata - The record of the run asked for. - - Returns - ------- - str - What differs, and what to do instead. - """ + """Return the message refusing a resume, naming what changed.""" return ( f"{path} cannot be resumed; {stored.summarise_differences(requested)}.\n" "A resume needs the same configuration hash: to change the physics or the " @@ -2097,23 +1652,6 @@ def _file_path(path: Path | str | Output, reader: str) -> Path: A handler is not a file: one that keeps nothing has nothing to continue, and one that has a file is reached through its path. - - Parameters - ---------- - path : pathlib.Path or str or solprop.output.schema.Output - What the caller gave. - reader : str - The function being called, for the message. - - Returns - ------- - pathlib.Path - The file. - - Raises - ------ - OutputError - If `path` is an output handler. """ if isinstance(path, Output): msg = ( @@ -2172,18 +1710,7 @@ def check_cache(path: Path | str | Output, metadata: RunMetadata) -> bool: def _check_plasma_benchmark(metadata: RunMetadata) -> None: - """Keep the historical gated equation out of every continuation path. - - Parameters - ---------- - metadata : RunMetadata - Stored run identity, including any private benchmark source label. - - Raises - ------ - OutputError - If the output was produced by the non-resumable gated benchmark. - """ + """Keep the historical gated equation out of every continuation path.""" if metadata.configuration.get("_benchmark_plasma_source"): raise OutputError("gated plasma benchmark output cannot be resumed") @@ -2201,20 +1728,6 @@ def _check_accessors( this build has dropped -- so the list is checked as a **subset** here instead. A file written before an accessor was added resumes; one written by a build that had an accessor this one does not is refused by the name it is missing. - - Parameters - ---------- - path : pathlib.Path or str - The file, for the message. - stored : solprop.output.schema.RunMetadata - The record the file carries. - metadata : solprop.output.schema.RunMetadata - The record of the run about to start. - - Raises - ------ - OutputError - Naming the accessors this build cannot supply. """ if stored.modal is None or metadata.modal is None: return @@ -2239,18 +1752,8 @@ def _check_accessors( def _last_history(file: h5py.File, index: int) -> tuple[StepHistory, StepHistory]: """Return the whole step history and the part belonging to one plane. - Parameters - ---------- - file : h5py.File - The open file. - index : int - Index of the plane whose slice is wanted. - - Returns - ------- - tuple - The whole history and the slice, both as - :class:`solprop.output.schema.StepHistory`. + - Returns: the whole history and the slice, both as + :class:`solprop.output.schema.StepHistory`. """ group = _group(file, "step_history") counts = np.asarray(_dataset(group, "counts")[: index + 1], dtype=np.int64) @@ -2270,17 +1773,7 @@ def _last_history(file: h5py.File, index: int) -> tuple[StepHistory, StepHistory def _last_step_stats(file: h5py.File, index: int) -> StepStats: """Return the between-planes statistics rows belonging to one plane. - Parameters - ---------- - file : h5py.File - The open file. - index : int - Index of the plane whose rows are wanted. - - Returns - ------- - solprop.output.schema.StepStats - The rows of that plane, empty for a run that recorded no periodic subset. + - Returns: the rows of that plane, empty for a run that recorded no periodic subset. """ group = _group(file, "step_stats") names = [ @@ -2409,20 +1902,9 @@ def _regular_resume_plane( ) -> tuple[SavedPlane, StepHistory]: """Read the last regular plane and its complete stored attempt history. - Parameters - ---------- - file : h5py.File - Open saved output with regular observations. - metadata : RunMetadata - Grid and output amplitude conventions. - index : int - Nonnegative regular-plane index to restore. - - Returns - ------- - tuple - Restored plane and the complete attempt history through that plane; the - plane's field and noise phase are ``None`` where the run kept no field. + - ``metadata``: grid and output amplitude conventions. + - Returns: restored plane and the complete attempt history through that plane; the + plane's field and noise phase are ``None`` where the run kept no field. """ # A stored rule's indices are already counted from the entrance. retention = _stored_retention(file) diff --git a/src/solprop/output/memory.py b/src/solprop/output/memory.py index 2dc557d6..96bf86e8 100644 --- a/src/solprop/output/memory.py +++ b/src/solprop/output/memory.py @@ -55,17 +55,7 @@ def _grown(block: np.ndarray, used: int) -> np.ndarray: """Return a copy of a block with twice the room, keeping the used entries. - Parameters - ---------- - block : numpy.ndarray - The current block, whose leading axis is the plane index. - used : int - How many leading entries carry data. - - Returns - ------- - numpy.ndarray - A new block of twice the leading length, with the used entries copied in. + - ``block``: the current block, whose leading axis is the plane index. """ grown = np.zeros((2 * block.shape[0], *block.shape[1:]), dtype=block.dtype) grown[:used] = block[:used] @@ -75,15 +65,7 @@ def _grown(block: np.ndarray, used: int) -> np.ndarray: def _same_array(one: np.ndarray, other: np.ndarray) -> bool: """Return whether two arrays are views of exactly the same memory. - Parameters - ---------- - one, other : numpy.ndarray - The arrays. - - Returns - ------- - bool - Whether they start at the same address with the same shape and strides. + - Returns: whether they start at the same address with the same shape and strides. """ return ( one.__array_interface__["data"][0] == other.__array_interface__["data"][0] @@ -95,17 +77,7 @@ def _same_array(one: np.ndarray, other: np.ndarray) -> bool: def _history_slice(columns: Mapping[str, np.ndarray], piece: slice) -> StepHistory: """Return one plane's step history out of the concatenated columns. - Parameters - ---------- - columns : collections.abc.Mapping - The concatenated history columns, keyed by field name. - piece : slice - The entries belonging to this plane. - - Returns - ------- - solprop.output.schema.StepHistory - The history of that plane. + - ``columns``: the concatenated history columns, keyed by field name. """ return StepHistory(**{name: columns[name][piece] for name in HISTORY_FIELDS}) @@ -113,17 +85,7 @@ def _history_slice(columns: Mapping[str, np.ndarray], piece: slice) -> StepHisto def _joined(pieces: Sequence[np.ndarray], dtype: type) -> np.ndarray: """Return the pieces of one column as one array, keeping its dtype when empty. - Parameters - ---------- - pieces : collections.abc.Sequence - One piece per plane, possibly none at all. - dtype : type - The column's dtype, which an empty concatenation would otherwise lose. - - Returns - ------- - numpy.ndarray - The concatenated column. + - ``dtype``: the column's dtype, which an empty concatenation would otherwise lose. """ if not pieces: return np.zeros(0, dtype=dtype) @@ -135,20 +97,11 @@ def _step_stats_slice( ) -> StepStats: """Return one plane's statistics rows out of the per-plane pieces. - Parameters - ---------- - columns : collections.abc.Mapping - The row columns, keyed by column name, each holding one piece per plane. - index : int - Which plane. - names : collections.abc.Sequence - The statistics the run recorded between planes. - - Returns - ------- - solprop.output.schema.StepStats - The rows of that plane, with no rows when it recorded none. Every plane - contributes a piece to both index columns, so this is always indexable. + - ``columns``: the row columns, keyed by column name, each holding one piece per + plane. + - ``names``: the statistics the run recorded between planes. + - Returns: the rows of that plane, with no rows when it recorded none. Every plane + contributes a piece to both index columns, so this is always indexable. """ return StepStats( z=columns["z"][index], @@ -235,13 +188,7 @@ def __init__(self, block: int = INITIAL_BLOCK) -> None: } def _write_metadata(self, metadata: RunMetadata) -> None: - """Record that a run has begun; the base class keeps the metadata itself. - - Parameters - ---------- - metadata : RunMetadata - The record being written. - """ + """Record that a run has begun; the base class keeps the metadata itself.""" # Guarded: the hash costs seconds on a large grid, and only this line needs it. if logger.isEnabledFor(logging.DEBUG): logger.debug( @@ -254,13 +201,8 @@ def _allocate(self, plane: SavedPlane, shape: tuple[int, ...] | None) -> None: The room is the planes the run reserved, or the handler's block when it reserved none. - Parameters - ---------- - plane : SavedPlane - The first plane, which fixes the statistic names. - shape : tuple of int or None - Its field's shape, which with its noise phase fixes the field block; - ``None`` on a field-free plane. + - ``shape``: its field's shape, which with its noise phase fixes the field + block; ``None`` on a field-free plane. """ room = self._planned or self._block self._capacity = room @@ -280,13 +222,7 @@ def _allocate_fields(self, plane: SavedPlane, shape: tuple[int, ...]) -> None: Every plane's room for a run that keeps every field, which then grows with the planes; exactly the named planes for one that keeps some. - Parameters - ---------- - plane : SavedPlane - The first plane that keeps its field, whose noise phase says whether one - is stored. - shape : tuple of int - Its field's shape, ``(n_modes, n_pol, n_omega)``. + - ``shape``: its field's shape, ``(n_modes, n_pol, n_omega)``. """ retention = self.retention rows = ( @@ -313,12 +249,7 @@ def _expand(self) -> None: def _make_room(self, plane: SavedPlane, shape: tuple[int, ...] | None) -> None: """Grow the blocks so that they have room for the next plane. - Parameters - ---------- - plane : SavedPlane - The next plane. - shape : tuple of int or None - Its field's shape, or ``None`` when it keeps none. + - ``shape``: its field's shape, or ``None`` when it keeps none. """ if self._capacity == 0: self._allocate(plane, shape) @@ -399,20 +330,7 @@ def _take_exported( plane: SavedPlane, exported: Complex[np.ndarray, "n_modes n_pol n_omega"], ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: - """Return the row holding an exported field, copying it in from elsewhere. - - Parameters - ---------- - plane : SavedPlane - The plane, without a field. - exported : numpy.ndarray - Its field in the stored form: this store's row, or another array. - - Returns - ------- - numpy.ndarray - The block's row that holds it. - """ + """Return the row holding an exported field, copying it in from elsewhere.""" self._make_room(plane, exported.shape) row = self._fields[self._n_fields] if not _same_array(row, exported): @@ -424,17 +342,7 @@ def _stored_field( ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: """Convert a plane's field straight into its room in the block. - Parameters - ---------- - plane : SavedPlane - The plane, which carries a field. - kind : {'field', 'envelope'} - The grid's spectral layout. - - Returns - ------- - numpy.ndarray - The block's row that now holds it. + - ``kind``: the grid's spectral layout. """ assert plane.field is not None self._make_room(plane, plane.field.shape) @@ -447,17 +355,14 @@ def _append( ) -> None: """Append one plane to the blocks, growing them when they are full. - Parameters - ---------- - plane : SavedPlane - The plane: its field in the internal convention, or ``None`` where the - field was exported straight into the stored form - (:meth:`~solprop.output.schema.PlaneStore.save_exported`) and `stored` - alone holds it. A subclass whose own ``_append`` reads the plane's field - does not take exports (``PlaneStore.takes_exports``). - stored : numpy.ndarray or None - That field converted to the optics convention, in its row of the block - (:meth:`_stored_field`); ``None`` on a field-free plane, which stores none. + - ``plane``: the plane: its field in the internal convention, or ``None`` where + the field was exported straight into the stored form + (:meth:`~solprop.output.schema.PlaneStore.save_exported`) and `stored` alone + holds it. A subclass whose own ``_append`` reads the plane's field does not + take exports (``PlaneStore.takes_exports``). + - ``stored``: that field converted to the optics convention, in its row of the + block (:meth:`_stored_field`); ``None`` on a field-free plane, which stores + none. """ self._make_room(plane, None if stored is None else stored.shape) index = self._n_saved @@ -480,11 +385,6 @@ def _append_rows(self, rows: StepStats) -> None: above one a short plane need not -- and it still contributes an empty piece to every column, so that one cumulative sum of the counts locates every plane in every column. - - Parameters - ---------- - rows : solprop.output.schema.StepStats - The rows of the plane being appended. """ index = self._n_saved self._step_counts[index] = rows.n_rows @@ -507,22 +407,7 @@ def _append_rows(self, rows: StepStats) -> None: def _read(self, group: str, name: str) -> np.ndarray: """Return one stored array, trimmed to the planes actually saved. - Parameters - ---------- - group : str - ``''``, ``'stats'``, ``'step_history'`` or ``'step_stats'``. - name : str - Name within the group. - - Returns - ------- - numpy.ndarray - The stored array. - - Raises - ------ - OutputError - If the group holds nothing of that name, or no plane has been saved. + - ``group``: ``''``, ``'stats'``, ``'step_history'`` or ``'step_stats'``. """ saved = self._n_saved if group == "": @@ -551,22 +436,7 @@ def _read(self, group: str, name: str) -> np.ndarray: def _read_plane_array(self, name: str, saved: int) -> np.ndarray: """Return one of the arrays stored once per plane. - Parameters - ---------- - name : str - ``'z'``, ``'field'`` or ``'noise_phase'``. - saved : int - How many planes have been stored. - - Returns - ------- - numpy.ndarray - The array, trimmed to the planes actually saved. - - Raises - ------ - OutputError - If nothing has been saved yet, or the run carried no noise model. + - ``name``: ``'z'``, ``'field'`` or ``'noise_phase'``. """ if name == "z": return self._z[:saved] @@ -589,23 +459,6 @@ def _read_rows(self, name: str, saved: int) -> np.ndarray: A run that recorded no periodic subset reads back as an **empty channel** rather than an error, so that processing and plotting can ask for it unconditionally and fall back to the planes. - - Parameters - ---------- - name : str - ``'counts'``, ``'z'``, ``'step'`` or a statistic name. - saved : int - How many planes have been stored. - - Returns - ------- - numpy.ndarray - The column, concatenated over planes. - - Raises - ------ - OutputError - If the run recorded a channel and this is not one of its columns. """ if name == "counts": return self._step_counts[:saved] diff --git a/src/solprop/output/resumable.py b/src/solprop/output/resumable.py index 13f5aaed..e59a9731 100644 --- a/src/solprop/output/resumable.py +++ b/src/solprop/output/resumable.py @@ -90,15 +90,7 @@ def rebuildable(self) -> bool: def _writer_alive(path: Path) -> bool: """Return whether another writer holds a file. - Parameters - ---------- - path : pathlib.Path - The file. - - Returns - ------- - bool - True when its writer lock cannot be taken. + - Returns: True when its writer lock cannot be taken. """ lock = WriterLock(path) try: @@ -110,18 +102,7 @@ def _writer_alive(path: Path) -> bool: def _recipe_format(path: Path) -> int | None: - """Return the format of the recipe a file records, or ``None``. - - Parameters - ---------- - path : pathlib.Path - The file. - - Returns - ------- - int or None - The ``recipe`` group's format attribute. - """ + """Return the format of the recipe a file records, or ``None``.""" with h5py.File(path, "r") as file: group = file.get("recipe") if not isinstance(group, h5py.Group): @@ -132,15 +113,7 @@ def _recipe_format(path: Path) -> int | None: def _schema(path: Path) -> int | None: """Return the output schema a file declares. - Parameters - ---------- - path : pathlib.Path - The file. - - Returns - ------- - int or None - Its ``schema_version`` attribute, or ``None`` when it has none. + - Returns: its ``schema_version`` attribute, or ``None`` when it has none. """ with h5py.File(path, "r") as file: version = file.attrs.get("schema_version") @@ -148,18 +121,7 @@ def _schema(path: Path) -> int | None: def _unusable_state(state: ResumeState) -> str | None: - """Return why a file's last state cannot be continued, or ``None`` if it can. - - Parameters - ---------- - state : solprop.output.hdf5.ResumeState - The file's resumable state. - - Returns - ------- - str or None - The reason. - """ + """Return why a file's last state cannot be continued, or ``None`` if it can.""" if state.plane.field is None: return "its last plane kept no field and no terminal checkpoint follows it" if state.outcome is not None and state.outcome.logical_calls: @@ -210,32 +172,21 @@ def resumability(path: str | Path | Output, *, gate: bool = False) -> Resumabili "none" if found is None else "newer" if found > FORMAT else "stored" ) - def refused(reason: str) -> Resumability: - """Return the answer for a file that cannot continue. - - Parameters - ---------- - reason : str - Why. - - Returns - ------- - Resumability - The answer. - """ + def _refused(reason: str) -> Resumability: + """Return the answer for a file that cannot continue.""" return Resumability(path, False, None, None, reason, recipe, found) if schema != SCHEMA_VERSION: - return refused( + return _refused( f"the file is of output schema {schema}, and this solprop reads " f"{SCHEMA_VERSION} only" ) if _writer_alive(path): - return refused("a writer still holds the file") + return _refused("a writer still holds the file") try: state = resume(path) except OutputError as error: - return refused(f"no state to continue from: {error}") + return _refused(f"no state to continue from: {error}") planes = np.asarray( _decode(state.metadata.configuration["save_planes"]), # type: ignore[arg-type] dtype=float, @@ -243,10 +194,10 @@ def refused(reason: str) -> Resumability: position = float(state.plane.z) upcoming = int(np.searchsorted(planes, position, side="right")) if state.checkpoint is None and upcoming >= planes.size: - return refused("the run is complete: no work is left") + return _refused("the run is complete: no work is left") unusable = _unusable_state(state) if unusable is not None: - return refused(unusable) + return _refused(unusable) mode: ResumeMode = "interpolated" if state.interpolated else True differences = None gate_error = None diff --git a/src/solprop/output/scan.py b/src/solprop/output/scan.py index fec9af4a..c62a7fc4 100644 --- a/src/solprop/output/scan.py +++ b/src/solprop/output/scan.py @@ -43,33 +43,12 @@ def _json(value: object) -> str: The text stands alone, so that a Queue's manifest can rebuild the scan from it: an array keeps its values here, whatever its size. A run record's arrays go beside its text instead, through :attr:`PointResult.metadata_arrays`. - - Parameters - ---------- - value : object - Value to validate or serialise. - - Returns - ------- - str - Canonical JSON text. """ return json.dumps(_inlined(serialise(value)), sort_keys=True, separators=(",", ":")) def _hash(text: str) -> str: - """Identify a serialised common record by its SHA-256 digest. - - Parameters - ---------- - text : str - Canonical serialised content. - - Returns - ------- - str - Hexadecimal SHA-256 digest. - """ + """Identify a serialised common record by its SHA-256 digest.""" return hashlib.sha256(text.encode()).hexdigest() @@ -80,14 +59,7 @@ def _hash(text: str) -> str: def _write_array(group: h5py.Group, name: str, values: np.ndarray) -> None: """Store numerical or textual axes without pickling objects. - Parameters - ---------- - group : h5py.Group - Destination or source HDF5 group. - name : str - Dataset path or column name. - values : np.ndarray - One-dimensional axis or result array in its declared physical units. + - ``values``: One-dimensional axis or result array in its declared physical units. """ if values.dtype.kind in "US": group.create_dataset( @@ -155,13 +127,7 @@ class PointResult: def _validate_metadata(result: PointResult) -> None: - """Validate provenance even when propagation or reduction failed. - - Parameters - ---------- - result : PointResult - Point payload with a constructed simulation's provenance. - """ + """Validate provenance even when propagation or reduction failed.""" if len(result.configuration_hash) != 64: raise OutputError("completed result requires a configuration hash") document = json.loads(result.metadata_json) @@ -295,15 +261,7 @@ def record_recipe(self, record: RecipeRecord | None) -> None: write_recipe(self.file, record) def _initialise(self, scan: Scan, description: str) -> None: - """Create scheduling columns before any point is dispatched. - - Parameters - ---------- - scan : Scan - Cartesian scan or selection retaining global indices. - description : str - Serialised scan identity and retention contract. - """ + """Create scheduling columns before any point is dispatched.""" file = self.file file.attrs.update( scan_schema_version=1, @@ -461,17 +419,9 @@ def commit(self, result: PointResult) -> None: def _conflict(self, result: PointResult) -> str: """Explain a refused reuse by what changed in the run record. - Parameters - ---------- - result : PointResult - The conflict a worker reported, with the point's new run metadata. - - Returns - ------- - str - The worker's explanation, followed by what changed when both records can - be read: a scan with a changed record, a tolerance among it, writes to a - new file. + - Returns: the worker's explanation, followed by what changed when both records + can be read: a scan with a changed record, a tolerance among it, writes to a + new file. """ key = f"staged/{result.index}" if not result.metadata_json or key not in self.file: @@ -487,13 +437,7 @@ def _conflict(self, result: PointResult) -> str: return f"{result.error}; {stored.summarise_differences(new)}" def _stage(self, result: PointResult) -> None: - """Write recoverable point arrays and references to common metadata. - - Parameters - ---------- - result : PointResult - Complete point payload with status and provenance. - """ + """Write recoverable point arrays and references to common metadata.""" file = self.file key = f"staged/{result.index}" if key in file: @@ -566,18 +510,7 @@ def _stage(self, result: PointResult) -> None: def _common_record(self, document: object) -> object: # Array leaves are content-addressed before recursion: a dense synthesis # matrix belongs in one record, not thousands of scalar records. - """Deduplicate metadata leaves by their content digest. - - Parameters - ---------- - document : object - Serialised metadata or content-addressed reference tree. - - Returns - ------- - object - Reference tree with leaves stored once. - """ + """Deduplicate metadata leaves by their content digest.""" if ( isinstance(document, dict) and "__ndarray__" not in document @@ -687,12 +620,7 @@ def _collect_layouts(self) -> None: def _rectangular(self, done: np.ndarray, layout_name: str | None) -> None: """Expose uniform arrays over the complete global point axis. - Parameters - ---------- - done : np.ndarray - Completed global indices in increasing order. - layout_name : str | None - Uniform layout digest, or None for multiple layouts. + - ``layout_name``: uniform layout digest, or None for multiple layouts. """ file = self.file for name in ("stats", "step_stats", "reduced", "fields", "z"): @@ -739,13 +667,7 @@ def _rectangular(self, done: np.ndarray, layout_name: str | None) -> None: file.flush() def _common_fields(self, done: np.ndarray) -> None: - """Record metadata paths that are constant or vary between points. - - Parameters - ---------- - done : np.ndarray - Completed global indices in increasing order. - """ + """Record metadata paths that are constant or vary between points.""" common = _group(self.file, "common") if not len(done): return @@ -769,17 +691,7 @@ def _common_fields(self, done: np.ndarray) -> None: def _reference_paths(document: object, prefix: str = "") -> dict[str, object]: """Flatten reference paths for comparison without loading dense arrays. - Parameters - ---------- - document : object - Serialised metadata or content-addressed reference tree. - prefix : str - Dotted metadata path accumulated by recursion. - - Returns - ------- - dict - Dotted paths and their common-record references. + - Returns: dotted paths and their common-record references. """ if not isinstance(document, dict) or set(document) == {"ref"}: return {prefix: document} @@ -790,18 +702,7 @@ def _reference_paths(document: object, prefix: str = "") -> dict[str, object]: def _array_paths(group: h5py.Group) -> list[str]: - """List datasets beneath a point group. - - Parameters - ---------- - group : h5py.Group - Destination or source HDF5 group. - - Returns - ------- - list of str - Dataset paths relative to the group. - """ + """List datasets beneath a point group.""" paths: list[str] = [] group.visititems( lambda key, value: ( @@ -812,20 +713,7 @@ def _array_paths(group: h5py.Group) -> list[str]: def _expand(file: h5py.File, value: object) -> object: - """Resolve common metadata references in a committed point. - - Parameters - ---------- - file : h5py.File - Open controller-owned HDF5 file. - value : object - Value to validate or serialise. - - Returns - ------- - object - Metadata with common-record references resolved. - """ + """Resolve common metadata references in a committed point.""" if isinstance(value, dict): if set(value) == {"ref"}: return json.loads(str(_dataset(file, f"common/{value['ref']}").asstr()[()])) @@ -834,24 +722,7 @@ def _expand(file: h5py.File, value: object) -> object: def _refuse_point_schema(file: h5py.File, index: int, document: object) -> None: - """Refuse one point's stored run record if it declares another output schema. - - Parameters - ---------- - file : h5py.File - Open scan file holding the point's common metadata records. - index : int - Global point index, named in the refusal. - document : object - The point's parsed ``metadata_refs``. - - Raises - ------ - OutputError - If the record declares an output schema other than - :data:`~solprop.output.schema.SCHEMA_VERSION`, in the words a run file is - refused with, after the point's index. - """ + """Refuse one point's stored run record if it declares another output schema.""" declared = document.get("schema_version") if isinstance(document, dict) else None try: _refuse_schema(_expand(file, declared)) @@ -865,16 +736,6 @@ def _refuse_other_schemas(file: h5py.File) -> None: The scan's own ``scan_schema_version`` versions its container; the output schema is declared by each point's run record, done or failed, and a record of any schema but this release's is refused by name, as a run file is (ADR-058). - - Parameters - ---------- - file : h5py.File - Open scan file. - - Raises - ------ - OutputError - If any staged point's record declares another output schema. """ accepted: set[str] = set() for name, group in _group(file, "staged").items(): diff --git a/src/solprop/output/schema.py b/src/solprop/output/schema.py index 510c715d..1f07b5c8 100644 --- a/src/solprop/output/schema.py +++ b/src/solprop/output/schema.py @@ -238,17 +238,8 @@ def _tag_unreproducible(value: object, key: str) -> dict[str, JsonValue]: """Return the record of an object that cannot be rebuilt from the file. - Parameters - ---------- - value : object - The object to describe. - key : str - Tag naming why it is irreducible, ``'__callable__'`` or ``'__repr__'``. - - Returns - ------- - dict - A record holding the object's ``repr`` and ``reproducible: False``. + - ``key``: tag naming why it is irreducible, ``'__callable__'`` or ``'__repr__'``. + - Returns: a record holding the object's ``repr`` and ``reproducible: False``. """ logger.debug("configuration entry recorded by repr only: %s", key) return {key: repr(value), "reproducible": False} @@ -266,15 +257,7 @@ def _serialise_dataclass(value: object) -> dict[str, JsonValue]: :class:`~solprop.modes.guide.ChannelSource` holds the provider object, whose identity the run records once beside its channels. - Parameters - ---------- - value : object - A dataclass instance. - - Returns - ------- - dict - ``{'__class__': 'module.QualName', : , ...}``. + - Returns: ``{'__class__': 'module.QualName', : , ...}``. """ kind = type(value) name = f"{kind.__module__}.{kind.__qualname__}" @@ -295,15 +278,7 @@ def _serialise_enum(value: Enum) -> dict[str, JsonValue]: The class matters as much as the value: two enumerations may share a member value and mean different things, and the configuration hash has to tell them apart. - Parameters - ---------- - value : enum.Enum - The member. - - Returns - ------- - dict - ``{'__enum__': 'module.QualName', 'value': }``. + - Returns: ``{'__enum__': 'module.QualName', 'value': }``. """ kind = type(value) return { @@ -319,11 +294,6 @@ class _ArrayReference(dict): and the array travels with it until the record is stored (:meth:`RunMetadata.record_arrays`). - Parameters - ---------- - values : numpy.ndarray - The array, kept by reference and not copied. - Attributes ---------- array : numpy.ndarray @@ -350,15 +320,8 @@ def _serialise_numpy(value: np.ndarray | np.generic) -> JsonValue: complex number and a nested list of them would not survive a round trip. A numeric array of more than :data:`RECORD_ARRAY_LIMIT` elements is a reference instead. - Parameters - ---------- - value : numpy.ndarray or numpy.generic - The array or the scalar. - - Returns - ------- - dict or bool or int or float or str - A ``__ndarray__`` record for an array, and the plain Python value of a scalar. + - Returns: a ``__ndarray__`` record for an array, and the plain Python value of a + scalar. """ if isinstance(value, np.generic): return serialise(value.item()) @@ -370,15 +333,7 @@ def _serialise_numpy(value: np.ndarray | np.generic) -> JsonValue: def _inline_array(value: np.ndarray) -> dict[str, JsonValue]: """Return an array as a ``__ndarray__`` record, whatever its size. - Parameters - ---------- - value : numpy.ndarray - The array. - - Returns - ------- - dict - Its dtype, its shape and its values. + - Returns: its dtype, its shape and its values. """ record: dict[str, JsonValue] = { "dtype": str(value.dtype), @@ -400,23 +355,6 @@ def _inlined(value: JsonValue) -> JsonValue: For text with no store beside it, which has to stand alone: only a run record keeps its arrays beside its text (:meth:`RunMetadata.record_arrays`). A reference already read back from text carries no array and stays as it is. - - Parameters - ---------- - value : NoneType or bool or int or float or str or list or dict - What :func:`serialise` returned. - - Returns - ------- - NoneType or bool or int or float or str or list or dict - The structure, with no array left to store. - - Examples - -------- - >>> import numpy as np - >>> from solprop.output.schema import _inlined, serialise - >>> sorted(_inlined(serialise({"axis": np.zeros(5000)}))["axis"]) - ['__ndarray__', 'dtype', 'shape'] """ if isinstance(value, _ArrayReference): return _inline_array(value.array) @@ -428,19 +366,7 @@ def _inlined(value: JsonValue) -> JsonValue: def _serialise_sequence(value: object) -> list[JsonValue]: - """Return a sequence or a set as a list, ordering set members deterministically. - - Parameters - ---------- - value : object - A sequence, a set or a frozenset. - - Returns - ------- - list - The serialised members. A set's are sorted by their canonical JSON text, so - that the configuration hash does not depend on set iteration order. - """ + """Return a sequence or a set as a list, ordering set members deterministically.""" # `object` is not declared iterable; the caller has already checked that this is # a set or a sequence. members = [serialise(item) for item in value] # type: ignore[union-attr] - checked by the caller @@ -450,18 +376,7 @@ def _serialise_sequence(value: object) -> list[JsonValue]: def _serialise_object(value: object) -> JsonValue: - """Return a structured or opaque object as a JSON-safe structure. - - Parameters - ---------- - value : object - Anything that is not a primitive, an enumeration or a NumPy value. - - Returns - ------- - NoneType or bool or int or float or str or list or dict - A structure of JSON primitives. - """ + """Return a structured or opaque object as a JSON-safe structure.""" if isinstance(value, Path): return str(value) if type(value) is CubicSpline: @@ -622,22 +537,7 @@ def _array_name(digest: ArrayDigest) -> str: equal bytes and different dtypes or shapes are different arrays, and one must not take the other's place in the store. - Parameters - ---------- - digest : ArrayDigest - The array's digest, dtype and shape. - - Returns - ------- - str - The three of them, joined. - - Examples - -------- - >>> import numpy as np - >>> from solprop.output.schema import ArrayDigest, _array_name - >>> _array_name(ArrayDigest.of(np.zeros((50, 100)))).split("-")[1:] - ['float64', '50x100'] + - ``digest``: the array's digest, dtype and shape. """ shape = "x".join(str(size) for size in digest.shape) return f"{digest.sha256}-{digest.dtype}-{shape}" @@ -730,33 +630,13 @@ def recorded_input(value: object) -> object: def _recorded_inline(values: np.ndarray) -> bool: """Return whether a run record carries an array input itself, not its digest. - Parameters - ---------- - values : numpy.ndarray - The array input. - - Returns - ------- - bool - True for at most :data:`INLINE_ARRAY_LIMIT` elements. + - Returns: True for at most :data:`INLINE_ARRAY_LIMIT` elements. """ return values.size <= INLINE_ARRAY_LIMIT def _canonical_json(value: JsonValue) -> str: - """Return the one textual form of a structure that a digest may be taken over. - - Parameters - ---------- - value : NoneType or bool or int or float or str or list or dict - A structure of JSON primitives. - - Returns - ------- - str - Compact JSON with sorted keys, so that two equal structures built in different - orders produce identical text. - """ + """Return the one textual form of a structure that a digest may be taken over.""" return json.dumps(value, sort_keys=True, separators=(",", ":")) @@ -767,15 +647,7 @@ def _decode_array(record: Mapping[str, object]) -> np.ndarray: only the dtype has to be restored, and the imaginary part rejoined when there is one. - Parameters - ---------- - record : collections.abc.Mapping - A record produced by :func:`serialise` from a NumPy array. - - Returns - ------- - numpy.ndarray - The array, with its original dtype and shape. + - Returns: the array, with its original dtype and shape. """ dtype = np.dtype(str(record["dtype"])) if "__imaginary__" in record: @@ -798,15 +670,7 @@ def _decode(value: object) -> object: JSON, because the physics objects it describes are not reconstructible in general (a callable was recorded by ``repr``). - Parameters - ---------- - value : object - A value produced by :func:`serialise`, or an array read from beside the record. - - Returns - ------- - object - The rebuilt value, or ``value`` itself when there is nothing to rebuild. + - Returns: the rebuilt value, or ``value`` itself when there is nothing to rebuild. """ if isinstance(value, dict) and "__ndarray__" in value: return _decode_array(value) @@ -818,28 +682,7 @@ def _resolved( arrays: Mapping[str, object], found: dict[str, np.ndarray], ) -> object: - """Return a stored document with each array reference replaced by its array. - - Parameters - ---------- - value : object - The document, as :func:`json.loads` read it. - arrays : collections.abc.Mapping - What holds the arrays, by name (:func:`_array_name`): an HDF5 group, or a - mapping of arrays. - found : dict - The arrays read, by name, which this fills. - - Returns - ------- - object - The document, with an array in place of each reference. - - Raises - ------ - OutputError - If an array is absent, or its bytes or its layout are not what name it. - """ + """Return a stored document with each array reference replaced by its array.""" if isinstance(value, list): return [_resolved(item, arrays, found) for item in value] if not isinstance(value, dict): @@ -863,24 +706,7 @@ def _resolved( def _dotted(path: Sequence[str | int]) -> str: - """Return a path into a stored record as a refusal writes it. - - Parameters - ---------- - path : collections.abc.Sequence - Mapping keys and list indices. - - Returns - ------- - str - The keys joined by dots, with each index in brackets. - - Examples - -------- - >>> from solprop.output.schema import _dotted - >>> _dotted(("modes", 2, "label")) - 'modes[2].label' - """ + """Return a path into a stored record as a refusal writes it.""" text = "" for part in path: text += f"[{part}]" if isinstance(part, int) else f".{part}" if text else part @@ -895,31 +721,7 @@ def _stored_entry( default: object = None, required: bool = False, ) -> object: - """Return one entry of a stored record, reading an absent one as `default`. - - Parameters - ---------- - document : collections.abc.Mapping - The stored mapping. - name : str - The entry. - path : tuple of str - Where the mapping sits in the run record. - default : object, optional - What an absent entry reads as. - required : bool, optional - Whether an absent entry is refused. - - Returns - ------- - object - The entry, or `default`. - - Raises - ------ - OutputError - If a required entry is absent. - """ + """Return one entry of a stored record, reading an absent one as `default`.""" if name in document: return document[name] if required: @@ -939,25 +741,6 @@ def _rebuild[T]( wrote stays readable: a field the class no longer has is dropped, and an absent field takes the field's default. A resume never compares the rebuilt instance: it compares the stored text (:meth:`RunMetadata.configuration_hash`). - - Parameters - ---------- - build : type - The record dataclass to rebuild, called with the fields as keyword arguments. - record : collections.abc.Mapping - Its serialised form. - path : tuple, optional - Where the record sits in the run record, for a refusal. - - Returns - ------- - object - The rebuilt record. - - Raises - ------ - OutputError - If a field with no default is absent. """ fields = {entry.name: entry for entry in dataclasses.fields(build) if entry.init} # pyright: ignore[reportArgumentType] - a record dataclass arguments = {name: _decode(item) for name, item in record.items() if name in fields} @@ -981,18 +764,8 @@ def _rebuild[T]( def _run_git(arguments: Sequence[str], root: Path) -> str | None: """Return the output of one read-only git command, or ``None`` if it cannot run. - Parameters - ---------- - arguments : collections.abc.Sequence - Arguments after ``git``. Passed as a list, never through a shell. - root : pathlib.Path - Directory to run in. - - Returns - ------- - str or None - The stripped standard output, or ``None`` when git is absent, the directory is - not a repository, or the command fails or times out. + - Returns: the stripped standard output, or ``None`` when git is absent, the + directory is not a repository, or the command fails or times out. """ try: finished = subprocess.run( @@ -1188,15 +961,8 @@ def detect(cls) -> "GitProvenance | None": def _tracks(path: Path) -> bool: """Return whether the git repository around `path` tracks it, asked once. - Parameters - ---------- - path : pathlib.Path - A file of the package. - - Returns - ------- - bool - ``True`` when git runs, finds a repository and that repository tracks the file. + - Returns: ``True`` when git runs, finds a repository and that repository tracks the + file. """ return _run_git(["ls-files", "--error-unmatch", path.name], path.parent) is not None @@ -1204,15 +970,8 @@ def _tracks(path: Path) -> bool: def _version_of(name: str) -> str: """Return an installed distribution's version, or ``'unknown'``. - Parameters - ---------- - name : str - Distribution name. - - Returns - ------- - str - The version string, or ``'unknown'`` when the distribution is not installed. + - Returns: the version string, or ``'unknown'`` when the distribution is not + installed. """ try: return _package_version(name) @@ -1251,10 +1010,7 @@ def _installed_versions() -> dict[str, str]: Every record asks, so every scan point did; what is installed does not change under a running process. - Returns - ------- - dict - The versions; callers copy it. + - Returns: the versions; callers copy it. """ return { "solprop": _version_of("solprop"), @@ -1897,10 +1653,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. """ attempts = int(np.asarray(self.z).size) if not attempts: @@ -2114,15 +1867,7 @@ def _adopted(values: np.ndarray) -> np.ndarray: :meth:`solprop.backends.protocol.HostStepping.observe` promises, and never for a batch member, whose view would pin the whole batch. - Parameters - ---------- - values : numpy.ndarray - The array, of the type a plane holds. - - Returns - ------- - numpy.ndarray - The same array, read-only. + - Returns: the same array, read-only. """ values.setflags(write=False) _ADOPTED[id(values)] = values @@ -2134,17 +1879,7 @@ def _plane_array( ) -> np.ndarray: """Return an array as a :class:`SavedPlane` holds it: read-only, nobody else's. - Parameters - ---------- - values : numpy.ndarray - The array given to the plane, of any numeric dtype. - dtype : type - The element type the plane holds. - - Returns - ------- - numpy.ndarray - `values` itself when :func:`_adopted` marked it, a frozen copy otherwise. + - Returns: `values` itself when :func:`_adopted` marked it, a frozen copy otherwise. """ if _ADOPTED.get(id(values)) is values: return values @@ -2227,20 +1962,14 @@ def __repr__(self) -> str: def _field_text(self) -> str: """Return the field summarised, or a note that the plane keeps none. - Returns - ------- - str - The field's shape, or ``"no field"``. + - Returns: the field's shape, or ``"no field"``. """ return "no field" if self.field is None else summarise(self.field) 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 f"SavedPlane at z = {engineering(self.z, 'm')}", [ ("field", self._field_text()), @@ -2343,18 +2072,6 @@ def _with_phase(plane: SavedPlane, phase: np.ndarray) -> SavedPlane: The store's own view of a plane whose field it holds apart, in the stored form (:class:`StoredField`); it never leaves the store, whose hooks read the noise phase from the plane as from one that carries its field. - - Parameters - ---------- - plane : SavedPlane - The plane, without a field. - phase : numpy.ndarray - The field's noise phase. - - Returns - ------- - SavedPlane - A copy of `plane` with the phase. """ held = dataclasses.replace(plane) object.__setattr__(held, "noise_phase", _plane_array(phase, np.float64)) @@ -2435,18 +2152,7 @@ def __post_init__(self) -> None: def _json_failure(part: object) -> str | None: - """Return why a part of a run outcome is not finite JSON, or ``None`` if it is. - - Parameters - ---------- - part : object - The part. - - Returns - ------- - str or None - The encoder's reason. - """ + """Return why a part of a run outcome is not finite JSON, or ``None`` if it is.""" try: json.dumps(part, allow_nan=False) except (TypeError, ValueError) as error: @@ -2460,16 +2166,6 @@ def _json_copy(value: object) -> object: Cheaper than another JSON round trip, and than :func:`copy.deepcopy`, because the value is known to hold only dicts, lists, strings, numbers, booleans and ``None``. - - Parameters - ---------- - value : object - The value. - - Returns - ------- - object - Its copy: new dicts and lists, the same immutable leaves. """ if isinstance(value, dict): return {key: _json_copy(item) for key, item in value.items()} @@ -2489,19 +2185,6 @@ def _amended_outcome( earlier closing records were validated when it took them, so a saved plane validates its record once. The caller hands over copies of them (:func:`_json_copy`), so that each outcome holds its own and a change to one read back reaches no other. - - Parameters - ---------- - outcome : RunOutcome - The validated outcome. - previous_executions : tuple of collections.abc.Mapping - The store's closing records of earlier executions. - - Returns - ------- - RunOutcome - The amended copy; its other parts are shared with `outcome`, which the store - holds alone. """ amended = copy.copy(outcome) object.__setattr__(amended, "previous_executions", previous_executions) @@ -2511,15 +2194,7 @@ def _amended_outcome( def _outcome_record(record: Mapping[str, object]) -> dict[str, object]: """Own finite JSON values so later provider mutations cannot change a snapshot. - Parameters - ---------- - record : Mapping[str, object] - Execution facts composed of finite JSON values. - - Returns - ------- - dict - Independently owned nested values, detached from provider state. + - ``record``: execution facts composed of finite JSON values. """ try: return cast( @@ -2695,18 +2370,8 @@ def _holds_carried(plane: SavedPlane, carried: SavedPlane) -> bool: carried attempts, every column equal; a new one from the resumed march starts where the resume did, with attempts of its own and its own wall clock. - Parameters - ---------- - plane : SavedPlane - The checkpoint's plane. - carried : SavedPlane - The resumed checkpoint's plane, whose attempts no regular plane holds yet. - - Returns - ------- - bool - True when `plane`'s history and rows start with `carried`'s, or `carried` - holds nothing to add. + - Returns: True when `plane`'s history and rows start with `carried`'s, or `carried` + holds nothing to add. """ before, history = carried.step_history, plane.step_history if before.n_steps == 0: @@ -2722,20 +2387,7 @@ def _holds_carried(plane: SavedPlane, carried: SavedPlane) -> bool: def _prepend_checkpoint_history( previous: SavedPlane, current: SavedPlane ) -> SavedPlane: - """Retain attempts and periodic rows preceding a fresh-start resume. - - Parameters - ---------- - previous : SavedPlane - Pending checkpoint carrying attempts before the restart. - current : SavedPlane - New accepted field and attempts after the restart. - - Returns - ------- - SavedPlane - Current field with both histories and adjusted periodic-row indices. - """ + """Retain attempts and periodic rows preceding a fresh-start resume.""" history = StepHistory( **{ name: np.concatenate( @@ -2770,16 +2422,7 @@ def _differences( ) -> None: """Collect the dotted paths at which two serialised structures differ. - Parameters - ---------- - left : NoneType or bool or int or float or str or list or dict - The first structure. - right : NoneType or bool or int or float or str or list or dict - The second. - path : str - Dotted path of the position being compared. - found : list - Accumulator the paths are appended to, in depth-first order. + - ``right``: the second. """ if isinstance(left, dict) and isinstance(right, dict): if _RECORD_ARRAY in left and _RECORD_ARRAY in right: @@ -2808,15 +2451,7 @@ def _differences( def _optional_text(value: JsonValue) -> str | None: """Return a stored optional string, ``None`` when null. - Parameters - ---------- - value : object - The stored value. - - Returns - ------- - str or None - The string, or ``None``. + - Returns: the string, or ``None``. """ return None if value is None else str(value) @@ -2827,16 +2462,6 @@ def _refuse_schema(declared: object) -> None: One rule for every other number: an earlier release wrote a lower output schema, a development version of solprop wrote schemas up to 28, and a later release may write a higher one, and this release reads none of them (ADR-058). - - Parameters - ---------- - declared : object - The ``schema_version`` the record declares. - - Raises - ------ - OutputError - If `declared` is not :data:`SCHEMA_VERSION`. """ if type(declared) is int and declared == SCHEMA_VERSION: return @@ -2876,16 +2501,6 @@ def _stored_hashed(document: Mapping[str, JsonValue]) -> dict[str, JsonValue]: list (:func:`_hashed_modal`), and the material manifest's hashed part (:func:`solprop.physdata.selection.hashed_document`). An entry the document lacks stays absent, so the record digests to the stamp its writer computed. - - Parameters - ---------- - document : collections.abc.Mapping - A stored record, as :meth:`RunMetadata.to_json` wrote it. - - Returns - ------- - dict - Its hashed part. """ hashed = {name: document[name] for name in _HASHED_KEYS if name in document} modal = hashed.get("modal") @@ -2917,16 +2532,9 @@ def _hashed_modal(modal: Mapping[str, object] | None) -> JsonValue: the record for a reader, and the resume door checks it as a **subset** instead (:func:`solprop.output.hdf5.check_cache`). - Parameters - ---------- - modal : collections.abc.Mapping or None - The identity, as :func:`solprop.translate.general.modal_identity` writes - it, or ``None`` for a run that is not a general modal one. - - Returns - ------- - object - The serialised identity without ``accessors``, or ``None``. + - ``modal``: the identity, as :func:`solprop.translate.general.modal_identity` + writes it, or ``None`` for a run that is not a general modal one. + - Returns: the serialised identity without ``accessors``, or ``None``. """ if modal is None: return None @@ -2936,17 +2544,8 @@ def _hashed_modal(modal: Mapping[str, object] | None) -> JsonValue: def _guide_text(guide: object) -> str: """Return the one printed line describing the guide a general run was built on. - Parameters - ---------- - guide : object - The ``guide`` entry of a general modal identity, as - :func:`solprop.translate.general.guide_identity` writes it. - - Returns - ------- - str - The provider's model, its region count and the resolution its reference was - sampled at, or ``'none'`` when the entry is missing or malformed. + - Returns: the provider's model, its region count and the resolution its reference + was sampled at, or ``'none'`` when the entry is missing or malformed. """ if not isinstance(guide, Mapping): return "none" @@ -2975,18 +2574,8 @@ def _tables_text(tables: object) -> str: that two files can be compared by, which is what a digest is for. The full document is in ``metadata.modal['tables']['model_id']``. - Parameters - ---------- - tables : object - The ``tables`` entry of a general modal identity, as - :attr:`solprop.translate.modal_tables.ModalTransformTables.identity` - writes it. - - Returns - ------- - str - A 12-hex digest of the model identity, the gauge and the policy, or - ``'none'`` when the entry is missing or malformed. + - Returns: a 12-hex digest of the model identity, the gauge and the policy, or + ``'none'`` when the entry is missing or malformed. """ if not isinstance(tables, Mapping): return "none" @@ -3007,17 +2596,10 @@ def _tables_text(tables: object) -> str: def _responses_text(responses: object) -> str: """Return the one printed line describing a general run's regional responses. - Parameters - ---------- - responses : object - The ``responses`` entry of a general modal identity, or ``None`` for a - guide whose regions are all linear. - - Returns - ------- - str - The compiled model, the responding region count and each region's declared - susceptibility, or ``'none'`` when no region responds. + - ``responses``: the ``responses`` entry of a general modal identity, or ``None`` + for a guide whose regions are all linear. + - Returns: the compiled model, the responding region count and each region's + declared susceptibility, or ``'none'`` when no region responds. """ if not isinstance(responses, Mapping): return "none" @@ -3034,26 +2616,7 @@ def _responses_text(responses: object) -> str: def _flow_text(flow: object) -> str: - """Return the printed summary of a coupled-flow entry, or that there is none. - - Parameters - ---------- - flow : object - The ``modal["flow"]`` entry, a mapping for a run carrying a coupled flow and - absent for the cumulative diagonal one every other route uses. - - Returns - ------- - str - For example ``mean impedance coherent wall, constant_schur, worst ratio - 9.012e-03, conditioning 1.000e+00``. - - Examples - -------- - >>> from solprop.output.schema import _flow_text - >>> _flow_text(None) - 'diagonal' - """ + """Return the printed summary of a coupled-flow entry, or that there is none.""" if not isinstance(flow, Mapping): return "diagonal" ratios = [ @@ -3081,15 +2644,7 @@ def _modal_rows(modal: Mapping[str, object] | None) -> list[tuple[str, object]]: so that a printed record says which guide, which table and which basis produced the file; the rows are absent altogether for a route that has no such identity. - Parameters - ---------- - modal : collections.abc.Mapping or None - The identity, or ``None`` for a route that declares none. - - Returns - ------- - list - ``(key, value)`` rows for :meth:`RunMetadata._describe`. + - ``modal``: the identity, or ``None`` for a route that declares none. """ if modal is None: return [] @@ -3148,18 +2703,7 @@ def _modal_rows(modal: Mapping[str, object] | None) -> list[tuple[str, object]]: def _material_rows(manifest: MaterialManifest) -> list[tuple[str, object]]: - """Return the printed row of a material manifest, or none for an empty one. - - Parameters - ---------- - manifest : solprop.physdata.selection.MaterialManifest - The run's manifest. - - Returns - ------- - list - One ``("materials", ...)`` row naming the optmat version and the models. - """ + """Return the printed row of a material manifest, or none for an empty one.""" if manifest.empty: return [] names = ", ".join(sorted({record.name for record in manifest.records})) or "none" @@ -3347,10 +2891,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. """ grid = self.grid kinds = [mode.kind.rsplit(".", 1)[-1] for mode in self.modes] @@ -3497,12 +3038,6 @@ def _hashed_record(self) -> dict[str, JsonValue]: once -- and serialising the configuration was most of an output's opening (SEP 25 section 6.3). The record is frozen, so the first answer stands; :meth:`hashed` hands out a shallow copy, which its callers extend. - - Returns - ------- - dict - The hashed part of the record, JSON-ready: as the file holds it for a - record read from one (:meth:`from_json`). """ if self._stored is not None: return self._stored @@ -3551,13 +3086,7 @@ def configuration_hash(self) -> str: @functools.cached_property def _configuration_digest(self) -> str: - """The resume key, computed once per record (:meth:`configuration_hash`). - - Returns - ------- - str - The SHA-256 hex digest of the canonical JSON of the hashed part. - """ + """The resume key, computed once per record (:meth:`configuration_hash`).""" text = _canonical_json(self._hashed_record) return hashlib.sha256(text.encode("utf-8")).hexdigest() @@ -3728,24 +3257,18 @@ def record_arrays(self) -> dict[str, np.ndarray]: """ found = dict(self._stored_arrays or {}) - def walk(value: object) -> None: - """Collect the arrays of a serialised structure. - - Parameters - ---------- - value : object - The structure. - """ + def _walk(value: object) -> None: + """Collect the arrays of a serialised structure.""" if isinstance(value, _ArrayReference): found[str(value[_RECORD_ARRAY])] = value.array elif isinstance(value, dict): for item in value.values(): - walk(item) + _walk(item) elif isinstance(value, list): for item in value: - walk(item) + _walk(item) - walk(self._document) + _walk(self._document) return found def to_json(self) -> str: @@ -3776,10 +3299,7 @@ def to_json(self) -> str: def _document(self) -> dict[str, JsonValue]: """The whole serialised record, built once per record (:meth:`to_json`). - Returns - ------- - dict - The hashed part and what is stored beside it, JSON-ready. + - Returns: the hashed part and what is stored beside it, JSON-ready. """ document = self.hashed() # The whole identity, including the accessor list `hashed` leaves out: what a @@ -3927,11 +3447,6 @@ def _example_metadata() -> RunMetadata: A documentation and experimentation helper, not part of the public surface: build a real record with the :class:`RunMetadata` constructor, whose own example shows how. - - Returns - ------- - RunMetadata - A mode-averaged record on a small envelope grid with one configuration entry. """ grid = EnvelopeGrid.from_limits(1.0, 835e-9, (400e-9, 2e-6), 2e-12) return RunMetadata( @@ -4546,17 +4061,11 @@ def stored_spectrum( rows = field.reshape(-1, field.shape[-1]) written = target.reshape(-1, field.shape[-1]) - def convert(selected: slice) -> None: - """Convert the selected rows into their room. - - Parameters - ---------- - selected : slice - The rows. - """ + def _convert(selected: slice) -> None: + """Convert the selected rows into their room.""" fourier.spectrum_to_optics(rows[selected], kind=kind, out=written[selected]) - parallel_rows(rows.shape[0], field.nbytes, convert) + parallel_rows(rows.shape[0], field.nbytes, _convert) return target @@ -4575,16 +4084,6 @@ def _takes_exports_by_mro(cls: type) -> bool: or declare ``_takes_exports = True`` in its own body: a ``save`` or a field hook from a subclass or a mixin that does not vouch for the exported path keeps the class on ``save``, which is given every plane with its field. - - Parameters - ---------- - cls : type - A :class:`PlaneStore` subclass. - - Returns - ------- - bool - Whether the class takes exported fields. """ mro = cls.__mro__ declared = next((klass for klass in mro if "_takes_exports" in vars(klass)), None) @@ -4718,13 +4217,7 @@ def record_recipe(self, record: RecipeRecord) -> None: self._recipe = record def _write_recipe(self, record: RecipeRecord) -> None: - """Persist the recipe; a store that lives in memory has nothing to do. - - Parameters - ---------- - record : RecipeRecord - The recipe. - """ + """Persist the recipe; a store that lives in memory has nothing to do.""" @property def recipe(self) -> RecipeRecord | None: @@ -4815,10 +4308,7 @@ def record_outcome( def _last_saved_z(self) -> float: """Return the position of the last regular saved plane. - Returns - ------- - float - The position, m. + - Returns: the position, m. """ return float(self["z"][-1]) @@ -4827,12 +4317,7 @@ def _write_outcome( ) -> None: """Persist records; the memory store needs no additional storage hook. - Parameters - ---------- - outcome : RunOutcome or None - Current execution outcome. - checkpoint : TerminalCheckpoint or None - Accepted checkpoint, or None to clear an older one. + - ``checkpoint``: accepted checkpoint, or None to clear an older one. """ @property @@ -4905,15 +4390,7 @@ def annotate(self, name: str, value: object) -> None: self._annotations[name] = cast("JsonValue", json.loads(text)) def _write_annotation(self, name: str, text: str) -> None: - """Persist one annotation; a store that lives in memory has nothing to do. - - Parameters - ---------- - name : str - The annotation's name. - text : str - Its JSON document. - """ + """Persist one annotation; a store that lives in memory has nothing to do.""" @property def metadata(self) -> RunMetadata | None: @@ -4979,21 +4456,12 @@ def step_stat_names(self) -> tuple[str, ...]: def _state_text(self) -> str: """Return whether the store is open, for printing. - Returns - ------- - str - ``"open"`` or ``"closed"``. + - Returns: ``"open"`` or ``"closed"``. """ return "closed" if self._closed else "open" def _span_text(self) -> str: - """Return the positions the saved planes span, for printing. - - Returns - ------- - str - The first and last position, or a note that they cannot be read now. - """ + """Return the positions the saved planes span, for printing.""" if not self._n_saved: return "no plane saved yet" try: @@ -5019,10 +4487,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]] = [ ("state", self._state_text()), @@ -5197,19 +4662,7 @@ def save_exported(self, plane: SavedPlane, stored: StoredField) -> None: def _check_destination(self, plane: SavedPlane, shape: tuple[int, ...]) -> None: """Refuse to give room for a plane that cannot be stored with a field. - Parameters - ---------- - plane : SavedPlane - The plane about to be stored. - shape : tuple of int - The field's shape. - - Raises - ------ - OutputError - If the handler is closed or was never opened, the plane carries a field - already, the run keeps no field at the next plane, or `shape` is not the - field shape the store holds or was told (:meth:`retain`). + - ``shape``: the field's shape. """ self._check_usable() self._require_metadata() @@ -5238,13 +4691,8 @@ def _check_destination(self, plane: SavedPlane, shape: tuple[int, ...]) -> None: def _store(self, plane: SavedPlane, exported: StoredField | None) -> None: """Store one plane, its field taken from the plane or already exported. - Parameters - ---------- - plane : SavedPlane - The plane. - exported : StoredField or None - Its field in the stored form (:meth:`save_exported`), or ``None`` to - convert the plane's own field. + - ``exported``: its field in the stored form (:meth:`save_exported`), or + ``None`` to convert the plane's own field. """ self._check_usable() metadata = self._require_metadata() @@ -5279,18 +4727,6 @@ def _take_exported( A store that keeps fields in a room of its own copies a field exported elsewhere into it (`MemoryOutput`). - - Parameters - ---------- - plane : SavedPlane - The plane, without a field. - exported : numpy.ndarray - Its field in the stored form. - - Returns - ------- - numpy.ndarray - The field to append. """ del plane return exported @@ -5469,13 +4905,7 @@ def __getitem__(self, key: str) -> np.ndarray: return self._read(group, name) def _field_planes(self) -> Int[np.ndarray, " n_kept"]: - """Return the indices of the saved planes that keep their field. - - Returns - ------- - numpy.ndarray - The indices, int64 and increasing; every saved plane of an ``"all"`` run. - """ + """Return the indices of the saved planes that keep their field.""" indices = range(self._n_saved) return np.array( [index for index in indices if self._retention.keeps(index)], dtype=np.int64 @@ -5484,15 +4914,7 @@ def _field_planes(self) -> Int[np.ndarray, " n_kept"]: def _refuse_partial_fields(self, name: str) -> None: """Refuse a whole-run field read on a run that kept fields on some planes only. - Parameters - ---------- - name : str - ``'field'`` or ``'noise_phase'``. - - Raises - ------ - OutputError - Naming the run's ``fields``. + - ``name``: ``'field'`` or ``'noise_phase'``. """ if self._retention.fields == "all": return @@ -5572,16 +4994,9 @@ def plane_field(self, index: int, convention: Convention = "optics") -> np.ndarr def _read_field(self, slot: int) -> np.ndarray: """Return one stored field by its row in the field storage. - Parameters - ---------- - slot : int - The row: the plane's index in an ``"all"`` run, its position among the - kept planes otherwise. - - Returns - ------- - numpy.ndarray - The stored field, optics convention. + - ``slot``: the row: the plane's index in an ``"all"`` run, its position among + the kept planes otherwise. + - Returns: the stored field, optics convention. """ return self._read("", "field")[slot] @@ -5620,17 +5035,7 @@ def _stored_field( A handler with room for it already converts it there (`MemoryOutput`). - Parameters - ---------- - plane : SavedPlane - The plane, which carries a field. - kind : {'field', 'envelope'} - The grid's spectral layout. - - Returns - ------- - numpy.ndarray - The converted field. + - ``kind``: the grid's spectral layout. """ assert plane.field is not None return stored_spectrum(plane.field, kind) @@ -5642,21 +5047,8 @@ def _check_layout( ) -> None: """Adopt the layout from the first plane, and refuse a later one that differs. - Parameters - ---------- - plane : SavedPlane - The plane being appended. - stored : numpy.ndarray or None - Its field, in any convention, as only its shape is read; ``None`` on a - field-free plane. - - Raises - ------ - OutputError - If the plane carries a field where the run keeps none or none where it - keeps one, naming the run's ``fields``, or the field's shape, the - statistic names or the presence of a noise phase differ from the layout - already fixed. + - ``stored``: its field, in any convention, as only its shape is read; ``None`` + on a field-free plane. """ index = self._n_saved retention = self._retention @@ -5725,42 +5117,19 @@ def _check_layout( raise OutputError(msg) def _fields_stored(self) -> int: - """Return how many of the planes saved so far kept their field. - - Returns - ------- - int - The rows the field storage holds, which is also the next field's row. - """ + """Return how many of the planes saved so far kept their field.""" if self._retention.fields == "all": return self._n_saved return sum(1 for index in self._retention.field_planes if index < self._n_saved) def _check_usable(self) -> None: - """Refuse to write to a closed handler. - - Raises - ------ - OutputError - If :meth:`close` has already run. - """ + """Refuse to write to a closed handler.""" if self._closed: msg = "this output handler is closed; open a new one to store another run" raise OutputError(msg) def _require_metadata(self) -> RunMetadata: - """Return the run's metadata, refusing to work without it. - - Returns - ------- - RunMetadata - The record given to :meth:`open`. - - Raises - ------ - OutputError - If :meth:`open` has not been called. - """ + """Return the run's metadata, refusing to work without it.""" if self._metadata is None: msg = "call open(metadata) before saving or reading a plane" raise OutputError(msg) @@ -5768,13 +5137,7 @@ def _require_metadata(self) -> RunMetadata: @abstractmethod def _write_metadata(self, metadata: RunMetadata) -> None: - """Commit the run's metadata to the backing store. - - Parameters - ---------- - metadata : RunMetadata - The record to store. - """ + """Commit the run's metadata to the backing store.""" ... @abstractmethod @@ -5785,17 +5148,13 @@ def _append( ) -> None: """Append one plane to the backing store. - Parameters - ---------- - plane : SavedPlane - The plane: its field in the internal convention, or ``None`` where the - field was exported straight into the stored form - (:meth:`~solprop.output.schema.PlaneStore.save_exported`) and `stored` - alone holds it. A subclass whose own ``_append`` reads the plane's field - does not take exports (``PlaneStore.takes_exports``). - stored : numpy.ndarray or None - That field converted to the optics convention, which is what to store; - ``None`` on a field-free plane, which stores no field. + - ``plane``: the plane: its field in the internal convention, or ``None`` where + the field was exported straight into the stored form + (:meth:`~solprop.output.schema.PlaneStore.save_exported`) and `stored` alone + holds it. A subclass whose own ``_append`` reads the plane's field does not + take exports (``PlaneStore.takes_exports``). + - ``stored``: that field converted to the optics convention, which is what to + store; ``None`` on a field-free plane, which stores no field. """ ... @@ -5803,17 +5162,8 @@ def _append( def _read(self, group: str, name: str) -> np.ndarray: """Return one stored array. - Parameters - ---------- - group : str - ``''`` for a top-level array, else ``'stats'`` or ``'step_history'``. - name : str - Name within the group. - - Returns - ------- - numpy.ndarray - The stored array. + - ``group``: ``''`` for a top-level array, else ``'stats'`` or + ``'step_history'``. """ ... @@ -5826,21 +5176,9 @@ def _release(self) -> None: def _split_key(key: str) -> tuple[str, str]: """Split an accessor key into its group and its name. - Parameters - ---------- - key : str - ``'field'``, ``'z'``, ``'noise_phase'``, ``'field_planes'``, ``'stats/'``, - ``'step_history/'`` or ``'step_stats/'``. - - Returns - ------- - tuple of str - ``(group, name)``, the group empty for a top-level array. - - Raises - ------ - OutputError - If the key has none of those forms. + - ``key``: ``'field'``, ``'z'``, ``'noise_phase'``, ``'field_planes'``, + ``'stats/'``, ``'step_history/'`` or ``'step_stats/'``. + - Returns: ``(group, name)``, the group empty for a top-level array. """ if key in ("field", "z", "noise_phase", "field_planes"): return ("", key) diff --git a/src/solprop/output/snapshots.py b/src/solprop/output/snapshots.py index 26848fe0..6fc3155a 100644 --- a/src/solprop/output/snapshots.py +++ b/src/solprop/output/snapshots.py @@ -251,13 +251,7 @@ def _finish(self) -> None: self._active = False def _due(self) -> bool: - """Coalesce interval and explicit requests, honouring failure backoff. - - Returns - ------- - bool - Whether an attempt is due at this boundary. - """ + """Coalesce interval and explicit requests, honouring failure backoff.""" now = time.monotonic() explicit = self._requested != self._attempted if explicit or ( @@ -279,24 +273,11 @@ def _service( ) -> None: """Publish one requested endpoint, reopening both writers before returning. - Parameters - ---------- - output : HDF5Output - Live optical writer at a committed accepted endpoint. - archive : TDSEArchiveWriter or None - Optional matching live archive. - next_save_index : int - Index of the next scheduled save beyond this endpoint. - require_history : bool - Whether Adams history must be present. - persist : callable - Persist the retained march and running execution record without observing. - provenance : RunMetadata or None, optional - Current execution's version and Git record. Defaults to the writer's - metadata; the driver supplies its current record when resuming. - z : float or None, optional - The accepted endpoint, m, when `persist` records it as a running - checkpoint; the writer's last recorded position by default. + - ``require_history``: whether Adams history must be present. + - ``provenance``: current execution's version and Git record. Defaults to the + writer's metadata; the driver supplies its current record when resuming. + - ``z``: the accepted endpoint, m, when `persist` records it as a running + checkpoint; the writer's last recorded position by default. """ if not self._due(): return @@ -413,20 +394,7 @@ def _suspended_writers( optical: HDF5Output, archive: TDSEArchiveWriter | None, ) -> Iterator[None]: - """Reopen every suspended writer, preserving the primary failure on cleanup. - - Parameters - ---------- - optical : HDF5Output - Optical writer to suspend. - archive : TDSEArchiveWriter or None - Optional archive writer to suspend after the optical writer. - - Yields - ------ - None - With both HDF5 handles closed and writer ownership retained. - """ + """Reopen every suspended writer, preserving the primary failure on cleanup.""" suspended: list[HDF5Output | TDSEArchiveWriter] = [] primary: BaseException | None = None try: @@ -462,19 +430,8 @@ def _copy_generation( ) -> _Publication: """Copy closed files and publish once; recover from copy/publication failures. - Parameters - ---------- - store : pathlib.Path - Owned snapshot store. - generation : str - Unique generation name. - optical, archive : pathlib.Path or None - Closed working files; only the archive may be absent. - - Returns - ------- - _Publication - Publication path, copied bytes, elapsed seconds and any failure. + - ``optical``, ``archive``: closed working files; only the archive may be absent. + - Returns: publication path, copied bytes, elapsed seconds and any failure. """ staging = store / ("." + generation) started = time.monotonic() @@ -516,18 +473,7 @@ def _copy_generation( def _digest(path: Path) -> str: - """Hash a closed file without loading it into memory. - - Parameters - ---------- - path : pathlib.Path - File to read. - - Returns - ------- - str - Hexadecimal SHA-256 digest. - """ + """Hash a closed file without loading it into memory.""" with path.open("rb") as stream: return hashlib.file_digest(stream, "sha256").hexdigest() @@ -537,19 +483,7 @@ def _copy_file( ) -> _FileRecord: """Copy and independently verify a closed source with bounded host buffers. - Parameters - ---------- - source, destination : pathlib.Path - Closed source and new destination file. - role : str - Optical or archive role in the pair. - relative : pathlib.Path - Destination relative to its generation directory. - - Returns - ------- - _FileRecord - Verified filename, size in bytes and SHA-256. + - Returns: verified filename, size in bytes and SHA-256. """ destination.parent.mkdir(parents=True, exist_ok=True) digest = hashlib.sha256() @@ -570,10 +504,7 @@ def _copy_file( def _sync_directory(path: Path) -> None: """Persist directory entries where the platform provides directory fsync. - Parameters - ---------- - path : pathlib.Path - Directory whose entries must be durable. + - ``path``: directory whose entries must be durable. """ if os.name == "nt": return @@ -585,15 +516,7 @@ def _sync_directory(path: Path) -> None: def _publish_manifest(directory: Path, manifest: Mapping[str, object]) -> None: - """Publish the manifest only after every copied file is durable and read-only. - - Parameters - ---------- - directory : pathlib.Path - Private staging generation. - manifest : mapping - Verified identities, counters and checksums to publish last. - """ + """Publish the manifest only after every copied file is durable and read-only.""" for path in directory.rglob("*"): if path.is_file(): path.chmod(0o444) @@ -610,13 +533,7 @@ def _publish_manifest(directory: Path, manifest: Mapping[str, object]) -> None: def _discard_staging(path: Path) -> None: - """Remove only this attempt's staging data, retaining failures against capacity. - - Parameters - ---------- - path : pathlib.Path - Staging directory owned by the failed attempt. - """ + """Remove only this attempt's staging data, retaining failures against capacity.""" try: # Windows cannot unlink read-only files; publication may have reached chmod. for item in path.rglob("*"): @@ -628,36 +545,14 @@ def _discard_staging(path: Path) -> None: def _object(value: object) -> dict[str, object]: - """Require a JSON object, keeping untrusted manifest values explicit. - - Parameters - ---------- - value : object - Decoded JSON or structured provenance value. - - Returns - ------- - dict - Mapping with string keys. - """ + """Require a JSON object, keeping untrusted manifest values explicit.""" if not isinstance(value, Mapping) or any(not isinstance(key, str) for key in value): raise OutputError("snapshot manifest requires an object") return dict(cast("Mapping[str, object]", value)) def _file_records(manifest: Mapping[str, object]) -> list[_FileRecord]: - """Validate the small manifest file table before touching any named path. - - Parameters - ---------- - manifest : mapping - Decoded manifest containing a file table. - - Returns - ------- - list of _FileRecord - Optical file followed by the optional archive. - """ + """Validate the small manifest file table before touching any named path.""" values = manifest.get("files") if not isinstance(values, list) or len(values) not in (1, 2): raise OutputError("snapshot must contain an optical file and optional archive") @@ -693,17 +588,7 @@ def _file_records(manifest: Mapping[str, object]) -> list[_FileRecord]: def _checkpoint(directory: Path, records: list[_FileRecord]) -> dict[str, object]: """Cross-check the copied optical endpoint and all referenced archive identities. - Parameters - ---------- - directory : pathlib.Path - Generation containing closed files. - records : list of _FileRecord - Validated file table. - - Returns - ------- - dict - Checkpoint identity and provenance checked against the actual files. + - Returns: checkpoint identity and provenance checked against the actual files. """ optical = directory / records[0].path state = resume(optical) @@ -770,21 +655,7 @@ def _archive_checkpoint( ) -> dict[str, object]: """Verify that the archive belongs to this accepted optical checkpoint. - Parameters - ---------- - directory : pathlib.Path - Generation containing the pair. - records : list of _FileRecord - Optical and archive file table. - state : ResumeState - Accepted optical endpoint. - reference : object - Archive reference from the optical execution record. - - Returns - ------- - dict - Matching archive identity, session, counters and context. + - Returns: matching archive identity, session, counters and context. """ optical = directory / records[0].path if len(records) != 2: diff --git a/src/solprop/output/tdse_archive.py b/src/solprop/output/tdse_archive.py index fff0d6bc..988e3e14 100644 --- a/src/solprop/output/tdse_archive.py +++ b/src/solprop/output/tdse_archive.py @@ -243,17 +243,8 @@ def _array( ) -> RecordField: """Return an array field. - Parameters - ---------- - name, unit, axes : str - Attribute name, SI unit and axis names. - optional : bool - Whether ``None`` is admitted. - - Returns - ------- - RecordField - The field. + - ``name``, ``unit``, ``axes``: attribute name, SI unit and axis names. + - ``optional``: whether ``None`` is admitted. """ return RecordField(name, "array", unit, axes, optional) @@ -470,18 +461,7 @@ def structured_json(value: object) -> str: def _plain(value: object) -> object: # noqa: PLR0911 - one return per value kind - """Return a JSON-compatible copy of one structured value. - - Parameters - ---------- - value : object - Any value. - - Returns - ------- - object - Plain containers and scalars. - """ + """Return a JSON-compatible copy of one structured value.""" if value is None or isinstance(value, bool | str): return value if isinstance(value, int): @@ -514,52 +494,20 @@ def _plain(value: object) -> object: # noqa: PLR0911 - one return per value kin def _now() -> str: - """Return the current UTC time in ISO 8601. - - Returns - ------- - str - Timestamp. - """ + """Return the current UTC time in ISO 8601.""" return datetime.now(UTC).isoformat() def _digest(data: bytes) -> str: """Return a short content address for a shared value. - Parameters - ---------- - data : bytes - The value's bytes, with its dtype and shape folded in by the caller. - - Returns - ------- - str - Thirty-two hexadecimal characters of its SHA-256. + - ``data``: the value's bytes, with its dtype and shape folded in by the caller. """ return hashlib.sha256(data).hexdigest()[:32] def _admit_array(value: object, where: str) -> np.ndarray: - """Return an array the archive can store with its exact dtype, or refuse it. - - Parameters - ---------- - value : object - A provider array. - where : str - Its path in the record, for the error. - - Returns - ------- - numpy.ndarray - The same array. - - Raises - ------ - TDSEArchiveError - If it is not a NumPy array of a numeric or boolean dtype. - """ + """Return an array the archive can store with its exact dtype, or refuse it.""" if not isinstance(value, np.ndarray): msg = f"cannot serialize {where}: expected a NumPy array, got {type(value)!r}" raise TDSEArchiveError(msg) @@ -570,25 +518,7 @@ def _admit_array(value: object, where: str) -> np.ndarray: def _admit_scalar(value: object, where: str) -> np.ndarray: - """Return a numeric scalar as a zero-dimensional array of its own dtype. - - Parameters - ---------- - value : object - A Python or NumPy number. - where : str - Its path in the record, for the error. - - Returns - ------- - numpy.ndarray - Zero-dimensional array. - - Raises - ------ - TDSEArchiveError - If it is not a number. - """ + """Return a numeric scalar as a zero-dimensional array of its own dtype.""" if isinstance(value, bool | int | float | np.number | np.bool_): return np.asarray(value) msg = f"cannot serialize {where}: expected a number, got {type(value)!r}" @@ -596,25 +526,7 @@ def _admit_scalar(value: object, where: str) -> np.ndarray: def _admit_labels(value: object, where: str) -> np.ndarray: - """Return integer order labels as an int64 vector. - - Parameters - ---------- - value : object - A tuple of integers. - where : str - Its path in the record, for the error. - - Returns - ------- - numpy.ndarray - The labels. - - Raises - ------ - TDSEArchiveError - If the value is not a tuple of plain integers. - """ + """Return integer order labels as an int64 vector.""" if not isinstance(value, tuple) or not all( isinstance(item, int) and not isinstance(item, bool) for item in cast("tuple[object, ...]", value) @@ -625,25 +537,7 @@ def _admit_labels(value: object, where: str) -> np.ndarray: def _outcomes_json(value: object, where: str) -> str: - """Encode the per-row outcome records. - - Parameters - ---------- - value : object - A sequence of records with ``failure`` and ``failure_step``. - where : str - Its path in the record, for the error. - - Returns - ------- - str - JSON list of ``{"failure": ..., "failure_step": ...}``. - - Raises - ------ - TDSEArchiveError - If an outcome lacks either attribute. - """ + """Encode the per-row outcome records.""" if not isinstance(value, tuple | list): msg = f"cannot serialize {where}: outcomes must be a sequence" raise TDSEArchiveError(msg) @@ -684,19 +578,10 @@ class _Counters: def _relative_reference(archive: Path, optical: Path | None) -> dict[str, object]: """Return where the optical file finds its archive. - Parameters - ---------- - archive : pathlib.Path - The archive path. - optical : pathlib.Path or None - The optical HDF5 file, or None for an in-memory output. - - Returns - ------- - dict - ``path`` relative to the optical file's directory with ``relative_to = - "optical_file"``, or the absolute path with ``relative_to = None`` when there - is no optical file or no relative path exists. + - ``optical``: the optical HDF5 file, or None for an in-memory output. + - Returns: ``path`` relative to the optical file's directory with ``relative_to = + "optical_file"``, or the absolute path with ``relative_to = None`` when there is + no optical file or no relative path exists. """ absolute = archive.resolve() if optical is not None: @@ -890,14 +775,7 @@ def _close_failed_open( ) -> None: """Release a failed factory's ownership only after confirmed closure. - Parameters - ---------- - file : h5py.File or None - Handle opened before setup failed, if any. - lease : WriterLock - Factory's acquired lease. - failure : BaseException - Primary error, with cleanup failures attached as notes. + - ``file``: handle opened before setup failed, if any. """ if file is not None: try: @@ -917,21 +795,7 @@ def _validate_existing( ) -> None: """Validate an existing archive while its caller owns the handle. - Parameters - ---------- - file : h5py.File - The file, open read-write under the persistent writer lease. - path : pathlib.Path - The existing path. - run_uuid : str - The identity it must carry. - resume : bool - Whether continuing is allowed at all. - - Raises - ------ - TDSEArchiveError - If the file is not this run's archive, or this is not a resume. + - ``run_uuid``: the identity it must carry. """ schema = file.attrs.get("schema") stored = file.attrs.get("run_uuid") @@ -1055,13 +919,7 @@ def ensure_writable(self) -> None: raise TDSEArchiveError("the TDSE archive is suspended") def _require_session(self) -> h5py.Group: - """Return the live session; suspended writers cannot issue HDF5 operations. - - Returns - ------- - h5py.Group - Live session group. - """ + """Return the live session; suspended writers cannot issue HDF5 operations.""" self.ensure_writable() assert self._session is not None return self._session @@ -1136,20 +994,7 @@ def reopen(self) -> None: raise self._fail(error, "snapshot reopening") from error def _fail(self, error: BaseException, what: str) -> TDSEArchiveError: - """Mark the writer failed and return the error to raise. - - Parameters - ---------- - error : BaseException - What went wrong. - what : str - What was being written. - - Returns - ------- - TDSEArchiveError - The failure, chained to `error`. - """ + """Mark the writer failed and return the error to raise.""" if isinstance(error, TDSEArchiveError): failure = error else: @@ -1165,10 +1010,7 @@ def _fail(self, error: BaseException, what: str) -> TDSEArchiveError: def _measure(self, started: float) -> None: """Add one write's duration and the file's growth to the counters. - Parameters - ---------- - started : float - ``time.perf_counter()`` when the write began. + - ``started``: ``time.perf_counter()`` when the write began. """ self._counters.seconds += time.perf_counter() - started # A file removed while open has no size to report; the counters stand. @@ -1176,13 +1018,7 @@ def _measure(self, started: float) -> None: self._bytes_written = self.path.stat().st_size - self._start_size def _require_file(self) -> h5py.File: - """Return the open file. - - Returns - ------- - h5py.File - The file. - """ + """Return the open file.""" self.ensure_writable() assert self._file is not None return self._file @@ -1190,15 +1026,8 @@ def _require_file(self) -> h5py.File: def _shared(self, value: np.ndarray | str) -> h5py.Dataset: """Store an immutable value once in the session and return its dataset. - Parameters - ---------- - value : numpy.ndarray or str - An array (stored with its dtype and shape) or JSON text. - - Returns - ------- - h5py.Dataset - The stored dataset, to hard-link where it is used. + - ``value``: an array (stored with its dtype and shape) or JSON text. + - Returns: the stored dataset, to hard-link where it is used. """ shared = cast("h5py.Group", self._require_session()["shared"]) if isinstance(value, str): @@ -1218,16 +1047,8 @@ def _dataset( ) -> None: """Write one array exactly, uncompressed and contiguous, with its labels. - Parameters - ---------- - group : h5py.Group - Where it goes. - name : str - Dataset name. - value : numpy.ndarray - The array; its dtype and shape are kept. - **attrs : str - Unit and axis labels. + - ``value``: the array; its dtype and shape are kept. + - ``**attrs``: unit and axis labels. """ dataset = group.create_dataset(name, data=value) for key, label in attrs.items(): @@ -1322,18 +1143,7 @@ def begin_evaluation( return identifier def _group(self, evaluation: int) -> h5py.Group: - """Return an evaluation group begun in this session. - - Parameters - ---------- - evaluation : int - Its identifier. - - Returns - ------- - h5py.Group - The group. - """ + """Return an evaluation group begun in this session.""" try: return self._open_evaluations[evaluation] except KeyError: @@ -1376,19 +1186,7 @@ def write_result( def _write_record( self, group: h5py.Group, raw: object, schema: RecordSchema, where: str ) -> None: - """Store every field of one record under its serializer. - - Parameters - ---------- - group : h5py.Group - The record's group. - raw : object - The record. - schema : RecordSchema - Its serializer. - where : str - Its path, for errors. - """ + """Store every field of one record under its serializer.""" group.attrs["serializer"] = schema.serializer absent: list[str] = [] for item in schema.fields: @@ -1410,19 +1208,7 @@ def _write_record( def _write_field( self, group: h5py.Group, item: RecordField, value: object, where: str ) -> None: - """Store one present field. - - Parameters - ---------- - group : h5py.Group - The record's group. - item : RecordField - The field's description. - value : object - Its value. - where : str - Its path, for errors. - """ + """Store one present field.""" if item.kind == "array": array = _admit_array(value, where) if item.name == "time": @@ -1633,29 +1419,12 @@ def writer_memory(*, rows: int, samples: int) -> dict[str, int]: def _disk_full(error: BaseException) -> bool: - """Return whether an error reports a full disk. - - Parameters - ---------- - error : BaseException - A write error. - - Returns - ------- - bool - True for ``ENOSPC``. - """ + """Return whether an error reports a full disk.""" return isinstance(error, OSError) and error.errno == errno.ENOSPC def _cap_metadata_cache(file: h5py.File) -> None: - """Bound the open file's HDF5 metadata cache by the admitted size. - - Parameters - ---------- - file : h5py.File - The open archive. - """ + """Bound the open file's HDF5 metadata cache by the admitted size.""" config = file.id.get_mdc_config() config.set_initial_size = True config.initial_size = ARCHIVE_METADATA_CACHE_BYTES // 4 @@ -1665,18 +1434,7 @@ def _cap_metadata_cache(file: h5py.File) -> None: def _next_evaluation_id(sessions: h5py.Group) -> int: - """Return the identifier after every evaluation already in the file. - - Parameters - ---------- - sessions : h5py.Group - The file's sessions. - - Returns - ------- - int - One more than the largest stored identifier, or 0. - """ + """Return the identifier after every evaluation already in the file.""" largest = -1 for name in sessions: try: @@ -1809,20 +1567,7 @@ class ArchivedSession: def _attr(group: h5py.HLObject, name: str) -> object: - """Return an attribute as a plain Python value. - - Parameters - ---------- - group : h5py.HLObject - The owner: a file, group or dataset. - name : str - Attribute name. - - Returns - ------- - object - The value, with NumPy scalars as Python ones and arrays as lists. - """ + """Return an attribute as a plain Python value.""" value = group.attrs[name] if isinstance(value, np.ndarray): return cast("object", value.tolist()) @@ -1832,18 +1577,7 @@ def _attr(group: h5py.HLObject, name: str) -> object: def _text(dataset: object) -> str: - """Return the text of a string dataset. - - Parameters - ---------- - dataset : object - An h5py string dataset. - - Returns - ------- - str - Its text. - """ + """Return the text of a string dataset.""" return str(cast("h5py.Dataset", dataset).asstr()[()]) @@ -1930,13 +1664,7 @@ def run_uuid(self) -> str: return str(_attr(self._file, "run_uuid")) def _sessions(self) -> list[h5py.Group]: - """Return the session groups in order. - - Returns - ------- - list of h5py.Group - The groups. - """ + """Return the session groups in order.""" sessions = cast("h5py.Group", self._file["sessions"]) return [cast("h5py.Group", sessions[name]) for name in sorted(sessions)] @@ -1952,18 +1680,7 @@ def sessions(self) -> tuple[ArchivedSession, ...]: return tuple(self._session(group) for group in self._sessions()) def _session(self, group: h5py.Group) -> ArchivedSession: - """Read one session's labels. - - Parameters - ---------- - group : h5py.Group - The session group. - - Returns - ------- - ArchivedSession - Its description. - """ + """Read one session's labels.""" trials: dict[int, str] = {} if "trials" in group: for name in cast("h5py.Group", group["trials"]): @@ -2022,18 +1739,7 @@ def truncated(self) -> bool: return any(not item.complete for item in self.evaluations()) def _evaluation_group(self, evaluation: ArchivedEvaluation) -> h5py.Group: - """Return the group of an evaluation. - - Parameters - ---------- - evaluation : ArchivedEvaluation - The evaluation. - - Returns - ------- - h5py.Group - Its group. - """ + """Return the group of an evaluation.""" session = self._sessions()[evaluation.session] evaluations = cast("h5py.Group", session["evaluations"]) return cast( @@ -2113,22 +1819,7 @@ def raw(self, evaluation: ArchivedEvaluation, chunk: int) -> dict[str, object]: def _evaluation( evaluations: h5py.Group, name: str, trials: Mapping[int, str] ) -> ArchivedEvaluation: - """Read one evaluation's labels, flagging anything incomplete. - - Parameters - ---------- - evaluations : h5py.Group - The session's evaluations. - name : str - The group's name. - trials : mapping - The session's trial decisions. - - Returns - ------- - ArchivedEvaluation - Its description. - """ + """Read one evaluation's labels, flagging anything incomplete.""" identifier = int(name.removeprefix("evaluation_")) try: group = cast("h5py.Group", evaluations[name]) @@ -2172,20 +1863,7 @@ def _evaluation( def _broken(identifier: int, evaluations: h5py.Group) -> ArchivedEvaluation: - """Describe an evaluation group that cannot be read at all. - - Parameters - ---------- - identifier : int - Its identifier. - evaluations : h5py.Group - The session's evaluations, for its session index. - - Returns - ------- - ArchivedEvaluation - Marked incomplete, with no labels. - """ + """Describe an evaluation group that cannot be read at all.""" session = evaluations.parent index = int(cast("int", _attr(session, "index"))) return ArchivedEvaluation( @@ -2210,18 +1888,8 @@ def _chunks( ) -> tuple[tuple[ArchivedChunk, ...], bool]: """Read an evaluation's chunk descriptions. - Parameters - ---------- - group : h5py.Group - The evaluation group. - labels : mapping - Its labels, with ``n_chunks``. - - Returns - ------- - tuple - The chunks present, and whether every expected chunk stored its input and - its record. + - Returns: the chunks present, and whether every expected chunk stored its input and + its record. """ expected = int(cast("int", labels.get("n_chunks", 0))) chunks: list[ArchivedChunk] = [] @@ -2253,15 +1921,7 @@ def _chunks( def _read_record(group: h5py.Group) -> dict[str, object]: """Read one stored record back into plain containers. - Parameters - ---------- - group : h5py.Group - The record's group. - - Returns - ------- - dict - Field name to value, ``None`` for an absent field. + - Returns: field name to value, ``None`` for an absent field. """ record: dict[str, object] = {"serializer": str(_attr(group, "serializer"))} for name in json.loads(str(_attr(group, "absent"))): From 096af3a8715b26784bd0cd600cafbb920efe6760 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:16:38 +0000 Subject: [PATCH 18/50] Restore a layout note, a platform note and a hash reason in solprop.output private docs Co-Authored-By: Claude Opus 5.5 --- src/solprop/output/_file_lock.py | 5 ++++- src/solprop/output/hdf5.py | 7 ++++++- src/solprop/output/schema.py | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/solprop/output/_file_lock.py b/src/solprop/output/_file_lock.py index ca482d0b..c8c1cd8e 100644 --- a/src/solprop/output/_file_lock.py +++ b/src/solprop/output/_file_lock.py @@ -22,7 +22,10 @@ def lock(descriptor: int, *, shared: bool = False, blocking: bool = True) -> None: - """Acquire the advisory lock on an open descriptor.""" + """Acquire the advisory lock on an open descriptor. + + - ``shared``: a reader lock suffices; Windows takes an exclusive one regardless. + """ if sys.platform == "win32": _windows_lock(descriptor, blocking=blocking) return diff --git a/src/solprop/output/hdf5.py b/src/solprop/output/hdf5.py index 78d2155b..fcf5eee9 100644 --- a/src/solprop/output/hdf5.py +++ b/src/solprop/output/hdf5.py @@ -265,7 +265,12 @@ def _read_march(file: h5py.File, z: float) -> MarchSnapshot | None: def _trajectory_state( stored: Complex[np.ndarray, "*shape"], ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: - """Return a stored march state without the leading axis of one its file keeps.""" + """Return a stored march state without the leading axis of one its file keeps. + + Every file writes ``(1, n_modes, n_pol, n_omega)``; ``(n_modes, n_pol, n_omega)`` + is taken as it is. A leading axis longer than one is a stacked group, which a + single trajectory's snapshot never is, and is refused rather than squeezed. + """ if stored.ndim == 4 and stored.shape[0] == 1: return stored[0] if stored.ndim == 3: diff --git a/src/solprop/output/schema.py b/src/solprop/output/schema.py index 1f07b5c8..41c71321 100644 --- a/src/solprop/output/schema.py +++ b/src/solprop/output/schema.py @@ -366,7 +366,11 @@ def _inlined(value: JsonValue) -> JsonValue: def _serialise_sequence(value: object) -> list[JsonValue]: - """Return a sequence or a set as a list, ordering set members deterministically.""" + """Return a sequence or a set as a list, ordering set members deterministically. + + A set's members are sorted by their canonical JSON text, so that the configuration + hash does not depend on set iteration order. + """ # `object` is not declared iterable; the caller has already checked that this is # a set or a sequence. members = [serialise(item) for item in value] # type: ignore[union-attr] - checked by the caller From 615b3a5aad624813a4b7e236b7d53354c4989b34 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:16:50 +0000 Subject: [PATCH 19/50] Slim private docstrings in solprop.service Mechanical: uv run python tools/docstring_diet.py src/solprop/service --write --check --report --rename-closures Co-Authored-By: Claude Opus 5.5 --- src/solprop/service/agent.py | 80 ++------- src/solprop/service/beam.py | 16 +- src/solprop/service/beams.py | 18 +-- src/solprop/service/design.py | 71 ++------ src/solprop/service/explore.py | 13 +- src/solprop/service/frames.py | 81 +--------- src/solprop/service/handlers.py | 89 ++-------- src/solprop/service/install.py | 23 +-- src/solprop/service/kernel.py | 18 +-- src/solprop/service/launch.py | 32 +--- src/solprop/service/machines.py | 130 ++------------- src/solprop/service/postprocess.py | 104 ++---------- src/solprop/service/recipes.py | 16 +- src/solprop/service/record.py | 72 +-------- src/solprop/service/reduce.py | 28 +--- src/solprop/service/results.py | 215 ++++--------------------- src/solprop/service/run.py | 127 ++------------- src/solprop/service/runs.py | 83 ++-------- src/solprop/service/scan_reductions.py | 109 ++----------- src/solprop/service/sheet.py | 161 ++++-------------- src/solprop/service/waveguide.py | 28 +--- 21 files changed, 184 insertions(+), 1330 deletions(-) diff --git a/src/solprop/service/agent.py b/src/solprop/service/agent.py index 90da6d54..769ba464 100644 --- a/src/solprop/service/agent.py +++ b/src/solprop/service/agent.py @@ -115,18 +115,7 @@ def hello() -> dict[str, object]: def _folder(body: Mapping[str, Any]) -> Path: - """Return the run folder a request names, with ``~`` expanded. - - Parameters - ---------- - body : Mapping - The request's body. - - Returns - ------- - pathlib.Path - The folder. - """ + """Return the run folder a request names, with ``~`` expanded.""" return Path(str(body["folder"])).expanduser() @@ -285,18 +274,8 @@ def _follow( ) -> None: """Read a run's record and send what is new, until it ends or is unfollowed. - Parameters - ---------- - folder : pathlib.Path - The run's folder. - key : str - The folder as the window named it, which its frames carry. - offset : int - Where to start, bytes. - period : float - How often to read the record, s. - done : threading.Event - Set to stop following. + - ``offset``: where to start, bytes. + - ``period``: how often to read the record, s. Notes ----- @@ -570,13 +549,7 @@ def kernel(self, body: Mapping[str, Any]) -> int: return client.submit(request) def _kernel_client(self) -> Any: - """Return this machine's kernel, started on first use. - - Returns - ------- - solprop.service.client.KernelClient - The kernel. - """ + """Return this machine's kernel, started on first use.""" if self._kernel is None: from solprop.service.client import KernelClient # noqa: PLC0415 - light @@ -584,13 +557,7 @@ def _kernel_client(self) -> Any: return self._kernel def _kernel_reply(self, reply: Reply) -> None: - """Send a kernel reply to the window. - - Parameters - ---------- - reply : Reply - The reply. - """ + """Send a kernel reply to the window.""" self.send({"kind": "kernel_reply", "reply": _reply_value(reply)}) def _kernel_lost(self) -> None: @@ -610,16 +577,8 @@ def close(self) -> None: def _lost(folder: Path) -> str | None: """Return why a run that recorded no ending has gone, or ``None`` while it lives. - Parameters - ---------- - folder : pathlib.Path - The run's folder. - - Returns - ------- - str or None - The reason, as the run's summary gives it; ``None`` for a run queued, - starting, running or ended with its own record. + - Returns: the reason, as the run's summary gives it; ``None`` for a run queued, + starting, running or ended with its own record. """ from solprop.service.launch import slurm_states # noqa: PLC0415 - light from solprop.service.runs import slurm_jobs, summarise # noqa: PLC0415 - light @@ -639,17 +598,7 @@ def _remove_tree(folder: Path, tries: int = 20) -> None: process leaves, and Windows refuses to delete a file that is open; so the folder is tried again for a few seconds. - Parameters - ---------- - folder : pathlib.Path - The folder. - tries : int, optional - How many times to try, a quarter of a second apart. - - Raises - ------ - OSError - If it still cannot be deleted. + - ``tries``: how many times to try, a quarter of a second apart. """ left = tries while True: @@ -667,18 +616,7 @@ def _remove_tree(folder: Path, tries: int = 20) -> None: def _reply_value(reply: Reply) -> dict[str, object]: - """Return a kernel reply as a frame carries it. - - Parameters - ---------- - reply : Reply - The reply. - - Returns - ------- - dict - Its fields. - """ + """Return a kernel reply as a frame carries it.""" return { "kind": reply.kind, "request_id": reply.request_id, diff --git a/src/solprop/service/beam.py b/src/solprop/service/beam.py index 6ffd28a5..47ac6056 100644 --- a/src/solprop/service/beam.py +++ b/src/solprop/service/beam.py @@ -75,21 +75,7 @@ def across(space: object, values: np.ndarray) -> tuple[str, np.ndarray, np.ndarr def _flux(grid: TimeGrid, space: object, medium: object, z: float) -> SpatialFlux: """Return the plane's measure: the run's grids and medium at the position. - Parameters - ---------- - grid : solprop.grid.TimeGrid - The run's time grid. - space : object - Its transverse grid. - medium : object - Its medium. - z : float - The plane's position, m. - - Returns - ------- - solprop.spatial.SpatialFlux - The measure. + - ``z``: the plane's position, m. """ return SpatialFlux.resolve(grid, space, medium, z=z) # pyright: ignore[reportArgumentType] diff --git a/src/solprop/service/beams.py b/src/solprop/service/beams.py index ca9a3ede..2e9deacd 100644 --- a/src/solprop/service/beams.py +++ b/src/solprop/service/beams.py @@ -43,21 +43,9 @@ def _even( ) -> tuple[np.ndarray, np.ndarray]: """Put a map's columns on even bins within the run's window. - Parameters - ---------- - path : str - The results file. - axis : str - The spectral axis. - x : numpy.ndarray - The columns' positions on it, ascending. - values : numpy.ndarray - The map, shape ``(n_rows, x.size)``. - - Returns - ------- - tuple of numpy.ndarray - The even positions, and the map on them as single precision. + - ``axis``: the spectral axis. + - ``values``: the map, shape ``(n_rows, x.size)``. + - Returns: the even positions, and the map on them as single precision. """ bins, image = even_rows(x, values.T, _window(path, axis, x, values.T), _ROWS) return bins, image.T.astype(np.float32) diff --git a/src/solprop/service/design.py b/src/solprop/service/design.py index 75a40fb6..48e4bb14 100644 --- a/src/solprop/service/design.py +++ b/src/solprop/service/design.py @@ -30,15 +30,8 @@ def _base(payload: Payload) -> Path | None: """Return the directory a payload's recipe reads its data files from. - Parameters - ---------- - payload : Mapping - Optionally ``base``. - - Returns - ------- - pathlib.Path or None - The directory, or ``None`` for none. + - ``payload``: optionally ``base``. + - Returns: the directory, or ``None`` for none. """ base = payload.get("base") return Path(str(base)) if base else None @@ -47,38 +40,13 @@ def _base(payload: Payload) -> Path | None: def _recipe(payload: Payload) -> Recipe: """Read the recipe a payload carries. - Parameters - ---------- - payload : Mapping - ``text``, the recipe's TOML, and optionally ``base``. - - Returns - ------- - solprop.recipe.Recipe - The recipe. + - ``payload``: ``text``, the recipe's TOML, and optionally ``base``. """ return Recipe.loads(str(payload["text"]), base=_base(payload)) def _with(recipe: Recipe, name: str, value: object, base: Path | None) -> Recipe: - """Return a copy of a recipe with one keyword set, and any pin on it removed. - - Parameters - ---------- - recipe : solprop.recipe.Recipe - The recipe. - name : str - The keyword. - value : object - Its value, as a recipe writes it. - base : pathlib.Path or None - The directory its data files resolve against. - - Returns - ------- - solprop.recipe.Recipe - The copy. - """ + """Return a copy of a recipe with one keyword set, and any pin on it removed.""" document = recipe.to_dict() pins = dict(document.get("pins", {})) if name in pins: @@ -95,20 +63,11 @@ def _with(recipe: Recipe, name: str, value: object, base: Path | None) -> Recipe def _mode_set(recipe: Recipe, z: float | None) -> dict[str, object]: """Return one recipe's mode set as plain data. - Parameters - ---------- - recipe : solprop.recipe.Recipe - A recipe on a guided route. - z : float or None - The position, m; ``None`` for the launch. - - Returns - ------- - dict - ``z`` and ``omega``, and per mode its ``label``, ``neff_minus_one``, - ``beta1``, ``beta2``, ``beta3``, ``alpha``, ``gamma`` (or ``None``), ``aeff``, - ``zdw`` (or ``None``), ``phase_mismatch`` to the launch soliton (or - ``None``) and ``rdw``, its phase-matched wavelengths, in SI. + - ``z``: the position, m; ``None`` for the launch. + - Returns: ``z`` and ``omega``, and per mode its ``label``, ``neff_minus_one``, + ``beta1``, ``beta2``, ``beta3``, ``alpha``, ``gamma`` (or ``None``), ``aeff``, + ``zdw`` (or ``None``), ``phase_mismatch`` to the launch soliton (or ``None``) and + ``rdw``, its phase-matched wavelengths, in SI. """ preview = recipe.modes(z) return { @@ -140,16 +99,8 @@ def _mode_set(recipe: Recipe, z: float | None) -> dict[str, object]: def _pump(recipe: Recipe) -> float | None: """Return the recipe's pump wavelength, if it gives one. - Parameters - ---------- - recipe : solprop.recipe.Recipe - The recipe. - - Returns - ------- - float or None - The ``wavelength`` keyword in m, or ``None`` where it is not a number (a - recipe of ``pulses``, or one that leaves it to a rule). + - Returns: the ``wavelength`` keyword in m, or ``None`` where it is not a number (a + recipe of ``pulses``, or one that leaves it to a rule). """ value = recipe.keywords().get("wavelength") return float(value) if isinstance(value, int | float) else None diff --git a/src/solprop/service/explore.py b/src/solprop/service/explore.py index 59e93fcf..1f896c9c 100644 --- a/src/solprop/service/explore.py +++ b/src/solprop/service/explore.py @@ -354,18 +354,7 @@ def design_sweep(name: str, values: Sequence[str]) -> list[str]: def _number(word: str) -> bool: - """Return whether a word is a number. - - Parameters - ---------- - word : str - The word. - - Returns - ------- - bool - Whether ``float`` reads it. - """ + """Return whether a word is a number.""" try: float(word) except ValueError: diff --git a/src/solprop/service/frames.py b/src/solprop/service/frames.py index cf4a34dc..1cdc3cfc 100644 --- a/src/solprop/service/frames.py +++ b/src/solprop/service/frames.py @@ -94,25 +94,7 @@ def flush(self) -> None: def _plain(value: object, arrays: list[np.ndarray]) -> object: - """Return a value's JSON form, collecting its arrays. - - Parameters - ---------- - value : object - Plain data with arrays. - arrays : list of numpy.ndarray - Where the arrays go, in order. - - Returns - ------- - object - The value with each array, and each float JSON cannot write, as a marker. - - Raises - ------ - solprop.service.messages.ServiceError - For a value that is not plain data. - """ + """Return a value's JSON form, collecting its arrays.""" if value is None or isinstance(value, bool | int | str): return value if isinstance(value, float): @@ -185,20 +167,7 @@ def encode(value: object) -> bytes: def _restore(value: object, arrays: list[np.ndarray]) -> object: - """Return a header's value with its markers replaced. - - Parameters - ---------- - value : object - The JSON value. - arrays : list of numpy.ndarray - The frame's arrays. - - Returns - ------- - object - The value as it was encoded. - """ + """Return a header's value with its markers replaced.""" if isinstance(value, dict): if _ARRAY in value and len(value) == 1: # pyright: ignore[reportUnknownArgumentType] return arrays[int(value[_ARRAY])] # pyright: ignore[reportUnknownArgumentType] @@ -213,20 +182,8 @@ def _restore(value: object, arrays: list[np.ndarray]) -> object: def _lengths(preamble: bytes) -> tuple[int, int]: """Return a frame's header and body lengths from its preamble. - Parameters - ---------- - preamble : bytes - The frame's first bytes, at least the preamble's size. - - Returns - ------- - tuple of int - The header's and the body's lengths, bytes. - - Raises - ------ - solprop.service.messages.ServiceError - If the bytes are not a frame's preamble. + - ``preamble``: the frame's first bytes, at least the preamble's size. + - Returns: the header's and the body's lengths, bytes. """ magic, header, body = _PREAMBLE.unpack_from(preamble) if magic != MAGIC: @@ -241,17 +198,7 @@ def _lengths(preamble: bytes) -> tuple[int, int]: def _value(header: bytes, body: bytes) -> object: """Return the value a frame's header and body hold. - Parameters - ---------- - header : bytes - The JSON header. - body : bytes - The arrays' bytes. - - Returns - ------- - object - The value. + - ``body``: the arrays' bytes. """ content: dict[str, Any] = json.loads(header.decode("utf-8")) arrays: list[np.ndarray] = [] @@ -340,22 +287,8 @@ def split_frames(data: bytes) -> tuple[list[object], int]: def _read_exactly(stream: Readable, size: int) -> bytes | None: """Return the next bytes of a stream, or ``None`` at its end. - Parameters - ---------- - stream : Readable - The stream. - size : int - How many bytes. - - Returns - ------- - bytes or None - Exactly that many; ``None`` when the stream ended before the first. - - Raises - ------ - solprop.service.messages.ServiceError - If the stream ends part of the way. + - ``size``: how many bytes. + - Returns: exactly that many; ``None`` when the stream ended before the first. """ chunks: list[bytes] = [] left = size diff --git a/src/solprop/service/handlers.py b/src/solprop/service/handlers.py index be0ea178..bf4989bf 100644 --- a/src/solprop/service/handlers.py +++ b/src/solprop/service/handlers.py @@ -51,16 +51,8 @@ def _recipe(payload: Payload) -> Recipe: """Read the recipe a payload carries. - Parameters - ---------- - payload : Mapping - ``text``, the recipe's TOML, and optionally ``base``, the directory its data - files resolve against. - - Returns - ------- - solprop.recipe.Recipe - The recipe. + - ``payload``: ``text``, the recipe's TOML, and optionally ``base``, the directory + its data files resolve against. """ base = payload.get("base") return Recipe.loads(str(payload["text"]), base=Path(str(base)) if base else None) @@ -69,16 +61,8 @@ def _recipe(payload: Payload) -> Recipe: def _plain(value: object) -> object: """Return a value as plain data, or its text where it is not. - Parameters - ---------- - value : object - A default or a resolved value. - - Returns - ------- - object - Numbers, strings, booleans and ``None`` as they are; tuples and lists - element by element; anything else as its ``repr``. + - Returns: numbers, strings, booleans and ``None`` as they are; tuples and lists + element by element; anything else as its ``repr``. """ if value is None or isinstance(value, bool | int | float | str): return value @@ -90,17 +74,9 @@ def _plain(value: object) -> object: def _default_text(entry: Parameter) -> str | None: """Return a keyword's default as a front end writes it. - Parameters - ---------- - entry : solprop.recipe.Parameter - The keyword's metadata. - - Returns - ------- - str or None - The default as a quantity string in the display unit where it is a number - with a unit, its text otherwise; ``None`` when the keyword is required or - its default is ``None``. + - Returns: the default as a quantity string in the display unit where it is a number + with a unit, its text otherwise; ``None`` when the keyword is required or its + default is ``None``. """ default = entry.default if default is REQUIRED or default is None: @@ -120,16 +96,8 @@ def _default_text(entry: Parameter) -> str | None: def _parameter(entry: Parameter) -> dict[str, object]: """Return one keyword's metadata as plain data. - Parameters - ---------- - entry : solprop.recipe.Parameter - The keyword's metadata. - - Returns - ------- - dict - Its fields, the forms as a list of dicts, and the default as written - (``default``) and as plain data (``default_value``, in the keyword's unit). + - Returns: its fields, the forms as a list of dicts, and the default as written + (``default``) and as plain data (``default_value``, in the keyword's unit). """ return { "name": entry.name, @@ -244,19 +212,7 @@ def describe(payload: Payload) -> dict[str, object]: def _type_catalog( keywords: list[dict[str, object]], ) -> dict[str, list[dict[str, object]]]: - """Return the keywords of every registered type a set of keywords can name. - - Parameters - ---------- - keywords : list of dict - Keywords as :func:`_parameter` describes them. - - Returns - ------- - dict - By type, its keywords described alike, for every type a recipe can write - that the keywords or those types' own keywords name. - """ + """Return the keywords of every registered type a set of keywords can name.""" catalog: dict[str, list[dict[str, object]]] = {} waiting = [keywords] while waiting: @@ -650,15 +606,7 @@ def overview(payload: Payload) -> dict[str, object]: def _pair(value: object) -> tuple[float, float] | None: """Return a two-element sequence as a pair of floats. - Parameters - ---------- - value : object - A pair, or ``None``. - - Returns - ------- - tuple of float or None - The pair. + - ``value``: a pair, or ``None``. """ if value is None: return None @@ -667,20 +615,7 @@ def _pair(value: object) -> tuple[float, float] | None: def _stats_at(handler: Output, index: int) -> dict[str, object]: - """Return a file's statistics row at one plane. - - Parameters - ---------- - handler : solprop.output.Output - The open file. - index : int - The plane. - - Returns - ------- - dict - Each recorded statistic at that plane, by name. - """ + """Return a file's statistics row at one plane.""" return {name: handler[f"stats/{name}"][index] for name in handler.stat_names} diff --git a/src/solprop/service/install.py b/src/solprop/service/install.py index 5cc6b011..21b4bb85 100644 --- a/src/solprop/service/install.py +++ b/src/solprop/service/install.py @@ -235,31 +235,14 @@ def run_setup( def _ssh_shell(machine: Machine, askpass: str) -> tuple[list[str], dict[str, str]]: """Return the command that runs a shell reading its script from its input. - Parameters - ---------- - machine : solprop.service.machines.Machine - The machine. - askpass : str - A program ``ssh`` asks a password or code of; empty for none. - - Returns - ------- - tuple - The command and its environment. + - ``askpass``: a program ``ssh`` asks a password or code of; empty for none. + - Returns: the command and its environment. """ return ssh_command(machine, "sh -s", askpass=askpass or None) def _feed(process: subprocess.Popen[str], script: str) -> None: - """Write a script to a process's input and close it. - - Parameters - ---------- - process : subprocess.Popen - The process: a shell reading its script. - script : str - The script. - """ + """Write a script to a process's input and close it.""" assert process.stdin is not None # a pipe, as asked with contextlib.suppress(OSError): process.stdin.write(script) diff --git a/src/solprop/service/kernel.py b/src/solprop/service/kernel.py index feba46f6..eba9a9c1 100644 --- a/src/solprop/service/kernel.py +++ b/src/solprop/service/kernel.py @@ -118,23 +118,7 @@ def answer(request: Request, handlers: Mapping[str, Handler]) -> Reply: def _waiting(connection: Connection) -> list[Request]: - """Take one request, waiting for it, and every other already waiting. - - Parameters - ---------- - connection : multiprocessing.connection.Connection - The kernel's end of the pipe. - - Returns - ------- - list of Request - The requests, in arrival order. - - Raises - ------ - EOFError - When the window has closed its end. - """ + """Take one request, waiting for it, and every other already waiting.""" requests = [connection.recv()] while connection.poll(0): requests.append(connection.recv()) diff --git a/src/solprop/service/launch.py b/src/solprop/service/launch.py index 2c2702f5..a7376c50 100644 --- a/src/solprop/service/launch.py +++ b/src/solprop/service/launch.py @@ -101,20 +101,7 @@ def prepare(spec: RunSpec) -> Path: def _job_command(python: str, folder: Path) -> list[str]: - """Return the command that runs a folder's job. - - Parameters - ---------- - python : str - The machine's Python, with solprop installed. - folder : pathlib.Path - The run's folder. - - Returns - ------- - list of str - The command. - """ + """Return the command that runs a folder's job.""" return [python, "-m", "solprop.service.job", str(folder)] @@ -363,22 +350,7 @@ def _run( ) -> subprocess.CompletedProcess[str]: """Run a scheduler command and return what it printed. - Parameters - ---------- - command : Sequence of str - The command. - timeout : float, optional - The most seconds to wait. - - Returns - ------- - subprocess.CompletedProcess - Its result. - - Raises - ------ - solprop.service.messages.ServiceError - If the command is not there or takes too long. + - ``timeout``: the most seconds to wait. """ try: return subprocess.run( diff --git a/src/solprop/service/machines.py b/src/solprop/service/machines.py index 6d143f6a..0588676f 100644 --- a/src/solprop/service/machines.py +++ b/src/solprop/service/machines.py @@ -88,17 +88,7 @@ def ssh_hosts(config: Path | None = None) -> list[str]: def _read_hosts(path: Path, found: list[str], seen: set[Path]) -> None: - """Add the hosts one configuration file names, following its includes. - - Parameters - ---------- - path : pathlib.Path - The file. - found : list of str - Where the hosts go. - seen : set of pathlib.Path - The files already read, so an include loop ends. - """ + """Add the hosts one configuration file names, following its includes.""" try: resolved = path.expanduser().resolve() if resolved in seen: @@ -308,15 +298,7 @@ def agent_command( def _drain(stream: BinaryIO, into: list[bytes]) -> None: - """Read a stream to its end, keeping what it said, and close it. - - Parameters - ---------- - stream : BinaryIO - The stream: a session's error output. - into : list of bytes - Where what it said goes. - """ + """Read a stream to its end, keeping what it said, and close it.""" with contextlib.suppress(OSError, ValueError): into.append(stream.read()) with contextlib.suppress(OSError): @@ -324,42 +306,13 @@ def _drain(stream: BinaryIO, into: list[bytes]) -> None: def _said(drainer: threading.Thread, errors: list[bytes]) -> str: - """Return what an ended session wrote to its error stream. - - Parameters - ---------- - drainer : threading.Thread - The thread reading that stream, which ends with it. - errors : list of bytes - Where it read the stream to. - - Returns - ------- - str - The text. - """ + """Return what an ended session wrote to its error stream.""" drainer.join(timeout=5) return b"".join(errors).decode("utf-8", "replace").strip() def _development(version: str) -> bool: - """Return whether a solprop version is a development build's. - - Parameters - ---------- - version : str - The version. - - Returns - ------- - bool - True for a ``.dev`` release or one with a local part. - - Examples - -------- - >>> _development("0.2.0"), _development("0.2.1.dev17+g8077f26c3.d20261107") - (False, True) - """ + """Return whether a solprop version is a development build's.""" return ".dev" in version or "+" in version @@ -425,15 +378,7 @@ def open(self) -> None: self._thread.start() def _set(self, state: str, detail: object = None) -> None: - """Change the state and say so. - - Parameters - ---------- - state : str - The new state. - detail : object, optional - What goes with it. - """ + """Change the state and say so.""" self.state = state if state == "up": self._up.set() @@ -462,13 +407,7 @@ def _run(self) -> None: self._set("closed") def _session(self) -> str: - """Run one session to its end. - - Returns - ------- - str - Why it ended. - """ + """Run one session to its end.""" try: command, environment = self._command(self.machine) except ServiceError as error: @@ -497,20 +436,7 @@ def _session(self) -> str: stream.close() def _read(self, stream: BinaryIO, said: Callable[[], str]) -> str: - """Read a session's frames until it ends. - - Parameters - ---------- - stream : BinaryIO - The session's output. - said : Callable - Returns what the session wrote to its error stream, once it has ended. - - Returns - ------- - str - Why it ended. - """ + """Read a session's frames until it ends.""" try: greeting = read_frame(stream, skip_preface=True) except (EOFError, ServiceError) as error: @@ -534,18 +460,7 @@ def _read(self, stream: BinaryIO, said: Callable[[], str]) -> str: self._deliver(cast("dict[str, Any]", frame)) def _no_helper(self, said: str) -> str: - """Return why a session gave no hello, as the user should read it. - - Parameters - ---------- - said : str - What ``ssh`` or the machine's shell wrote. - - Returns - ------- - str - The reason; a machine without solprop is refused, not retried. - """ + """Return why a session gave no hello, as the user should read it.""" missing = ( "No module named" in said or "not found" in said or "No such file" in said ) @@ -559,18 +474,7 @@ def _no_helper(self, said: str) -> str: return said def _refusal(self, greeting: object) -> str | None: - """Return why a helper's hello is refused, or ``None`` to accept it. - - Parameters - ---------- - greeting : object - The first frame. - - Returns - ------- - str or None - The reason. - """ + """Return why a helper's hello is refused, or ``None`` to accept it.""" if not isinstance(greeting, dict) or greeting.get("kind") != "hello": return "the machine's helper did not say hello" hello = cast("dict[str, Any]", greeting) @@ -596,13 +500,7 @@ def _refusal(self, greeting: object) -> str | None: return None def _deliver(self, frame: dict[str, Any]) -> None: - """Hand a frame to whoever waits for it, or to the window. - - Parameters - ---------- - frame : dict - The frame. - """ + """Hand a frame to whoever waits for it, or to the window.""" if frame.get("kind") in {"reply", "error"}: with self._lock: waiting = self._pending.pop(int(frame.get("id", -1)), None) @@ -614,13 +512,7 @@ def _deliver(self, frame: dict[str, Any]) -> None: self._on_frame(frame) def _fail_pending(self, reason: str) -> None: - """Answer every request still waiting with the session's end. - - Parameters - ---------- - reason : str - Why the session ended. - """ + """Answer every request still waiting with the session's end.""" with self._lock: pending, self._pending = self._pending, {} for event, box in pending.values(): diff --git a/src/solprop/service/postprocess.py b/src/solprop/service/postprocess.py index 8c7116d8..0eed889d 100644 --- a/src/solprop/service/postprocess.py +++ b/src/solprop/service/postprocess.py @@ -85,25 +85,7 @@ def catalogue(_payload: Payload) -> dict[str, list[str]]: def _number(values: Mapping[str, object], name: str) -> float: - """Return a stage's value as a number. - - Parameters - ---------- - values : Mapping - The stage's values. - name : str - The value. - - Returns - ------- - float - It. - - Raises - ------ - solprop.exceptions.SolpropError - If it is blank. - """ + """Return a stage's value as a number.""" value = values.get(name) if value is None: msg = f"the stage needs its {name.replace('_', ' ')}" @@ -114,22 +96,7 @@ def _number(values: Mapping[str, object], name: str) -> float: def _range(values: Mapping[str, object], name: str) -> tuple[float, float] | None: """Return a stage's search range, or none where it is left blank. - Parameters - ---------- - values : Mapping - The stage's values. - name : str - The range's name: ``name_min`` and ``name_max`` are read. - - Returns - ------- - tuple of float or None - The range, ascending; ``None`` when both ends are blank. - - Raises - ------ - solprop.exceptions.SolpropError - If one end is given and not the other. + - Returns: the range, ascending; ``None`` when both ends are blank. """ low, high = values.get(f"{name}_min"), values.get(f"{name}_max") if low is None and high is None: @@ -144,15 +111,7 @@ def _range(values: Mapping[str, object], name: str) -> tuple[float, float] | Non def _pressure(values: Mapping[str, object]) -> float | None: """Return a material's pressure, which only a gas takes. - Parameters - ---------- - values : Mapping - The stage's values. - - Returns - ------- - float or None - The pressure in bar for a gas (one bar when left blank), else ``None``. + - Returns: the pressure in bar for a gas (one bar when left blank), else ``None``. """ medium = str(values.get("medium")) if medium not in {gas.value for gas in Gas}: @@ -167,17 +126,7 @@ def _prisms(values: Mapping[str, object], wavelength: float) -> PrismPair: The pair works within its band (a wavelength range): outside it, rays of a broad spectrum miss the faces. - Parameters - ---------- - values : Mapping - The stage's values. - wavelength : float - The reference wavelength, m. - - Returns - ------- - solprop.optics.PrismPair - The pair. + - ``wavelength``: the reference wavelength, m. """ glass = str(values.get("medium")) geometry = PrismGeometry( @@ -199,17 +148,7 @@ def _prisms(values: Mapping[str, object], wavelength: float) -> PrismPair: def _gratings(values: Mapping[str, object], wavelength: float) -> GratingPair: """Return a stage's grating pair: first order, Littrow where no angle is given. - Parameters - ---------- - values : Mapping - The stage's values. - wavelength : float - The reference wavelength, m. - - Returns - ------- - solprop.optics.GratingPair - The pair. + - ``wavelength``: the reference wavelength, m. """ density = _number(values, "line_density") incidence = values.get("incidence") @@ -361,17 +300,9 @@ def search( def _pulse(t: np.ndarray, power: np.ndarray) -> dict[str, float]: """Return a pulse's width and peak, as solprop measures them. - Parameters - ---------- - t : numpy.ndarray - The time axis, s. - power : numpy.ndarray - The power, W. - - Returns - ------- - dict - ``fwhm`` (s) and ``peak`` (W). + - ``t``: the time axis, s. + - ``power``: the power, W. + - Returns: ``fwhm`` (s) and ``peak`` (W). """ return {"fwhm": fwhm(t, power), "peak": float(np.max(power))} @@ -464,23 +395,8 @@ def _powers( ) -> dict[str, object]: """Return the pulse before, after the elements and at the transform limit. - Parameters - ---------- - handler : solprop.output.Output - The open file. - index : int - The plane. - selection : int or None - One mode, or ``None`` for their sum. - band : tuple of float or None - The bandpass. - elements : Sequence of TransferFunction - The compressor. - - Returns - ------- - dict - ``t``, ``before``, ``after``, ``limit`` and ``pulses``. + - ``selection``: one mode, or ``None`` for their sum. + - Returns: ``t``, ``before``, ``after``, ``limit`` and ``pulses``. """ modes = "sum" if selection is None else selection common = { diff --git a/src/solprop/service/recipes.py b/src/solprop/service/recipes.py index 70feb783..584e63ab 100644 --- a/src/solprop/service/recipes.py +++ b/src/solprop/service/recipes.py @@ -106,23 +106,17 @@ def data_files(text: str) -> list[str]: """ found: list[str] = [] - def walk(value: object) -> None: - """Add the references inside a value, depth first. - - Parameters - ---------- - value : object - A value as the recipe writes it. - """ + def _walk(value: object) -> None: + """Add the references inside a value, depth first.""" if isinstance(value, Mapping): file = value.get("file") if "sha256" in value and isinstance(file, str) and file not in found: found.append(file) for item in value.values(): - walk(item) + _walk(item) elif isinstance(value, list): for item in value: - walk(item) + _walk(item) - walk(dict(parse_text(text))) + _walk(dict(parse_text(text))) return found diff --git a/src/solprop/service/record.py b/src/solprop/service/record.py index e39394c8..80584f0c 100644 --- a/src/solprop/service/record.py +++ b/src/solprop/service/record.py @@ -136,18 +136,7 @@ def write_json(path: Path, value: Mapping[str, object]) -> None: def _read_json(path: Path) -> dict[str, Any] | None: - """Return a small JSON file's object, or ``None`` when it is missing or torn. - - Parameters - ---------- - path : pathlib.Path - The file. - - Returns - ------- - dict or None - Its content. - """ + """Return a small JSON file's object, or ``None`` when it is missing or torn.""" import json # noqa: PLC0415 - light, and only here try: @@ -188,15 +177,7 @@ def process_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 @@ -212,15 +193,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 @@ -526,15 +499,7 @@ def watch() -> None: def _value(event: Event) -> dict[str, object]: """Return an event as a frame carries it. - Parameters - ---------- - event : Event - The event. - - Returns - ------- - dict - Its run, kind and data. + - Returns: its run, kind and data. """ return {"run_id": event.run_id, "kind": event.kind, "data": event.data} @@ -646,18 +611,7 @@ def put(self, event: Event) -> None: self._queue.put(event) def _kept(self, event: Event) -> bool: - """Return whether an event goes into the record, or only into the summary. - - Parameters - ---------- - event : Event - The event. - - Returns - ------- - bool - False for progress within a period of the last kept. - """ + """Return whether an event goes into the record, or only into the summary.""" if event.kind != "progress": return True now = time.monotonic() @@ -667,13 +621,7 @@ def _kept(self, event: Event) -> bool: return True def _summarise(self, event: Event) -> None: - """Fold one event into the summary. - - Parameters - ---------- - event : Event - The event. - """ + """Fold one event into the summary.""" if event.kind == "progress": self._status["progress"] = dict(event.data) elif event.kind == "started": @@ -690,13 +638,7 @@ def _summarise(self, event: Event) -> None: self._status["ending"] = {"kind": event.kind, "data": data} def _rewrite(self, *, force: bool = False) -> None: - """Rewrite the summary, at most every period unless forced. - - Parameters - ---------- - force : bool, optional - Rewrite now. - """ + """Rewrite the summary, at most every period unless forced.""" now = time.monotonic() if not force and now - self._written < RECORD_PROGRESS_PERIOD: return diff --git a/src/solprop/service/reduce.py b/src/solprop/service/reduce.py index 80a96f92..2ff60c59 100644 --- a/src/solprop/service/reduce.py +++ b/src/solprop/service/reduce.py @@ -261,19 +261,7 @@ def __init__(self, display: Display) -> None: def _window(self, axis: str, x: np.ndarray) -> tuple[float, float]: """Return the spectral window on an axis. - Parameters - ---------- - axis : {'wavelength', 'f'} - The axis. - x : numpy.ndarray - The first plane's positive values on it, m or Hz. - - Returns - ------- - tuple of float - The window, ascending: the display's wavelength range (on frequency, - from the plane's lowest frequency to the range's highest), or the - plane's whole band. + - ``x``: the first plane's positive values on it, m or Hz. """ limits = self.display.wavelength_range if limits is None: @@ -287,19 +275,7 @@ def _window(self, axis: str, x: np.ndarray) -> tuple[float, float]: def _bins(self, axis: str, x: np.ndarray, count: int) -> np.ndarray: """Return an axis's bin edges, made from the first plane's values. - Parameters - ---------- - axis : str - ``"wavelength"``, ``"f"`` or ``"time"``. - x : numpy.ndarray - The plane's values on it. - count : int - The bins. - - Returns - ------- - numpy.ndarray - The edges. + - ``axis``: ``"wavelength"``, ``"f"`` or ``"time"``. """ if axis not in self._edges: if axis == "time": diff --git a/src/solprop/service/results.py b/src/solprop/service/results.py index efb7525a..1099490a 100644 --- a/src/solprop/service/results.py +++ b/src/solprop/service/results.py @@ -63,33 +63,14 @@ def _modes(payload: Payload) -> ModeSelection: """Return the mode selection a payload asks for. - Parameters - ---------- - payload : Mapping - Optionally ``modes``: ``"sum"`` or a mode's index. - - Returns - ------- - int or str - The selection, ``"sum"`` by default. + - ``payload``: optionally ``modes``: ``"sum"`` or a mode's index. """ modes = payload.get("modes", "sum") return "sum" if modes == "sum" else int(modes) # pyright: ignore[reportArgumentType] def _labels(handler: Output) -> list[str]: - """Return a file's mode labels, or none for a file without modes. - - Parameters - ---------- - handler : solprop.output.Output - The open file. - - Returns - ------- - list of str - Each channel's display label. - """ + """Return a file's mode labels, or none for a file without modes.""" try: return [label.display_label for label in channel_labels(handler)] except SolpropError: @@ -98,37 +79,13 @@ def _labels(handler: Output) -> list[str]: @functools.lru_cache(maxsize=4) def _simulation(path: str, stamp: float) -> Simulation: - """Return a run rebuilt from the recipe it records, for its grids and medium. - - Parameters - ---------- - path : str - The results file. - stamp : float - Its modification time, so that a rewritten file is rebuilt. - - Returns - ------- - solprop.driver.Simulation - The run's simulation, built and not run. - """ + """Return a run rebuilt from the recipe it records, for its grids and medium.""" del stamp # the cache's key only return Recipe.from_output(path, resolved=True).build() def _space(path: str) -> object: - """Return a spatial run's transverse grid (:func:`_simulation`'s). - - Parameters - ---------- - path : str - The results file. - - Returns - ------- - object - The ``spatial_grid``. - """ + """Return a spatial run's transverse grid (:func:`_simulation`'s).""" return _simulation(path, Path(path).stat().st_mtime).spatial_grid @@ -270,21 +227,9 @@ def beam(payload: Payload) -> dict[str, object]: def _fluence(path: str, handler: Output, index: int) -> dict[str, object]: """Return a spatial plane's fluence on its transverse axes. - Parameters - ---------- - path : str - The results file. - handler : solprop.output.Output - It, open. - index : int - The plane. - - Returns - ------- - dict - ``axes`` (their names and values, m: ``r``, ``x``, or ``y`` and ``x``), - ``values`` (``node_fluence`` on them) and ``unit``; ``error`` instead - where the run's grid cannot be rebuilt or laid out. + - Returns: ``axes`` (their names and values, m: ``r``, ``x``, or ``y`` and ``x``), + ``values`` (``node_fluence`` on them) and ``unit``; ``error`` instead where the + run's grid cannot be rebuilt or laid out. """ try: space = _space(path) @@ -448,25 +393,9 @@ def statistics(payload: Payload) -> dict[str, object]: def _drawn(handler: Output, payload: Payload) -> list[Figure]: """Draw one of solprop's plots of a file. - Parameters - ---------- - handler : solprop.output.Output - The open file. - payload : Mapping - ``plot``: ``"prop_2d"``, ``"spec_1d"``, ``"time_1d"``, ``"spectrogram"`` - or ``"plot_stats"``; ``z`` (a plane's position, for the slices and the - spectrogram); and optionally ``modes``. - - Returns - ------- - list of matplotlib.figure.Figure - The figures: one for a slice or a spectrogram, one per map or statistic - for the others. - - Raises - ------ - ValueError - If the plot is not one of these. + - ``payload``: ``plot``: ``"prop_2d"``, ``"spec_1d"``, ``"time_1d"``, + ``"spectrogram"`` or ``"plot_stats"``; ``z`` (a plane's position, for the slices + and the spectrogram); and optionally ``modes``. """ kind = str(payload["plot"]) modes = _modes(payload) @@ -505,17 +434,10 @@ def _drawn(handler: Output, payload: Payload) -> list[Figure]: def _health_facts(execution: Mapping[str, object]) -> dict[str, object]: """Return what a run's record says about how it ran. - Parameters - ---------- - execution : Mapping - The run's ``execution`` record. - - Returns - ------- - dict - ``backend``, ``precision``, ``threads``, ``wall_time`` (s), ``peak_memory`` - (bytes) and ``plasma_source`` (its check and limit), each ``None`` where the - record has none. + - ``execution``: the run's ``execution`` record. + - Returns: ``backend``, ``precision``, ``threads``, ``wall_time`` (s), + ``peak_memory`` (bytes) and ``plasma_source`` (its check and limit), each ``None`` + where the record has none. """ resources = execution.get("resources") plasma = execution.get("plasma_source") @@ -538,16 +460,9 @@ def _health_facts(execution: Mapping[str, object]) -> dict[str, object]: def _boundary_of(execution: Mapping[str, object]) -> dict[str, object] | None: """Return what the boundary monitor recorded along z. - Parameters - ---------- - execution : Mapping - The run's ``execution`` record. - - Returns - ------- - dict or None - ``z``, ``removed`` (the share of the light the absorber took, by position) - and ``budget``; ``None`` where no monitor ran. + - ``execution``: the run's ``execution`` record. + - Returns: ``z``, ``removed`` (the share of the light the absorber took, by + position) and ``budget``; ``None`` where no monitor ran. """ monitor = execution.get("boundary_monitor") planes = monitor.get("planes") if isinstance(monitor, Mapping) else None @@ -668,23 +583,10 @@ def _window( ) -> tuple[float, float]: """Return the spectral window of a spectrogram's image. - Parameters - ---------- - path : str - The results file. - axis : str - The map's axis. - x : numpy.ndarray - Its rows' abscissae, ascending. - values : numpy.ndarray - The map. - - Returns - ------- - tuple of float - The run's wavelength window on the axis, where its recipe gives one (on - frequency from the map's lowest row, as a field grid reaches zero); else - the rows that hold light (above a millionth of the peak). + - ``axis``: the map's axis. + - Returns: the run's wavelength window on the axis, where its recipe gives one (on + frequency from the map's lowest row, as a field grid reaches zero); else the rows + that hold light (above a millionth of the peak). """ limits = None if capabilities.output(path).recipe: @@ -703,19 +605,8 @@ def _window( def _energy_of(handler: Output, index: int, modes: ModeSelection) -> float: """Return a plane's energy, through the metric where the modes' basis needs it. - Parameters - ---------- - handler : solprop.output.Output - The open file. - index : int - The plane. - modes : ModeSelection - The modes. - - Returns - ------- - float - The energy, J: a general modal guide's is its physical energy, of every mode. + - Returns: the energy, J: a general modal guide's is its physical energy, of every + mode. """ try: return float(energy(handler, plane=index, modes=modes)) @@ -812,23 +703,7 @@ def scan_overview(payload: Payload) -> dict[str, object]: def _scan_recipe(path: str) -> Recipe: - """Return the recipe a scan's file records, as it was written. - - Parameters - ---------- - path : str - The scan's results file. - - Returns - ------- - solprop.recipe.Recipe - The recipe, with its ``[scan]`` table. - - Raises - ------ - solprop.exceptions.SolpropError - If the file records no recipe. - """ + """Return the recipe a scan's file records, as it was written.""" record = ScanOutput(path).recipe if record is None: msg = "the scan records no recipe, so its variables are not known" @@ -865,24 +740,7 @@ def _scan_axis(name: str, values: object) -> dict[str, object]: A keyword's values are read in SI; a nested path's (``pressure[0]``) are the quantities as the recipe writes them. - Parameters - ---------- - name : str - The variable's path. - values : object - Its values. - - Returns - ------- - dict - ``name``, ``values`` and ``unit`` (``""`` where they are SI numbers). - - Examples - -------- - >>> _scan_axis("pressure[0]", np.array(["0.8 bar", "1.2 bar"]))["values"].tolist() - [0.8, 1.2] - >>> _scan_axis("energy", np.array([1e-4]))["unit"] - '' + - Returns: ``name``, ``values`` and ``unit`` (``""`` where they are SI numbers). """ held = np.asarray(values).ravel() if held.dtype.kind in "fiu": @@ -900,23 +758,12 @@ def _scan_result( ) -> dict[str, object]: """Return one recorded result over a scan's points. - Parameters - ---------- - found : solprop.processing.scans.ScanOutput - The scan. - name : str - The result: a statistic (read at the last plane, summed over its other axes) - or a reduction (a value, or a spectrum or pulse on its fixed axis). - shape : tuple of int - The scan's shape. - recipe : solprop.recipe.Recipe - The scan's recipe, whose axes a spectrum or a pulse is held on. - - Returns - ------- - dict - ``summary``, ``units`` and ``values``; a spectrum or a pulse also its ``x`` - (m or s). + - ``name``: the result: a statistic (read at the last plane, summed over its other + axes) or a reduction (a value, or a spectrum or pulse on its fixed axis). + - ``shape``: the scan's shape. + - ``recipe``: the scan's recipe, whose axes a spectrum or a pulse is held on. + - Returns: ``summary``, ``units`` and ``values``; a spectrum or a pulse also its + ``x`` (m or s). """ grid = found.as_grid(name) short = name.split("/", 1)[1] diff --git a/src/solprop/service/run.py b/src/solprop/service/run.py index 0515aeeb..854c174a 100644 --- a/src/solprop/service/run.py +++ b/src/solprop/service/run.py @@ -108,15 +108,7 @@ def measure(self, simulation: object) -> None: self.space = (grid, space, simulation.medium) # pyright: ignore[reportAttributeAccessIssue] def _put(self, kind: str, **data: object) -> None: - """Hand one event to the record. - - Parameters - ---------- - kind : str - Its kind. - **data : object - Its content. - """ + """Hand one event to the record.""" self.emit(Event(self.run_id, kind, data)) def started(self, z_start: float, z_end: float, n_planes: int) -> None: @@ -354,18 +346,8 @@ def emit(self, record: logging.LogRecord) -> None: def _ending(output: Path, stopped: bool) -> tuple[str, dict[str, object]]: """Return the terminal event of a run that returned. - Parameters - ---------- - output : pathlib.Path - Its results file. - stopped : bool - Whether its stop flag was set. - - Returns - ------- - tuple - The kind (``"stopped"``, ``"finished"`` or ``"failed"``) and its data: the - outcome's ``status``, ``z`` and ``message``, and the file's ``path``. + - Returns: the kind (``"stopped"``, ``"finished"`` or ``"failed"``) and its data: + the outcome's ``status``, ``z`` and ``message``, and the file's ``path``. """ with open_file(output) as handler: outcome = handler.outcome @@ -386,20 +368,7 @@ def _ending(output: Path, stopped: bool) -> tuple[str, dict[str, object]]: def _plain(value: object) -> float | str: """Return a point's value as an event carries it. - Parameters - ---------- - value : object - A number, or a quantity as a recipe writes it (``"0.8 bar"``). - - Returns - ------- - float or str - The number, or the text. - - Examples - -------- - >>> _plain(np.float64(0.5)), _plain("0.8 bar") - (0.5, '0.8 bar') + - ``value``: a number, or a quantity as a recipe writes it (``"0.8 bar"``). """ try: return float(value) # pyright: ignore[reportArgumentType] @@ -410,18 +379,7 @@ def _plain(value: object) -> float | str: def _written(recipe: Recipe, variables: Mapping[str, object]) -> list[list[str]]: """Return each scan variable's values as the recipe's ``[scan]`` table writes them. - Parameters - ---------- - recipe : solprop.recipe.Recipe - The recipe. - variables : Mapping - The scan's variables and their values, in its axis order. - - Returns - ------- - list of list of str - A list given as quantities keeps its units; a range's values are numbers in - its keyword's unit. + - ``variables``: the scan's variables and their values, in its axis order. """ table: Mapping[str, object] = recipe.to_dict().get("scan", {}) written: list[list[str]] = [] @@ -447,25 +405,8 @@ def _scan( the CPUs and memory the run was given: this computer's, a machine's or a Slurm allocation's (design 13.4). - Parameters - ---------- - recipe : solprop.recipe.Recipe - The recipe, with its ``[scan]`` table. - folder : pathlib.Path - The scan's folder. - spec : solprop.service.worker.RunSpec - The run: its identity for its events, and what it records at each point - beyond its statistics. - emit : Callable - Where its events go: its record. - stop : solprop.backends.session.StopToken - Its stop token: the points not yet started are not run. - - Returns - ------- - tuple - The terminal event: ``"stopped"`` or ``"finished"``, with the scan file's - ``path`` and the number of points ``failed``. + - Returns: the terminal event: ``"stopped"`` or ``"finished"``, with the scan file's + ``path`` and the number of points ``failed``. """ scan = recipe.scan() output = folder / SCAN_OUTPUT @@ -491,18 +432,8 @@ def _scan( def _display(spec: RunSpec, output: Path) -> Display: """Return the display bins of a run: the spec's, in the recipe's window. - Parameters - ---------- - spec : solprop.service.worker.RunSpec - The run. - output : pathlib.Path - Its results file, whose recipe a continued run reads. - - Returns - ------- - solprop.service.reduce.Display - The display; the spec's own where the recipe gives no wavelength window or - cannot be read. + - Returns: the display; the spec's own where the recipe gives no wavelength window + or cannot be read. """ try: if spec.recipe is not None: @@ -573,22 +504,7 @@ def execute(spec: RunSpec) -> None: def _attach_log( folder: Path, run_id: str, emit: Callable[[Event], None] ) -> Callable[[], None]: - """Send solprop's log to the run's ``run.log``, and its warnings as events. - - Parameters - ---------- - folder : pathlib.Path - The run's folder. - run_id : str - The run. - emit : Callable - Where its events go: its record. - - Returns - ------- - Callable - What takes the handlers off again and closes the log. - """ + """Send solprop's log to the run's ``run.log``, and its warnings as events.""" log_file = logging.FileHandler(folder / LOG, encoding="utf-8") log_file.setFormatter( logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s") @@ -629,28 +545,7 @@ def _march( ) -> tuple[str, dict[str, object]]: """Run, continue or scan, and return the terminal event. - Parameters - ---------- - spec : solprop.service.worker.RunSpec - The run. - folder : pathlib.Path - Its folder. - emit : Callable - Where a scan's events go: its record. - observer : LiveObserver - The run's reporter. - stop : solprop.backends.session.StopToken - Its stop token. - - Returns - ------- - tuple - The terminal event's kind and data. - - Raises - ------ - ValueError - For a new run without its recipe. + - Returns: the terminal event's kind and data. """ output = folder / OUTPUT if spec.resume is None: diff --git a/src/solprop/service/runs.py b/src/solprop/service/runs.py index c46fb728..144e862b 100644 --- a/src/solprop/service/runs.py +++ b/src/solprop/service/runs.py @@ -62,18 +62,7 @@ def _read(path: Path) -> dict[str, Any] | None: - """Return a small JSON file, or ``None`` when missing or torn. - - Parameters - ---------- - path : pathlib.Path - The file. - - Returns - ------- - dict or None - Its object. - """ + """Return a small JSON file, or ``None`` when missing or torn.""" import json # noqa: PLC0415 - light, only here try: @@ -84,18 +73,7 @@ def _read(path: Path) -> dict[str, Any] | None: def _title(folder: Path) -> str: - """Return a run's title, from its recipe, or its folder's name. - - Parameters - ---------- - folder : pathlib.Path - The run's folder. - - Returns - ------- - str - The title. - """ + """Return a run's title, from its recipe, or its folder's name.""" try: text = (folder / RECIPE).read_text(encoding="utf-8") except OSError: @@ -210,25 +188,10 @@ def _living_state( # noqa: PLR0917 - called positionally ) -> tuple[str, str]: """Return the state of a run that has recorded no ending. - Parameters - ---------- - folder : pathlib.Path - The run's folder. - job : solprop.service.record.Job or None - Its ``job.json``. - submitted : Mapping - Its ``submitted.json``. - status : Mapping - Its ``status.json``. - slurm : Mapping - The scheduler's states, by job. - now : float - The time, s since the epoch. - - Returns - ------- - tuple of str - The state and its detail. + - ``submitted``: its ``submitted.json``. + - ``status``: its ``status.json``. + - ``now``: the time, s since the epoch. + - Returns: the state and its detail. """ del folder slurm_job = submitted.get("slurm_job") or (job.slurm_job if job else None) @@ -259,22 +222,11 @@ def _slurm_state( ) -> tuple[str, str]: """Return the state of a Slurm run that has recorded no ending. - Parameters - ---------- - known : SlurmState or None - The scheduler's state of its job; ``None`` where neither the queue nor the - accounts know it (yet, or any longer). - job : solprop.service.record.Job or None - Its ``job.json``, written once the job started. - status : Mapping - Its ``status.json``. - now : float - The time, s since the epoch. - - Returns - ------- - tuple of str - The state and its detail. + - ``known``: the scheduler's state of its job; ``None`` where neither the queue nor + the accounts know it (yet, or any longer). + - ``status``: its ``status.json``. + - ``now``: the time, s since the epoch. + - Returns: the state and its detail. """ if known is None: return ("running", "") if _fresh(status, now) else ("lost", "") @@ -288,17 +240,8 @@ def _slurm_state( def _fresh(status: Mapping[str, Any], now: float) -> bool: """Return whether a run has rewritten its summary lately. - Parameters - ---------- - status : Mapping - Its ``status.json``. - now : float - The time, s since the epoch. - - Returns - ------- - bool - True within :data:`STALE` of its last rewrite. + - ``status``: its ``status.json``. + - ``now``: the time, s since the epoch. """ updated = status.get("updated") return isinstance(updated, int | float) and now - float(updated) < STALE diff --git a/src/solprop/service/scan_reductions.py b/src/solprop/service/scan_reductions.py index eb1cbc8e..936a3609 100644 --- a/src/solprop/service/scan_reductions.py +++ b/src/solprop/service/scan_reductions.py @@ -86,15 +86,8 @@ def reduction_axis(recipe: Recipe, kind: str) -> np.ndarray: def _where(spec: Spec) -> dict[str, float | int]: """Return where a reduction reads: a distance, or nothing for the last plane. - Parameters - ---------- - spec : Mapping - The reduction's spec, with ``z`` (m) or none. - - Returns - ------- - dict - The readers' ``z``, or the last ``plane``. + - ``spec``: the reduction's spec, with ``z`` (m) or none. + - Returns: the readers' ``z``, or the last ``plane``. """ z = spec.get("z") return {"plane": -1} if z is None else {"z": float(z)} # pyright: ignore[reportArgumentType] @@ -103,15 +96,7 @@ def _where(spec: Spec) -> dict[str, float | int]: def _band(spec: Spec) -> tuple[float, float]: """Return a reduction's band. - Parameters - ---------- - spec : Mapping - The reduction's spec, with ``band``, two wavelengths in m. - - Returns - ------- - tuple of float - The band, ascending. + - ``spec``: the reduction's spec, with ``band``, two wavelengths in m. """ low, high = spec["band"] # pyright: ignore[reportGeneralTypeIssues] return min(float(low), float(high)), max(float(low), float(high)) @@ -120,19 +105,7 @@ def _band(spec: Spec) -> tuple[float, float]: def _energy_in_band(output: Output, spec: Spec, where: Mapping[str, Any]) -> np.ndarray: """Return the energy within the band. - Parameters - ---------- - output : solprop.output.Output - The point's run. - spec : Mapping - The reduction's spec, with its ``band``. - where : Mapping - The plane read (:func:`_where`). - - Returns - ------- - numpy.ndarray - The energy within the band. + - ``spec``: the reduction's spec, with its ``band``. """ low, high = _band(spec) x, density = get_spectral_density(output, "wavelength", modes="sum", **where) @@ -148,19 +121,7 @@ def _peak_power_in_band( ) -> np.ndarray: """Return the peak power of the pulse filtered to the band. - Parameters - ---------- - output : solprop.output.Output - The point's run. - spec : Mapping - The reduction's spec, with its ``band``. - where : Mapping - The plane read. - - Returns - ------- - numpy.ndarray - The peak power of the pulse filtered to the band. + - ``spec``: the reduction's spec, with its ``band``. """ _t, power = get_power( output, @@ -175,19 +136,7 @@ def _peak_power_in_band( def _spectrum(output: Output, on: np.ndarray, where: Mapping[str, Any]) -> np.ndarray: """Return the spectral energy density on a fixed axis. - Parameters - ---------- - output : solprop.output.Output - The point's run. - on : numpy.ndarray - The axis, m. - where : Mapping - The plane read. - - Returns - ------- - numpy.ndarray - The spectral energy density on the axis. + - ``on``: the axis, m. """ x, density = get_spectral_density(output, "wavelength", modes="sum", **where) order = np.argsort(x) @@ -197,19 +146,7 @@ def _spectrum(output: Output, on: np.ndarray, where: Mapping[str, Any]) -> np.nd def _pulse(output: Output, on: np.ndarray, where: Mapping[str, Any]) -> np.ndarray: """Return the power on a fixed time axis. - Parameters - ---------- - output : solprop.output.Output - The point's run. - on : numpy.ndarray - The axis, s. - where : Mapping - The plane read. - - Returns - ------- - numpy.ndarray - The power on the axis. + - ``on``: the axis, s. """ t, power = get_power(output, modes="sum", oversampling=TIME_OVERSAMPLING, **where) return np.interp(on, np.asarray(t), np.asarray(power), 0.0, 0.0) @@ -231,18 +168,7 @@ class _Reader: axis: np.ndarray | None = None def __call__(self, output: Output) -> np.ndarray: - """Return the reduction's value for a point's run. - - Parameters - ---------- - output : solprop.output.Output - The point's run. - - Returns - ------- - numpy.ndarray - The value. - """ + """Return the reduction's value for a point's run.""" kind = str(self.spec["kind"]) where = _where(self.spec) on: np.ndarray = np.zeros(0) if self.axis is None else self.axis @@ -258,23 +184,8 @@ def __call__(self, output: Output) -> np.ndarray: def _reader(spec: Spec, axis: np.ndarray | None) -> Callable[[Output], np.ndarray]: """Return what a reduction computes from a point's run. - Parameters - ---------- - spec : Mapping - ``kind``, and ``band`` or ``z`` as it takes them. - axis : numpy.ndarray or None - A spectrum's or a pulse's fixed axis. - - Returns - ------- - callable - From the point's output to its value; picklable, so a ``Pool``'s workers can - take it. - - Raises - ------ - KeyError - For a kind that is not one of :data:`KINDS`. + - ``spec``: ``kind``, and ``band`` or ``z`` as it takes them. + - ``axis``: a spectrum's or a pulse's fixed axis. """ if str(spec["kind"]) not in KINDS: raise KeyError(spec["kind"]) diff --git a/src/solprop/service/sheet.py b/src/solprop/service/sheet.py index bec53bb8..639c1a6d 100644 --- a/src/solprop/service/sheet.py +++ b/src/solprop/service/sheet.py @@ -91,15 +91,7 @@ def _recipe(payload: Payload) -> Recipe: """Read the recipe a payload carries. - Parameters - ---------- - payload : Mapping - ``text`` and optionally ``base``. - - Returns - ------- - solprop.recipe.Recipe - The recipe. + - ``payload``: ``text`` and optionally ``base``. """ base_dir = payload.get("base") return Recipe.loads( @@ -169,17 +161,9 @@ def capillary_numbers(recipe: Recipe, energy: float) -> SolitonParams: def _as_numbers(params: SolitonParams) -> dict[str, float]: """Return solprop's numbers as plain floats. - Parameters - ---------- - params : solprop.tools.SolitonParams - The numbers. - - Returns - ------- - dict - ``soliton_order``, ``beta2`` (s^2/m), ``gamma`` (1/(W m)), ``n2`` (m^2/W), - ``peak_intensity`` (W/m^2), ``peak_power`` (W), ``pressure`` (bar), ``zdw`` - (m), ``fission_length`` (m) and ``critical_power`` (W). + - Returns: ``soliton_order``, ``beta2`` (s^2/m), ``gamma`` (1/(W m)), ``n2`` + (m^2/W), ``peak_intensity`` (W/m^2), ``peak_power`` (W), ``pressure`` (bar), + ``zdw`` (m), ``fission_length`` (m) and ``critical_power`` (W). """ return { "soliton_order": float(params.soliton_order), @@ -210,30 +194,15 @@ def _shortest_wave( ) -> float | None: """Return the shortest wavelength phase-matched to the launch soliton. - Parameters - ---------- - mode : solprop.modes.protocol.Mode - The launch mode. - wavelength : float - The pump's wavelength, m. - beta2 : float - The dispersion at the pump, s^2/m. - peak_power : float - The peak power, W. - gamma : float - The nonlinear coefficient, 1/(W m). - fwhm : float - The duration, s. - shape : str - The pulse's shape. - limits : tuple of float or None, optional - The range to search, m; ``None`` takes the tool's. - - Returns - ------- - float or None - The wavelength, m; ``None`` when none is, as a pump in normal dispersion - launches no soliton. + - ``wavelength``: the pump's wavelength, m. + - ``beta2``: the dispersion at the pump, s^2/m. + - ``peak_power``: the peak power, W. + - ``gamma``: the nonlinear coefficient, 1/(W m). + - ``fwhm``: the duration, s. + - ``shape``: the pulse's shape. + - ``limits``: the range to search, m; ``None`` takes the tool's. + - Returns: the wavelength, m; ``None`` when none is, as a pump in normal dispersion + launches no soliton. """ if beta2 >= 0: return None @@ -266,17 +235,8 @@ def _wave_window(recipe: Recipe, pump: float) -> tuple[float, float] | None: The tool's own range reaches where this fibre's fits go non-finite (#538), so the search keeps to the run's window; a capillary keeps the tool's. - Parameters - ---------- - recipe : solprop.recipe.Recipe - The recipe. - pump : float - The pump's wavelength, m. - - Returns - ------- - tuple of float or None - The range, m; ``None`` for the tool's own. + - ``pump``: the pump's wavelength, m. + - Returns: the range, m; ``None`` for the tool's own. """ if recipe.keywords().get("lattice") is None: return None @@ -286,19 +246,9 @@ def _wave_window(recipe: Recipe, pump: float) -> tuple[float, float] | None: def _zero_near(wavelength: np.ndarray, beta2: np.ndarray, pump: float) -> float | None: """Return the zero of a dispersion curve nearest a pump, read from the curve. - Parameters - ---------- - wavelength : numpy.ndarray - The curve's wavelengths, m, ascending. - beta2 : numpy.ndarray - Its group-velocity dispersion there. - pump : float - The pump's wavelength, m. - - Returns - ------- - float or None - The wavelength, m; ``None`` where the curve does not cross zero. + - ``wavelength``: the curve's wavelengths, m, ascending. + - ``pump``: the pump's wavelength, m. + - Returns: the wavelength, m; ``None`` where the curve does not cross zero. """ crossings = np.flatnonzero(np.sign(beta2[:-1]) * np.sign(beta2[1:]) < 0) zeros = [ @@ -319,19 +269,8 @@ def _lattice_zero(recipe: Recipe, mode: object, pump: float) -> float: wall has one at 1919 nm as well as 485 nm for an 800 nm pump, with the window ending at 1600 nm. The sheet's number is the one on its curve. - Parameters - ---------- - recipe : solprop.recipe.Recipe - The recipe. - mode : solprop.modes.protocol.Mode - The fibre's mode. - pump : float - The pump's wavelength, m. - - Returns - ------- - float - The wavelength, m; NaN where the window holds no zero. + - ``pump``: the pump's wavelength, m. + - Returns: the wavelength, m; NaN where the window holds no zero. """ low, high = _curve_range(recipe, pump) wavelength = np.geomspace(low, high, CURVE_POINTS) @@ -343,17 +282,8 @@ def _lattice_zero(recipe: Recipe, mode: object, pump: float) -> float: def _curve_range(recipe: Recipe, pump: float) -> tuple[float, float]: """Return the wavelengths a dispersion curve covers: the run's own window. - Parameters - ---------- - recipe : solprop.recipe.Recipe - The recipe. - pump : float - The pump's wavelength, m. - - Returns - ------- - tuple of float - The two ends, m. + - ``pump``: the pump's wavelength, m. + - Returns: the two ends, m. """ keywords = recipe.keywords() limits = keywords.get("wavelength_limits") @@ -371,19 +301,8 @@ def _reach_of(values: np.ndarray, wavelength: np.ndarray, pump: float) -> float: The curve runs off near a resonance, and a fibre's resonance dwarfs the pump's neighbourhood, so the axis follows what the eye reads there. - Parameters - ---------- - values : numpy.ndarray - The curve. - wavelength : numpy.ndarray - Its wavelengths, m, ascending. - pump : float - The pump's wavelength, m. - - Returns - ------- - float - The reach, in the curve's unit. + - ``wavelength``: its wavelengths, m, ascending. + - ``pump``: the pump's wavelength, m. """ reach = float(np.nanmax(np.abs(values[wavelength > 0.25 * wavelength[-1]]))) omega, centre = 1.0 / wavelength, 1.0 / pump @@ -395,16 +314,8 @@ def _reach_of(values: np.ndarray, wavelength: np.ndarray, pump: float) -> float: def _shapes_of(mode: object) -> list[dict[str, object]]: """Return a guide's cross-section as plain shapes, to scale. - Parameters - ---------- - mode : solprop.modes.protocol.Mode - The guide's mode. - - Returns - ------- - list of dict - In drawing order, each ``kind`` and its numbers in metres, with ``fill`` and - ``edge``, solprop's colours of its region. + - Returns: in drawing order, each ``kind`` and its numbers in metres, with ``fill`` + and ``edge``, solprop's colours of its region. """ plain: list[dict[str, object]] = [] for shape in outline(mode): @@ -835,13 +746,7 @@ def gases(payload: Payload) -> list[str]: # noqa: ARG001 - none def _run_budget() -> ThreadBudget: - """Return the thread budget a run gets, not the kernel's own, which is smaller. - - Returns - ------- - solprop.backends.ThreadBudget - The budget under the ``SOLPROP_THREADS`` the window started with. - """ + """Return the thread budget a run gets, not the kernel's own, which is smaller.""" held = os.environ.get("SOLPROP_THREADS") started = os.environ.get(RUN_THREADS) if started is None: @@ -865,16 +770,8 @@ def _graded(recipe: Recipe) -> dict[str, float]: Read from the recipe's text alone: building the fibre solves its modes, which is seconds, not the moment an edit takes. - Parameters - ---------- - recipe : solprop.recipe.Recipe - The recipe, its provider a ``GradedIndexProvider`` inside ``provider``. - - Returns - ------- - dict - ``radius`` (m), ``contrast``, ``exponent``, and where they are plain indices, - ``axis`` and ``exterior``. + - Returns: ``radius`` (m), ``contrast``, ``exponent``, and where they are plain + indices, ``axis`` and ``exterior``. """ inner = "provider.provider" profile: dict[str, float] = { diff --git a/src/solprop/service/waveguide.py b/src/solprop/service/waveguide.py index c7ca2ada..71c0eef8 100644 --- a/src/solprop/service/waveguide.py +++ b/src/solprop/service/waveguide.py @@ -95,19 +95,8 @@ def _curve_of( ) -> tuple[np.ndarray, np.ndarray]: """Return one recipe's curve of a quantity against wavelength. - Parameters - ---------- - recipe : solprop.recipe.Recipe - The recipe. - key : str - The quantity's key; of :data:`~solprop.service.explore.QUANTITIES`. - numbers : Mapping or None - The pulse's numbers, for a phase mismatch; ``None`` for none. - - Returns - ------- - tuple of numpy.ndarray - The wavelengths, m, and the values in the quantity's unit. + - ``numbers``: the pulse's numbers, for a phase mismatch; ``None`` for none. + - Returns: the wavelengths, m, and the values in the quantity's unit. """ mode = recipe.build().modes[0] pump = float(recipe.keywords()["wavelength"]) # pyright: ignore[reportArgumentType] @@ -145,16 +134,9 @@ def _curve_of( def _varied(payload: Payload) -> list[tuple[str | None, Payload]]: """Return a design for each value of its sweep, or the design alone. - Parameters - ---------- - payload : Mapping - The design, with optionally ``sweep``: ``name`` (a parameter path) and - ``values``. - - Returns - ------- - list of tuple - The value (``None`` without a sweep) and its design. + - ``payload``: the design, with optionally ``sweep``: ``name`` (a parameter path) + and ``values``. + - Returns: the value (``None`` without a sweep) and its design. """ sweep = payload.get("sweep") if not isinstance(sweep, Mapping) or not sweep.get("values"): From eb340ba1ad9b68efc1ed3f56f3d664845d0aca3c Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:17:13 +0000 Subject: [PATCH 20/50] Restore flags, conventions and reasons in solprop.coupling private docs Co-Authored-By: Claude Opus 5.5 --- src/solprop/coupling/coupler.py | 1 + src/solprop/coupling/elements.py | 30 ++++++++++++++++++++++++++---- src/solprop/coupling/plane.py | 7 ++++++- src/solprop/coupling/remap.py | 1 + src/solprop/coupling/transport.py | 3 +++ 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/solprop/coupling/coupler.py b/src/solprop/coupling/coupler.py index e013676f..0da4007a 100644 --- a/src/solprop/coupling/coupler.py +++ b/src/solprop/coupling/coupler.py @@ -906,6 +906,7 @@ 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. + - ``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: diff --git a/src/solprop/coupling/elements.py b/src/solprop/coupling/elements.py index f99a0dea..9b2b8ea5 100644 --- a/src/solprop/coupling/elements.py +++ b/src/solprop/coupling/elements.py @@ -474,7 +474,10 @@ 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.""" + """Return whether two media have the same index on the band. + + 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)) ) @@ -530,7 +533,10 @@ 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.""" + """Resolve the axial-flux measure of the state's plane. + + The plane's medium is homogeneous, so the position is immaterial. + """ return SpatialFlux.resolve(state.grid, state.space, state.medium, z=0.0) @@ -597,6 +603,8 @@ def _angular_content( in place of the axial admittance; the obliquity of a propagating column is the admittance over the index, :math:`\cos\theta`. + - ``cone_cosine``: :math:`\cos\theta_*`; columns with a smaller obliquity are the + tail. - Returns: the split content, J. """ grid = state.grid @@ -634,6 +642,7 @@ def _reference_norm( ) -> float: """Return the squared reference-measure norm of a spectrum, up to a constant. + - ``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) @@ -664,7 +673,10 @@ def _node_content( index: Float[np.ndarray, " n_omega"], samples: Complex[np.ndarray, "n_modes n_pol n_omega"], ) -> Float[np.ndarray, " n_modes"]: - """Return each node's reference-measure content, up to a constant.""" + """Return each node's reference-measure content, up to a constant. + + - ``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) radial = isinstance(space, RadialGrid) @@ -852,6 +864,7 @@ def _apply_screen( - ``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. @@ -1470,6 +1483,11 @@ def _uniform_result( column keeps its obliquity, the loss per bin is the energy change per bin and the ledger closes to rounding. Across a medium change the loss is the declared reflection and the rest is obliquity. + + - ``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)) @@ -1904,7 +1922,11 @@ def _phase_only(transfer: TransferFunction) -> bool | None: def _reconstruction(transfer: TransferFunction) -> bool: - """Return whether a known optics element is a reconstruction operation.""" + """Return whether a known optics element is a reconstruction operation. + + 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) if isinstance(transfer, Material): diff --git a/src/solprop/coupling/plane.py b/src/solprop/coupling/plane.py index 8cd5fe90..01def4b4 100644 --- a/src/solprop/coupling/plane.py +++ b/src/solprop/coupling/plane.py @@ -128,7 +128,11 @@ def _frozen( def _canonical(record: object) -> str: - """Return the canonical JSON text an identity is a digest of.""" + """Return the canonical JSON text an identity is a digest of. + + A material's open interval bound is infinite, and JSON's ``Infinity`` spells it + deterministically. + """ return json.dumps(record, sort_keys=True, separators=(",", ":")) @@ -1596,6 +1600,7 @@ def _transverse( - ``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) diff --git a/src/solprop/coupling/remap.py b/src/solprop/coupling/remap.py index 0b89735b..804af6d3 100644 --- a/src/solprop/coupling/remap.py +++ b/src/solprop/coupling/remap.py @@ -383,6 +383,7 @@ def _record_window( """Return which source samples the target window keeps, and check the record. - ``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. """ diff --git a/src/solprop/coupling/transport.py b/src/solprop/coupling/transport.py index af2536ba..2e167bba 100644 --- a/src/solprop/coupling/transport.py +++ b/src/solprop/coupling/transport.py @@ -208,6 +208,9 @@ def _sequence( ) -> tuple[SpatialState, TransportReport]: """Apply an ordered sequence to an internal state and keep the ledger. + - ``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. - Returns: the last state and the sequential report. """ current = state From 8fb0ae5fe4ba967304a702d5c4bda1fb1d64ef83 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:17:13 +0000 Subject: [PATCH 21/50] Restore why the scan reader is picklable in solprop.service private docs Co-Authored-By: Claude Opus 5.5 --- src/solprop/service/scan_reductions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/solprop/service/scan_reductions.py b/src/solprop/service/scan_reductions.py index 936a3609..674ce692 100644 --- a/src/solprop/service/scan_reductions.py +++ b/src/solprop/service/scan_reductions.py @@ -184,6 +184,8 @@ def __call__(self, output: Output) -> np.ndarray: def _reader(spec: Spec, axis: np.ndarray | None) -> Callable[[Output], np.ndarray]: """Return what a reduction computes from a point's run. + A picklable object rather than a closure, so that a ``Pool``'s workers can take it. + - ``spec``: ``kind``, and ``band`` or ``z`` as it takes them. - ``axis``: a spectrum's or a pulse's fixed axis. """ From 873f6020db92b7de0263272de23c8d842100cab8 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:17:20 +0000 Subject: [PATCH 22/50] Slim private docstrings in solprop.nonlinear Mechanical: python tools/docstring_diet.py src/solprop/nonlinear --write --check --report --rename-closures Co-Authored-By: Claude Opus 5.5 --- src/solprop/nonlinear/envelope_plasma.py | 144 ++--------- src/solprop/nonlinear/gnlse.py | 207 +++------------ src/solprop/nonlinear/ionisation.py | 30 +-- src/solprop/nonlinear/mixtures.py | 131 ++-------- src/solprop/nonlinear/normalisation.py | 21 +- src/solprop/nonlinear/optmat_raman.py | 314 ++--------------------- src/solprop/nonlinear/optmat_rates.py | 184 ++----------- src/solprop/nonlinear/raman.py | 230 +++-------------- src/solprop/nonlinear/responses.py | 233 +++-------------- src/solprop/nonlinear/tdse.py | 53 +--- 10 files changed, 199 insertions(+), 1348 deletions(-) diff --git a/src/solprop/nonlinear/envelope_plasma.py b/src/solprop/nonlinear/envelope_plasma.py index 99c2df0f..a9589133 100644 --- a/src/solprop/nonlinear/envelope_plasma.py +++ b/src/solprop/nonlinear/envelope_plasma.py @@ -104,17 +104,8 @@ def __post_init__(self) -> None: def _checked_rate(rate: EvaluableRate, amplitude: float) -> float: """Reject a malformed rate before it enters an angular integral. - Parameters - ---------- - rate : EvaluableRate - Instantaneous magnitude model. - amplitude : float - Non-negative field magnitude in V/m. - - Returns - ------- - float - Finite non-negative rate in s^-1. + - ``amplitude``: Non-negative field magnitude in V/m. + - Returns: finite non-negative rate in s^-1. """ value = np.asarray(rate(amplitude)) if value.shape != () or not np.isrealobj(value): @@ -132,19 +123,8 @@ def _cycle_points( ) -> Float[np.ndarray, " n_points"]: """Seed Cartesian extrema and declared cutoff crossings for adaptive integration. - Parameters - ---------- - envelope : numpy.ndarray - Dimensionless envelope scaled to a largest real component of one. - scale : float - Physical amplitude scale in V/m. - rate : EvaluableRate - Magnitude model, optionally declaring threshold or table bounds. - - Returns - ------- - numpy.ndarray - Sorted full-cycle phase breakpoints in rad. + - ``scale``: physical amplitude scale in V/m. + - Returns: sorted full-cycle phase breakpoints in rad. """ real, imaginary = np.real(envelope), np.imag(envelope) phase = 0.5 * math.atan2( @@ -165,17 +145,8 @@ def _cycle_points( def crossing(theta: float, boundary: float) -> float: """Locate a rate boundary directly in the Cartesian real field. - Parameters - ---------- - theta : float - Optical phase in rad. - boundary : float - Rate threshold divided by the physical field scale, dimensionless. - - Returns - ------- - float - Signed difference between the scaled magnitude and the boundary. + - ``theta``: optical phase in rad. + - Returns: signed difference between the scaled magnitude and the boundary. """ return float(np.linalg.norm((envelope * np.exp(-1j * theta)).real)) - boundary @@ -204,18 +175,9 @@ def _cycle_sources( ) -> tuple[float, Complex[np.ndarray, " n_pol"]]: """Integrate the Cartesian real field and its first current harmonic. - Parameters - ---------- - envelope : numpy.ndarray - One physical V/m envelope sample, shape ``(n_pol,)``. - rate : EvaluableRate - Instantaneous isotropic magnitude model. - - Returns - ------- - tuple - Averaged rate in s^-1 and current divided by potential, density and - survival, in m/(V s). + - ``envelope``: one physical V/m envelope sample, shape ``(n_pol,)``. + - Returns: averaged rate in s^-1 and current divided by potential, density and + survival, in m/(V s). """ scale = float( max(np.max(np.abs(np.real(envelope))), np.max(np.abs(np.imag(envelope)))) @@ -247,18 +209,7 @@ def _cycle_sources( ) def integrand(theta: float) -> Complex[np.ndarray, " n_integrand"]: - """Form scaled Cartesian rate and first harmonic at one cycle phase. - - Parameters - ---------- - theta : float - Optical phase in radians. - - Returns - ------- - numpy.ndarray - Rate followed by the complex current components, dimensionless. - """ + """Form scaled Cartesian rate and first harmonic at one cycle phase.""" carrier = np.exp(1j * theta) field = (normalised * carrier.conjugate()).real square = float(field @ field) @@ -327,24 +278,11 @@ def _linear_cycle_rules( ``maximum_field``) is crossed once, at ``arccos(bound / a)``. The quarter is split there; below the lower bound the rate is exactly zero and that piece is skipped. - Parameters - ---------- - rate : EvaluableRate - Instantaneous magnitude rate in s^-1 of a field in V/m, vectorised. - amplitudes : numpy.ndarray - Envelope amplitudes in V/m, finite and positive, shape ``(n_amplitude,)``. - - Returns - ------- - tuple of numpy.ndarray - The averages in s^-1 by the :data:`_CYCLE_ORDER` rule and by the - :data:`_CYCLE_CHECK_ORDER` rule, from one evaluation of the rate. - - Raises - ------ - GridError - If the rate does not return a finite, non-negative real array of the - field's shape. + - ``rate``: instantaneous magnitude rate in s^-1 of a field in V/m, vectorised. + - ``amplitudes``: envelope amplitudes in V/m, finite and positive, shape + ``(n_amplitude,)``. + - Returns: the averages in s^-1 by the :data:`_CYCLE_ORDER` rule and by the + :data:`_CYCLE_CHECK_ORDER` rule, from one evaluation of the rate. """ lower = getattr(rate, "field_min", getattr(rate, "threshold", None)) bounds = sorted( @@ -511,12 +449,8 @@ def linear_cycle_average(rate: EvaluableRate, amplitude: float) -> float: def _validate_field(field_t: Complex[np.ndarray, "n_pol n_to"], dt: float) -> None: """Check physical envelope shape, finite data and time spacing. - Parameters - ---------- - field_t : numpy.ndarray - Physical V/m envelope, shape ``(n_pol, n_to)``. - dt : float - Fine time spacing in s. + - ``field_t``: physical V/m envelope, shape ``(n_pol, n_to)``. + - ``dt``: fine time spacing in s. """ if ( field_t.ndim != 2 @@ -538,19 +472,10 @@ def _population( ) -> tuple[Float[np.ndarray, " n_to"], Float[np.ndarray, " n_to"]]: """Keep survival directly, including after the fraction rounds to one. - Parameters - ---------- - rate : numpy.ndarray - Cycle-averaged rates in s^-1, shape ``(n_to,)``. - dt : float - Positive time spacing in s. - preionisation : float - Initial fraction in ``[0, 1]``. - - Returns - ------- - tuple - Fresh ionised fraction and surviving neutral fraction arrays. + - ``rate``: Cycle-averaged rates in s^-1, shape ``(n_to,)``. + - ``dt``: positive time spacing in s. + - ``preionisation``: initial fraction in ``[0, 1]``. + - Returns: fresh ionised fraction and surviving neutral fraction arrays. """ # Positive overflow represents saturated exposure, not a non-finite source. # Multiply the half-rates before adding to avoid premature overflow of W+W. @@ -697,16 +622,7 @@ def traits(self) -> ResponseTraits: def _plane_preionisation(self) -> float: """Return the initial fraction at the one plane the reference methods see. - Returns - ------- - float - The fraction in ``[0, 1]``. - - Raises - ------ - MaterialError - If the fraction is a path along z, which only the translation layer - resolves. + - Returns: the fraction in ``[0, 1]``. """ if isinstance(self.preionisation, ZProfile): raise MaterialError( @@ -721,18 +637,10 @@ def _moments( ) -> tuple[Float[np.ndarray, " n_to"], Complex[np.ndarray, "n_pol n_to"]]: """Recompute every cycle from this call's field, without population state. - Parameters - ---------- - field_t : numpy.ndarray - Physical V/m envelope, shape ``(n_pol, n_to)``. - dt : float - Positive time spacing in s. - - Returns - ------- - tuple - Averaged rates and first current harmonic per potential, density and - surviving neutral fraction. + - ``field_t``: physical V/m envelope, shape ``(n_pol, n_to)``. + - ``dt``: positive time spacing in s. + - Returns: averaged rates and first current harmonic per potential, density and + surviving neutral fraction. """ _validate_field(field_t, dt) rates = np.empty(field_t.shape[1], dtype=np.float64) diff --git a/src/solprop/nonlinear/gnlse.py b/src/solprop/nonlinear/gnlse.py index 4f309fcc..d199ac93 100644 --- a/src/solprop/nonlinear/gnlse.py +++ b/src/solprop/nonlinear/gnlse.py @@ -236,15 +236,8 @@ def __call__( ) -> Float[np.ndarray, " n_omega"]: """Return the same attenuation at every frequency. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies, rad/s, shape ``(n_omega,)``. - - Returns - ------- - numpy.ndarray - ``alpha`` in 1/m, shape ``(n_omega,)``. + - ``omega``: angular frequencies, rad/s, shape ``(n_omega,)``. + - Returns: ``alpha`` in 1/m, shape ``(n_omega,)``. """ return np.full(omega.shape, self.value) @@ -840,15 +833,8 @@ def __call__( ) -> Float[np.ndarray, " n_omega"]: """Evaluate the propagation constant or its analytic first derivative. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s, inside the table. - - Returns - ------- - numpy.ndarray - Values in rad/m, or s/m for derivative one, shape ``(n_omega,)``. + - ``omega``: angular frequencies in rad/s, inside the table. + - Returns: values in rad/m, or s/m for derivative one, shape ``(n_omega,)``. """ return ( self.table.beta(omega) @@ -868,15 +854,8 @@ def __call__( ) -> Float[np.ndarray, " n_omega"]: """Evaluate the table's power attenuation. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s, inside the table. - - Returns - ------- - numpy.ndarray - Power attenuation in 1/m, shape ``(n_omega,)``. + - ``omega``: angular frequencies in rad/s, inside the table. + - Returns: power attenuation in 1/m, shape ``(n_omega,)``. """ return self.table.alpha(omega) @@ -889,16 +868,6 @@ def _source_paths(mode: Mode) -> tuple[ZProfile, ...]: mode declares under :data:`solprop.modes.protocol.GEOMETRY_PATH_NAMES`, which is the same tuple the transform's kink walk reads. Only the ones that move are kept. - - Parameters - ---------- - mode : solprop.modes.protocol.Mode - The source mode. - - Returns - ------- - tuple of solprop.profiles.ZProfile - The varying declared paths, in declaration order. """ declared: tuple[ZProfile | None, ...] if isinstance(mode, ParameterisedMode): @@ -911,22 +880,8 @@ def _source_paths(mode: Mode) -> tuple[ZProfile, ...]: def _source_geometry(mode: Mode) -> tuple[tuple[float, float], Float[np.ndarray, " n"]]: """Return the span and the breakpoints a moving source mode declares. - Parameters - ---------- - mode : solprop.modes.protocol.Mode - The source mode, which moves along z. - - Returns - ------- - tuple - ``((start, end), breakpoints)`` in m: the tightest interval every varying - path covers, and the union of their kinks inside it. - - Raises - ------ - ModeError - If the mode moves but declares no varying path to say where, or the paths - it declares have no interval in common. + - Returns: ``((start, end), breakpoints)`` in m: the tightest interval every varying + path covers, and the union of their kinks inside it. """ paths = _source_paths(mode) if not paths: @@ -959,23 +914,9 @@ def _area_path( ) -> ZProfile: """Return the effective-area path declared beside a dispersion path. - Parameters - ---------- - aeff : array_like or solprop.profiles.ZProfile - One positive area in m^2 per declared position, or a path already. - positions : numpy.ndarray - The dispersion path's declared positions in m, shape ``(n_positions,)``. - - Returns - ------- - solprop.profiles.ZProfile - A :class:`solprop.profiles.PiecewiseLinear` through the declared areas, or - the supplied path unchanged. - - Raises - ------ - ModeError - If a sequence of areas does not carry one value per declared position. + - ``aeff``: one positive area in m^2 per declared position, or a path already. + - ``positions``: the dispersion path's declared positions in m, shape + ``(n_positions,)``. """ if isinstance(aeff, ZProfile): return aeff @@ -1001,15 +942,7 @@ def _dispersion_moves(mode: Mode) -> bool: frequency-only dispersion into a path, or the cumulative operator would leave the parameter expansion for the z table and the compiled routes with it. - Parameters - ---------- - mode : solprop.modes.protocol.Mode - The source mode. - - Returns - ------- - bool - ``True`` when the source's propagation constant depends on z. + - Returns: ``True`` when the source's propagation constant depends on z. """ declared = getattr(mode, "dispersion_is_constant", None) return not mode.z_constant if declared is None else not bool(declared) @@ -1018,13 +951,8 @@ def _dispersion_moves(mode: Mode) -> bool: def _warn_surrogate_area(label: str, omega: object) -> None: """Warn that a provider channel's area is its median-knot surrogate. - Parameters - ---------- - label : str - The display label of the channel handed to a scalar model with no area and - no wavelength. - omega : object - The frequency its table records the surrogate at, rad/s, if it records one. + - ``omega``: the frequency its table records the surrogate at, rad/s, if it records + one. """ where = ( f"{omega:.6e} rad/s ({2.0 * math.pi * C / omega * 1e9:.2f} nm)" @@ -1050,15 +978,8 @@ class _ModeArea: def __call__(self, z: Float[np.ndarray, " n_z"]) -> Float[np.ndarray, " n_z"]: """Sample the source mode's effective area. - Parameters - ---------- - z : numpy.ndarray - Positions in m, shape ``(n_z,)``. - - Returns - ------- - numpy.ndarray - ``A_eff(z)`` in m^2, shape ``(n_z,)``. + - ``z``: positions in m, shape ``(n_z,)``. + - Returns: ``A_eff(z)`` in m^2, shape ``(n_z,)``. """ return np.array([self.mode.aeff(float(one)) for one in z], dtype=float) @@ -1080,10 +1001,7 @@ class _ModeBeta: def breakpoints(self) -> Float[np.ndarray, " n_breakpoints"]: """Return the source mode's declared breakpoints. - Returns - ------- - numpy.ndarray - The positions in m, empty for a source whose dispersion is fixed. + - Returns: the positions in m, empty for a source whose dispersion is fixed. """ if not _dispersion_moves(self.mode): return np.empty(0, dtype=float) @@ -1093,11 +1011,8 @@ def breakpoints(self) -> Float[np.ndarray, " n_breakpoints"]: def z_span(self) -> tuple[float, float]: """Return the interval the source mode's declared paths cover. - Returns - ------- - tuple of float - ``(start, end)`` in m, ``(0, inf)`` for a source whose dispersion is - fixed. + - Returns: ``(start, end)`` in m, ``(0, inf)`` for a source whose dispersion is + fixed. """ if not _dispersion_moves(self.mode): return 0.0, math.inf @@ -1107,10 +1022,7 @@ def z_span(self) -> tuple[float, float]: def is_constant(self) -> bool: """Return whether the source mode's dispersion moves. - Returns - ------- - bool - ``True`` unless the source's propagation constant depends on z. + - Returns: ``True`` unless the source's propagation constant depends on z. """ return not _dispersion_moves(self.mode) @@ -1119,17 +1031,9 @@ def __call__( ) -> Float[np.ndarray, " n_omega"]: """Evaluate the propagation constant or its analytic first derivative. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s, inside the source mode's support. - z : float, optional - Position along the fibre, m. - - Returns - ------- - numpy.ndarray - Values in rad/m, or s/m for derivative one, shape ``(n_omega,)``. + - ``omega``: angular frequencies in rad/s, inside the source mode's support. + - ``z``: position along the fibre, m. + - Returns: values in rad/m, or s/m for derivative one, shape ``(n_omega,)``. """ return ( beta(self.mode, omega, z) @@ -1151,10 +1055,7 @@ class _ModeAlpha: def breakpoints(self) -> Float[np.ndarray, " n_breakpoints"]: """Return the source mode's declared breakpoints. - Returns - ------- - numpy.ndarray - The positions in m, empty for a source that does not move. + - Returns: the positions in m, empty for a source that does not move. """ return _ModeBeta(self.mode).breakpoints @@ -1162,10 +1063,7 @@ def breakpoints(self) -> Float[np.ndarray, " n_breakpoints"]: def z_span(self) -> tuple[float, float]: """Return the interval the source mode's declared paths cover. - Returns - ------- - tuple of float - ``(start, end)`` in m. + - Returns: ``(start, end)`` in m. """ return _ModeBeta(self.mode).z_span @@ -1173,10 +1071,7 @@ def z_span(self) -> tuple[float, float]: def is_constant(self) -> bool: """Return whether the source mode's loss moves. - Returns - ------- - bool - ``True`` unless the source's attenuation depends on z. + - Returns: ``True`` unless the source's attenuation depends on z. """ return not _dispersion_moves(self.mode) @@ -1185,17 +1080,9 @@ def __call__( ) -> Float[np.ndarray, " n_omega"]: """Evaluate the supplied mode's power attenuation. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s, inside the source mode's support. - z : float, optional - Position along the fibre, m. - - Returns - ------- - numpy.ndarray - Power attenuation in 1/m, shape ``(n_omega,)``. + - ``omega``: angular frequencies in rad/s, inside the source mode's support. + - ``z``: position along the fibre, m. + - Returns: power attenuation in 1/m, shape ``(n_omega,)``. """ return alpha(self.mode, omega, z) @@ -1474,11 +1361,6 @@ def _check_paths_agree(self) -> None: declared in the loss alone would otherwise be a corner nothing lands on. It is also what makes a fibre whose **loss** alone moves constructible: its value path is constant and declares no position at all. - - Raises - ------ - ModeError - Naming which of the three disagreements it is. """ value, derivative = self.dispersion_path, self.derivative_path if value.is_constant != derivative.is_constant: @@ -2352,20 +2234,7 @@ def field( def _positive(omega: Float[np.ndarray, " n_omega"]) -> Float[np.ndarray, " n_omega"]: """Return the frequencies as a float array, refusing non-positive ones. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies, rad/s, shape ``(n_omega,)``. - - Returns - ------- - numpy.ndarray - The same values as float64, shape ``(n_omega,)``. - - Raises - ------ - ModeError - If any value is not strictly positive. + - ``omega``: angular frequencies, rad/s, shape ``(n_omega,)``. """ values = np.asarray(omega, dtype=float) if values.size and values.min() <= 0.0: @@ -2677,19 +2546,9 @@ def _held_back_fraction( a given fraction is always held back; an omitted one is the silica bundle's ``f_R`` with Raman on (SEP 3, D4) and zero with Raman off. - Parameters - ---------- - raman : bool - Whether a Raman response takes the fraction. - raman_fraction : float or None - The fraction given, or ``None``. - bundle : SilicaBundle or None - The selection's silica bundle, resolved when Raman is on. - - Returns - ------- - tuple - The fraction and ``"user"``, ``"bundle"`` or ``"raman-off"``. + - ``raman_fraction``: the fraction given, or ``None``. + - ``bundle``: the selection's silica bundle, resolved when Raman is on. + - Returns: the fraction and ``"user"``, ``"bundle"`` or ``"raman-off"``. """ if raman_fraction is not None: return float(raman_fraction), "user" diff --git a/src/solprop/nonlinear/ionisation.py b/src/solprop/nonlinear/ionisation.py index 326c1924..43790b99 100644 --- a/src/solprop/nonlinear/ionisation.py +++ b/src/solprop/nonlinear/ionisation.py @@ -748,20 +748,7 @@ def ionisation_fraction( def _validate_positive(name: str, value: float) -> None: - """Raise unless ``value`` is a positive, finite number. - - Parameters - ---------- - name : str - The name to quote in the message. - value : float - The number to check. - - Raises - ------ - MaterialError - Naming the offending argument. - """ + """Raise unless ``value`` is a positive, finite number.""" if not math.isfinite(value) or value <= 0.0: msg = f"{name} must be positive and finite, got {value!r}" raise MaterialError(msg) @@ -770,20 +757,7 @@ def _validate_positive(name: str, value: float) -> None: def _finite_field(field_amplitude: ArrayLike | float) -> Float[np.ndarray, " *shape"]: """Return ``|field_amplitude|`` as a float array, rejecting anything not finite. - Parameters - ---------- - field_amplitude : array_like or float - Field amplitude in V/m. - - Returns - ------- - numpy.ndarray - The absolute value as float64, with the shape of the argument. - - Raises - ------ - MaterialError - If any element is a NaN or an infinity. + - ``field_amplitude``: field amplitude in V/m. """ # np.abs on a zero-dimensional array returns a numpy scalar rather than an array, # so the result is wrapped again: every public rate returns an ndarray, including diff --git a/src/solprop/nonlinear/mixtures.py b/src/solprop/nonlinear/mixtures.py index ed5c6703..e8177b5e 100644 --- a/src/solprop/nonlinear/mixtures.py +++ b/src/solprop/nonlinear/mixtures.py @@ -62,19 +62,8 @@ def _default_rate( (:func:`solprop.nonlinear.optmat_rates.selected_rate`): no solprop formula, and a custom species is refused, since its rate must be given explicitly (SEP 3, D1). - Parameters - ---------- - component : Component - Labelled species, density, initial ionisation and Raman overrides. - wavelength : float or None - Reference wavelength in m, the rate's carrier unless the selection names one. - selection : MaterialSelection - The run's material selection. - - Returns - ------- - IonisationRate - The default rate. + - ``wavelength``: reference wavelength in m, the rate's carrier unless the selection + names one. """ return selected_rate( component.species, selection, wavelength, label=component.label @@ -82,17 +71,7 @@ def _default_rate( def _check_labels(labels: Sequence[str], mixture: Mixture, option: str) -> None: - """Reject misspelled selections before building any response. - - Parameters - ---------- - labels : Sequence of str - Component labels explicitly selected by the caller. - mixture : Mixture - Material declaration supplying the valid labels. - option : str - Response option name used in a refusal diagnostic. - """ + """Reject misspelled selections before building any response.""" for label in labels: if label not in mixture.labels: raise MaterialError(f"unknown {option} component label {label!r}") @@ -109,27 +88,8 @@ def _component_raman( ) -> RamanResponse | None: """Select available terms by default and honour explicit per-term requests. - Parameters - ---------- - component : Component - Labelled species, density, initial ionisation and Raman overrides. - selected : bool or MolecularOptions - Whether and which molecular terms to include. - mixture : Mixture - The mixture the component belongs to, whose temperature (or, for a - temperature path, its reference, :func:`raman_temperature`) sets the - rotational populations. - thg : bool - Whether the field-grid drive retains third harmonics. - explicit : bool - Whether absence of the selected response is an error. - length : float or None, optional - The run's length in m, over which a temperature path is averaged. - - Returns - ------- - RamanResponse or None - Selected component terms, or None for an allowed omission. + - ``length``: the run's length in m, over which a temperature path is averaged. + - Returns: selected component terms, or None for an allowed omission. """ if selected is False: return None @@ -328,18 +288,8 @@ def raman_temperature( def _weight(mixture: Mixture, label: str | Sequence[str]) -> tuple[ZProfile, str]: """Return the density a reference temperature is weighted by, and its name. - Parameters - ---------- - mixture : Mixture - The fill. - label : str or sequence of str - One component, or the components of a composition. - - Returns - ------- - tuple - The component's density, or the components' summed density, in m^-3, and - the label or the labels joined by ``+``. + - Returns: the component's density, or the components' summed density, in m^-3, and + the label or the labels joined by ``+``. """ if isinstance(label, str): return mixture[label].density, label @@ -356,43 +306,19 @@ def _mean_temperature( ) -> float: """Return the mean of a temperature path weighted by one density over a span. - Parameters - ---------- - density : ZProfile - The species' number density in m^-3. - temperature : ZProfile - The temperature path in K. - start, end : float - The finite span in m. - kinks : numpy.ndarray - Positions in m inside the span where the density or the temperature kinks. - - Returns - ------- - float - ``int rho T dz / int rho dz`` in K. - - Raises - ------ - MaterialError - If the density vanishes over the span. + - ``density``: the species' number density in m^-3. + - ``temperature``: the temperature path in K. + - ``start``, ``end``: the finite span in m. + - ``kinks``: positions in m inside the span where the density or the temperature + kinks. + - Returns: ``int rho T dz / int rho dz`` in K. """ edges = np.unique(np.concatenate([[start, end], kinks])) def weighted(z: float, power: int) -> float: """Return ``rho(z) T(z)^power`` at one position. - Parameters - ---------- - z : float - Position in m. - power : int - 0 for the density, 1 for the density times the temperature. - - Returns - ------- - float - The integrand. + - ``z``: position in m. """ position = np.array([z]) value = float(density(position)[0]) @@ -423,20 +349,7 @@ def weighted(z: float, power: int) -> float: def _raman_choice( raman: bool | Sequence[str] | Mapping[str, bool | MolecularOptions], label: str ) -> bool | MolecularOptions: - """Return what a Raman selection asks of one label. - - Parameters - ---------- - raman : bool, sequence of str or Mapping - :func:`responses_for`'s selection. - label : str - The component's label. - - Returns - ------- - bool or MolecularOptions - Whether, and which terms. - """ + """Return what a Raman selection asks of one label.""" if isinstance(raman, Mapping): return raman.get(label, False) return raman if isinstance(raman, bool) else label in raman @@ -447,19 +360,7 @@ def _composition_at( ) -> RamanResponse | None: """Place a composition's one response at the label its leaf carries. - Parameters - ---------- - component : Component - The component being visited. - mixture : Mixture - The fill. - composition : RamanResponse or None - The composition's response, if any. - - Returns - ------- - RamanResponse or None - The response at its leaf's label, and nothing at the others. + - ``composition``: the composition's response, if any. """ if composition is None: return None diff --git a/src/solprop/nonlinear/normalisation.py b/src/solprop/nonlinear/normalisation.py index cebe4fc1..d5b30a9c 100644 --- a/src/solprop/nonlinear/normalisation.py +++ b/src/solprop/nonlinear/normalisation.py @@ -384,25 +384,8 @@ def modal_kerr_coefficient( def _metric_entry(metric: ArrayLike | float | None, channel: int | None) -> float: r"""Return ``[W^-1]_mm`` for one channel of a declared flux metric. - Parameters - ---------- - metric : array_like, float or None - The square Hermitian metric, one channel's diagonal entry ``W_mm``, or - ``None`` for the identity. - channel : int or None - The row a square metric is read at. - - Returns - ------- - float - The diagonal entry of ``W^-1`` this channel's coefficient is scaled by: - one for the identity, ``1/W_mm`` for a scalar. - - Raises - ------ - ModeError - If the metric is not a positive scalar or a Hermitian positive-definite - matrix, or `channel` is missing, out of range or given without a matrix. + - ``metric``: the square Hermitian metric, one channel's diagonal entry ``W_mm``, or + ``None`` for the identity. """ if metric is None: if channel not in (None, 0): diff --git a/src/solprop/nonlinear/optmat_raman.py b/src/solprop/nonlinear/optmat_raman.py index c2edf72b..dc17e217 100644 --- a/src/solprop/nonlinear/optmat_raman.py +++ b/src/solprop/nonlinear/optmat_raman.py @@ -547,15 +547,7 @@ class _Request: def _gas_kind(name: str) -> Literal["atom", "molecule"] | None: """Return whether optmat knows a species as an atom or a molecule. - Parameters - ---------- - name : str - A species name. - - Returns - ------- - {'atom', 'molecule'} or None - The kind, or ``None`` for a name optmat's gas registry does not curate. + - Returns: the kind, or ``None`` for a name optmat's gas registry does not curate. """ from optmat.exceptions import OptmatError # noqa: PLC0415 - optmat is lazy from optmat.gas.gases import as_gas, info # noqa: PLC0415 - as above @@ -611,21 +603,7 @@ def raman_status(gas: str) -> str: def _build(request: _Request) -> tuple[_optmat.Untyped, list[warnings.WarningMessage]]: """Return optmat's response for a request, with the warnings its build raised. - Parameters - ---------- - request : _Request - The composition, state and selection. - - Returns - ------- - tuple - The response optmat built, and the warnings it raised while being built. - - Raises - ------ - MaterialError - If optmat refuses the composition: naming each Raman-active constituent and - partner whose broadening it has not curated, or with optmat's reason. + - Returns: the response optmat built, and the warnings it raised while being built. """ from optmat.exceptions import OptmatError # noqa: PLC0415 - optmat is lazy from optmat.gas.raman_response import raman_response # noqa: PLC0415 - as above @@ -662,18 +640,6 @@ def _refuse_partners(request: _Request, error: Exception) -> NoReturn: Each Raman-active constituent is tried against each partner on its own, so that the message names the pair optmat has no broadening for (SEP 3, D16). - - Parameters - ---------- - request : _Request - The refused composition. - error : Exception - The refusal of the whole composition by optmat. - - Raises - ------ - MaterialError - Always. """ from optmat.exceptions import OptmatError # noqa: PLC0415 - optmat is lazy from optmat.gas.raman_response import raman_response # noqa: PLC0415 - as above @@ -711,24 +677,6 @@ def _supplied(response: object, request: _Request) -> _optmat.Untyped: A response fixes its composition, temperature and pump wavelength; one built for another state is refused rather than used where it does not apply. - - Parameters - ---------- - response : object - ``MaterialSelection.raman``: an optmat object with an identity. - request : _Request - The run's composition and state. - - Returns - ------- - optmat.gas.raman_response.GasRamanResponse - The response, as it is. - - Raises - ------ - MaterialError - If it is not a gas Raman response, or its composition, temperature or pump - wavelength is not the run's. """ model = _optmat.untyped(response) if _optmat.record(response).kind != "raman_response": @@ -767,23 +715,6 @@ def _with_dephasing(response: _optmat.Untyped, request: _Request) -> _optmat.Unt ``c + beta n`` of every band of its species (optmat's ``width_law``), so that the response's identity records the change (status ``user``). A law with a Dicke term is refused: optmat caps that term at a Doppler width the legacy law does not have. - - Parameters - ---------- - response : optmat.gas.raman_response.GasRamanResponse - The response. - request : _Request - The supplied laws by species. - - Returns - ------- - optmat.gas.raman_response.GasRamanResponse - The response with the laws applied, or as it was without any. - - Raises - ------ - MaterialError - If a supplied law has a Dicke term. """ laws: dict[str, tuple[float, float, float, float]] = {} for name, dephasing in request.dephasing.items(): @@ -820,18 +751,8 @@ def _reemit( ) -> None: """Raise optmat's range warnings once per run, and record them. - Parameters - ---------- - caught : sequence of warnings.WarningMessage - What optmat raised. - response : optmat.gas.raman_response.GasRamanResponse - The response the warnings are about. - kind : str - ``"temperature"`` or ``"density"``: one warning of each per response and run. - where : str - What the response is for. - part : str - The state declared, for the record. + - ``kind``: ``"temperature"`` or ``"density"``: one warning of each per response and + run. """ from optmat.exceptions import ModelRangeWarning # noqa: PLC0415 - optmat is lazy @@ -866,18 +787,8 @@ def _check_range( the smallest positive double is declared for that interval too, and the record's lower bound is zero: "to vacuum". - Parameters - ---------- - response : optmat.gas.raman_response.GasRamanResponse - The response. - request : _Request - The run's densities, at its probes in z order. - - Returns - ------- - tuple of float or None - The smallest and largest declared densities in m^-3, the smallest 0.0 for a - path that reaches vacuum, or ``None`` when none is known. + - Returns: the smallest and largest declared densities in m^-3, the smallest 0.0 for + a path that reaches vacuum, or ``None`` when none is known. """ if request.densities is None: return None @@ -906,25 +817,7 @@ def _check_range( def _kept_groups(response: _optmat.Untyped, request: _Request) -> list[_optmat.Untyped]: - """Return the damping groups the requested species and term flags keep. - - Parameters - ---------- - response : optmat.gas.raman_response.GasRamanResponse - The response. - request : _Request - The species to keep and their flags. - - Returns - ------- - list of optmat.gas.raman_response.RamanGroup - The kept groups in optmat's order. - - Raises - ------ - MaterialError - If a term required by ``True`` is absent, or nothing is left. - """ + """Return the damping groups the requested species and term flags keep.""" kept = [] for name, options in request.keep.items(): bands = { @@ -963,24 +856,8 @@ def _kept_groups(response: _optmat.Untyped, request: _Request) -> list[_optmat.U def _shift(groups: Sequence[_optmat.Untyped], request: _Request) -> RamanShift | None: """Decide the density at which the shifted groups' rows are built. - Parameters - ---------- - groups : sequence of optmat.gas.raman_response.RamanGroup - The kept groups. - request : _Request - The run's density path and selection. - - Returns - ------- - RamanShift or None - ``None`` when no kept group shifts; the constant density; or, under - ``raman_shift="reference-density"``, the reference density and its bound. - - Raises - ------ - MaterialError - If a group shifts on a varying density without the named approximation, or - on a density the run does not state. + - Returns: ``None`` when no kept group shifts; the constant density; or, under + ``raman_shift="reference-density"``, the reference density and its bound. """ shifted = [group for group in groups if group.shifted] if not shifted: @@ -1029,19 +906,7 @@ def _terms( second, so the isotropic amplitudes go to the isotropic term and the anisotropic ones to the rank-two term unchanged. An all-zero row is dropped. - Parameters - ---------- - groups : sequence of optmat.gas.raman_response.RamanGroup - The kept groups. - response : optmat.gas.raman_response.GasRamanResponse - Their response, for the width laws in Hz. - shift : RamanShift or None - The density a shifted group's rows are built at. - - Returns - ------- - tuple of RamanTensorTerm - In group order, isotropic before rank-two within a group. + - ``response``: their response, for the width laws in Hz. """ terms = [] for group in groups: @@ -1067,38 +932,7 @@ def _terms( def _assemble( request: _Request, *, thg: bool, labels: tuple[str, ...], leaf: str | None ) -> OptmatRamanResponse: - """Build, check and map one optmat response onto solprop's terms. - - Parameters - ---------- - request : _Request - The composition, state and what to keep. - thg : bool - Whether a field grid keeps the third harmonic of the drive. - labels : tuple of str - The labels whose summed density drives the response. - leaf : str or None - The label the compiled leaf carries. - - Returns - ------- - OptmatRamanResponse - The response. - - Raises - ------ - MaterialError - If optmat refuses the composition, a provisional response is not admitted, - or a shift or a term flag cannot be honoured. - - Warns - ----- - MaterialRangeWarning - Once per run each, if the temperature or a declared density is outside the - response's validated range. - MaterialStatusWarning - Once per run, for an admitted provisional response. - """ + """Build, check and map one optmat response onto solprop's terms.""" built, caught = _build(request) response = _with_dephasing(built, request) entry = _optmat.record(response) @@ -1156,24 +990,8 @@ def _carrier( ) -> float: """Return the pump wavelength the vibrational strengths are dispersed at. - Parameters - ---------- - selection : MaterialSelection - The run's selection; an explicit carrier is its deliberate choice. - wavelength : float or None - The run's grid reference wavelength in m. - where : str - What the response is for. - - Returns - ------- - float - ``selection.carrier_wavelength``, else `wavelength`, in m. - - Raises - ------ - MaterialError - If neither is given. + - ``wavelength``: the run's grid reference wavelength in m. + - Returns: ``selection.carrier_wavelength``, else `wavelength`, in m. """ carrier = selection.carrier_wavelength if carrier is None: @@ -1268,20 +1086,7 @@ def gas_raman( def _check_levels(options: MolecularOptions, where: str) -> None: - """Refuse a level truncation, which optmat's response does not take. - - Parameters - ---------- - options : MolecularOptions - The options. - where : str - What they are for. - - Raises - ------ - MaterialError - If ``min_j`` or ``max_j`` differs from its default. - """ + """Refuse a level truncation, which optmat's response does not take.""" defaults = MolecularOptions() if (options.min_j, options.max_j) != (defaults.min_j, defaults.max_j): msg = ( @@ -1317,17 +1122,7 @@ class _Path: def _sampled(mixture: Mixture, length: float | None) -> _Path: """Sample every population's density over the run. - Parameters - ---------- - mixture : Mixture - The fill. - length : float or None - The run's length in m, or ``None`` for the densities' own span. - - Returns - ------- - _Path - The probes, the densities and the summed path. + - ``length``: the run's length in m, or ``None`` for the densities' own span. """ paths = tuple(component.density for component in mixture.components) total = paths[0] if len(paths) == 1 else Sum(paths) @@ -1355,24 +1150,7 @@ def _composition( Duplicate labels of one species are merged; a label whose density is zero over the whole run takes no part. - Parameters - ---------- - mixture : Mixture - The fill. - path : _Path - Its sampled densities. - where : str - What the response is for. - - Returns - ------- - tuple - Species to mole fraction, and the largest relative spread of a fraction. - - Raises - ------ - MaterialError - If the fractions vary along the run beyond :data:`COMPOSITION_TOLERANCE`. + - Returns: species to mole fraction, and the largest relative spread of a fraction. """ names = [component.species.name for component in mixture.components] by_species: dict[str, Float[np.ndarray, " n_probes"]] = {} @@ -1405,20 +1183,7 @@ def _choices( mixture: Mixture, raman: bool | Sequence[str] | Mapping[str, bool | MolecularOptions], ) -> dict[str, MolecularOptions]: - """Return each selected label's term flags. - - Parameters - ---------- - mixture : Mixture - The fill. - raman : bool, sequence of str or Mapping - :func:`solprop.nonlinear.mixtures.responses_for`'s selection. - - Returns - ------- - dict - Label to options, for the labels selected. - """ + """Return each selected label's term flags.""" chosen: dict[str, MolecularOptions] = {} for component in mixture.components: label = component.label @@ -1441,25 +1206,7 @@ def _keep( ) -> tuple[dict[str, MolecularOptions], dict[str, Dephasing]]: """Return the species whose lines are kept, their flags and supplied laws. - Parameters - ---------- - mixture : Mixture - The fill. - chosen : Mapping of str to MolecularOptions - The selected labels' options. - explicit : bool - Whether the labels were named, so that one without a response is refused. - - Returns - ------- - tuple - Species to options, and species to a supplied dephasing. - - Raises - ------ - MaterialError - If a named label has no molecular response, or two labels of one species - disagree about their options or linewidths. + - Returns: species to options, and species to a supplied dephasing. """ keep: dict[str, MolecularOptions] = {} laws: dict[str, Dephasing] = {} @@ -1494,20 +1241,7 @@ def _keep( def _requested_temperature(options: Sequence[MolecularOptions]) -> float | None: """Return the one explicit reference temperature the options name, if any. - Parameters - ---------- - options : sequence of MolecularOptions - The kept species' options. - - Returns - ------- - float or None - The temperature in K, or ``None`` when none names one. - - Raises - ------ - MaterialError - If two name different temperatures: a response has one. + - Returns: the temperature in K, or ``None`` when none names one. """ named = {float(o.temperature) for o in options if o.temperature is not None} if len(named) > 1: @@ -1668,15 +1402,7 @@ def _reference_density(path: _Path) -> float | None: :math:`N_\mathrm{ref} = \int N^2\,dz / \int N\,dz`, by quadrature across the path's breakpoints. - Parameters - ---------- - path : _Path - The run's summed density. - - Returns - ------- - float or None - The reference density in m^-3, or ``None`` over an unbounded run. + - Returns: the reference density in m^-3, or ``None`` over an unbounded run. """ from solprop.nonlinear.mixtures import _mean_temperature # noqa: PLC0415 - a cycle diff --git a/src/solprop/nonlinear/optmat_rates.py b/src/solprop/nonlinear/optmat_rates.py index 3e03ad60..750374e4 100644 --- a/src/solprop/nonlinear/optmat_rates.py +++ b/src/solprop/nonlinear/optmat_rates.py @@ -133,25 +133,7 @@ def _validate_positive(name: str, value: float) -> float: - """Return a positive finite number, or refuse it by name. - - Parameters - ---------- - name : str - The argument, for the message. - value : float - The number. - - Returns - ------- - float - The number as a float. - - Raises - ------ - MaterialError - If it is not positive and finite. - """ + """Return a positive finite number, or refuse it by name.""" if isinstance(value, bool) or not (math.isfinite(value) and value > 0): msg = f"OptmatRate: {name} must be positive and finite, got {value!r}" raise MaterialError(msg) @@ -159,19 +141,7 @@ def _validate_positive(name: str, value: float) -> float: def _is_rate_model(value: object) -> bool: - """Return whether an object is an optmat rate model. - - Parameters - ---------- - value : object - Anything. - - Returns - ------- - bool - True for an object with an identity, an instantaneous rate, an energy per - event, a maximum field and a wavelength. - """ + """Return whether an object is an optmat rate model.""" return all( hasattr(value, name) for name in ( @@ -187,26 +157,7 @@ def _is_rate_model(value: object) -> bool: def _resolve(name: str, wavelength: float, form: str) -> object: """Resolve a species name and a model name to optmat's rate model. - Parameters - ---------- - name : str - The species: an atom of ``ionisation_rate_model`` or a molecule of - ``molecular_rate_model``. - wavelength : float - Vacuum carrier wavelength in m. - form : str - ``"recommended"`` or a model name of the species' registry. - - Returns - ------- - object - The optmat model. - - Raises - ------ - MaterialError - If optmat has no rate for the species, or the form is not one of its - family's, naming the family that serves it. + - ``wavelength``: vacuum carrier wavelength in m. """ from optmat.exceptions import OptmatError # noqa: PLC0415 - optmat is lazy from optmat.gas.ionisation_rates import ( # noqa: PLC0415 - optmat is lazy @@ -267,19 +218,7 @@ def _resolve(name: str, wavelength: float, form: str) -> object: @contextmanager def _quiet(*, reemit: bool) -> Iterator[list[warnings.WarningMessage]]: - """Evaluate optmat with its range warnings captured, and pass on any other. - - Parameters - ---------- - reemit : bool - Whether the caller handles the captured range warnings itself (the list is - returned) rather than dropping them. - - Yields - ------ - list of warnings.WarningMessage - optmat's ``ModelRangeWarning`` raised inside, for the caller. - """ + """Evaluate optmat with its range warnings captured, and pass on any other.""" from optmat.exceptions import ModelRangeWarning # noqa: PLC0415 - optmat is lazy ranged: list[warnings.WarningMessage] = [] @@ -299,17 +238,11 @@ def _bisect( ) -> tuple[float, float]: """Narrow a bracket in the logarithm of the field. - Parameters - ---------- - above : callable - True where the field is at or above the sought crossing. - low, high : float - A bracket in V/m, ``above(low)`` false and ``above(high)`` true. - - Returns - ------- - tuple of float - The narrowed bracket, ``high / low - 1`` below :data:`_BISECTION_TOLERANCE`. + - ``above``: True where the field is at or above the sought crossing. + - ``low``, ``high``: a bracket in V/m, ``above(low)`` false and ``above(high)`` + true. + - Returns: the narrowed bracket, ``high / low - 1`` below + :data:`_BISECTION_TOLERANCE`. """ # Each step halves the bracket's logarithmic width; from a few decades about # 55 steps reach the tolerance, far above the spacing of adjacent doubles. @@ -440,18 +373,7 @@ def __post_init__(self) -> None: self._bind_energy() def _resolve_model(self) -> object: - """Return the optmat model a name resolves to, or the model given. - - Returns - ------- - object - The optmat rate model. - - Raises - ------ - MaterialError - As the class does. - """ + """Return the optmat model a name resolves to, or the model given.""" if isinstance(self.source, str): if self.wavelength is None: msg = ( @@ -500,13 +422,7 @@ def optmat_model(self) -> _optmat.Untyped: return self._model() def _model(self) -> _optmat.Untyped: - """Return the model for attribute access. - - Returns - ------- - Any - The resolved optmat model. - """ + """Return the model for attribute access.""" return _optmat.untyped(self._resolved) def _raw( @@ -514,15 +430,8 @@ def _raw( ) -> Float[np.ndarray, " n_field"]: """Return optmat's instantaneous rate, its range warnings dropped. - Parameters - ---------- - field_amplitude : numpy.ndarray - Non-negative fields in V/m, shape ``(n_field,)``. - - Returns - ------- - numpy.ndarray - Rates in s^-1, shape ``(n_field,)``. + - ``field_amplitude``: Non-negative fields in V/m, shape ``(n_field,)``. + - Returns: rates in s^-1, shape ``(n_field,)``. """ with _quiet(reemit=False): return np.asarray( @@ -532,17 +441,8 @@ def _raw( def _find_threshold(self) -> float: """Return the field at which the rate reaches the negligible rate. - Returns - ------- - float - The smallest field in V/m of the final bracket, at which the rate is at - or above `negligible_rate`. - - Raises - ------ - MaterialError - If the rate is negligible even at the barrier-suppression field, or not - negligible at any field the search reaches. + - Returns: the smallest field in V/m of the final bracket, at which the rate is + at or above `negligible_rate`. """ model = self._model() negligible = self.negligible_rate @@ -550,15 +450,7 @@ def _find_threshold(self) -> float: def above(value: float) -> bool: """Return whether the rate at a field is at least the negligible rate. - Parameters - ---------- - value : float - Field in V/m. - - Returns - ------- - bool - The comparison. + - ``value``: field in V/m. """ return float(model.instantaneous_rate(value)) >= negligible @@ -587,11 +479,6 @@ def _translate_warnings(self, ranged: Sequence[warnings.WarningMessage]) -> None At resolution the model is evaluated below its barrier-suppression field, so a range warning there is about the carrier (below 350 nm), not the field. - - Parameters - ---------- - ranged : sequence of warnings.WarningMessage - The range warnings optmat raised while the model was resolved. """ record = self.material_records[0] # Each evaluation of the search repeats the same warning; one is enough. @@ -607,13 +494,10 @@ def _translate_warnings(self, ranged: Sequence[warnings.WarningMessage]) -> None def _energy_span(self) -> tuple[float, float]: """Return the fields the constant energy and its bound are defined over. - Returns - ------- - tuple of float - The tabulated fields above the threshold and up to the maximum field, - above which the rate is held (:meth:`__call__`); the threshold to the - maximum field without a `field_range`. A span the threshold leaves empty - is the threshold alone. + - Returns: the tabulated fields above the threshold and up to the maximum field, + above which the rate is held (:meth:`__call__`); the threshold to the maximum + field without a `field_range`. A span the threshold leaves empty is the + threshold alone. """ threshold = self._threshold ceiling = max(threshold, float(self._model().maximum_field)) @@ -627,15 +511,8 @@ def _energies( ) -> Float[np.ndarray, " n_field"]: """Return optmat's rate-weighted energy per event, its range warnings dropped. - Parameters - ---------- - fields : numpy.ndarray - Fields in V/m, shape ``(n_field,)``. - - Returns - ------- - numpy.ndarray - Energies in J, shape ``(n_field,)``. + - ``fields``: fields in V/m, shape ``(n_field,)``. + - Returns: energies in J, shape ``(n_field,)``. """ with _quiet(reemit=False): return np.asarray(self._model().energy_per_event(fields), dtype=np.float64) @@ -1269,25 +1146,18 @@ def peak_field_report( """ record = rate.material_records[0] - def at(value: float) -> float: + def _at(value: float) -> float: """Return the rate the statistic records at a field. - Parameters - ---------- - value : float - Field in V/m. - - Returns - ------- - float - Rate in s^-1. + - ``value``: field in V/m. + - Returns: rate in s^-1. """ return float(rate(value)) if average is None else float(average(value)) maximum = rate.maximum_field # The rate is held above the maximum field, so a peak at the rate there (to the # table's own tolerance) says the run reached it, and not by how much. - held = at(maximum) * (1.0 - _TABLE_TOLERANCE) + held = _at(maximum) * (1.0 - _TABLE_TOLERANCE) reached = peak_rate >= held > 0.0 if not peak_rate > 0.0: largest = None @@ -1295,7 +1165,7 @@ def at(value: float) -> float: largest = maximum else: _, largest = _bisect( - lambda value: at(value) >= peak_rate, rate.threshold, maximum + lambda value: _at(value) >= peak_rate, rate.threshold, maximum ) energy = None if largest is None else float(rate.energy_per_event(largest)) return PeakFieldReport( diff --git a/src/solprop/nonlinear/raman.py b/src/solprop/nonlinear/raman.py index 059d184b..5965bc89 100644 --- a/src/solprop/nonlinear/raman.py +++ b/src/solprop/nonlinear/raman.py @@ -110,10 +110,8 @@ def _tensor_bundle() -> SilicaBundle: """Return optmat's recommended tensor bundle of fused silica, the default. - Returns - ------- - SilicaBundle - ``SilicaBundle.resolve(tensor=True)``: Lin and Agrawal's shape (SEP 3, D11). + - Returns: ``SilicaBundle.resolve(tensor=True)``: Lin and Agrawal's shape (SEP 3, + D11). """ return SilicaBundle.resolve(tensor=True) @@ -919,24 +917,10 @@ def _check_evaluate( ``kind`` is a plain string rather than :data:`solprop.fourier.GridKind` for the reason given in :func:`solprop.nonlinear.responses._check_evaluate`. - Parameters - ---------- - values : numpy.ndarray - The samples passed as ``field_t``. - kind : str - Layout name to check. - dt : float - Sample spacing of the fine grid, s. - max_components : int, optional - How many Cartesian components the caller carries: two for the legacy - routes, whose transverse state is at most two-component, and three for the - general modal guide's tensor responses. - - Raises - ------ - GridError - If the layout is unknown, the array shape or dtype does not match it, the - sample spacing is not positive, or samples are not finite numbers. + - ``dt``: sample spacing of the fine grid, s. + - ``max_components``: how many Cartesian components the caller carries: two for the + legacy routes, whose transverse state is at most two-component, and three for the + general modal guide's tensor responses. """ if kind not in ("field", "envelope"): msg = f"kind must be 'field' or 'envelope', got {kind!r}" @@ -1030,16 +1014,6 @@ def _half_modulus_squared( ``abs(z) ** 2`` rounds twice and can miss the exact answer for an integer-valued complex number; the sum of squares is both closer and cheaper. - - Parameters - ---------- - values : numpy.ndarray - Complex samples, shape ``(n_to,)``. - - Returns - ------- - numpy.ndarray - Half the squared modulus, real, shape ``(n_to,)``. """ return 0.5 * (np.real(values) ** 2 + np.imag(values) ** 2) @@ -1080,10 +1054,7 @@ def __post_init__(self) -> None: def _check_density(density: float) -> None: """Reject a non-finite or negative molecular number density. - Parameters - ---------- - density : float - Number density in m^-3. + - ``density``: number density in m^-3. """ if not math.isfinite(density) or density < 0.0: raise MaterialError( @@ -1095,15 +1066,7 @@ def _check_density(density: float) -> None: def _check_delays(t: Float[np.ndarray, " n_to"]) -> Float[np.ndarray, " n_to"]: """Validate finite real delays before evaluating molecular models. - Parameters - ---------- - t : numpy.ndarray - Delays in s, shape ``(n_to,)``. - - Returns - ------- - numpy.ndarray - Finite real delays as float64. + - ``t``: delays in s, shape ``(n_to,)``. """ times = np.asarray(t) if times.ndim != 1 or not np.issubdtype(times.dtype, np.number): @@ -1120,19 +1083,11 @@ def _causal_convolution( ) -> Float[np.ndarray, "... n_to"]: """Evaluate a causal convolution with doubled padding along the last axis. - Parameters - ---------- - kernel : numpy.ndarray - Causal kernel in m^5 V^-2 s^-1, shape ``(n_to,)``. - drive : numpy.ndarray - Real quadratic drive in V^2/m^2, with ``n_to`` samples on its last axis. - dt : float - Positive temporal spacing in s. - - Returns - ------- - numpy.ndarray - Causal integral with the drive's shape, in m^3. + - ``kernel``: causal kernel in m^5 V^-2 s^-1, shape ``(n_to,)``. + - ``drive``: real quadratic drive in V^2/m^2, with ``n_to`` samples on its last + axis. + - ``dt``: positive temporal spacing in s. + - Returns: causal integral with the drive's shape, in m^3. """ n_to = kernel.size spectrum = np.fft.rfft(kernel, n=2 * n_to) * dt @@ -1148,19 +1103,9 @@ def _quadratic_tensor( ) -> Float[np.ndarray, "n_pol n_pol n_to"]: """Form the real molecular drive, retaining delayed off-diagonal coherence. - Parameters - ---------- - values : numpy.ndarray - Local field in V/m, shape ``(n_pol, n_to)``. - kind : {'field', 'envelope'} - Temporal representation. - thg : bool - Keep the instantaneous real-field drive; ignored for envelopes. - - Returns - ------- - numpy.ndarray - Real symmetric quadratic tensor in V^2/m^2, shape ``(n_pol, n_pol, n_to)``. + - ``values``: local field in V/m, shape ``(n_pol, n_to)``. + - Returns: real symmetric quadratic tensor in V^2/m^2, shape ``(n_pol, n_pol, + n_to)``. """ if kind == "field" and thg: return np.real(values[:, None, :] * values[None, :, :]) @@ -1513,21 +1458,10 @@ def _evaluate_tensor( ) -> Num[np.ndarray, "2 n_to"]: """Contract the molecular tensor directly, independently of backend layout. - Parameters - ---------- - values : numpy.ndarray - Local Cartesian field in V/m, shape ``(2, n_to)``. - density : float - Non-negative number density in m^-3. - kind : {'field', 'envelope'} - Temporal representation. - dt : float - Positive temporal spacing in s. - - Returns - ------- - numpy.ndarray - Cartesian polarisation in C/m^2, shape ``(2, n_to)``. + - ``values``: local Cartesian field in V/m, shape ``(2, n_to)``. + - ``density``: Non-negative number density in m^-3. + - ``dt``: positive temporal spacing in s. + - Returns: cartesian polarisation in C/m^2, shape ``(2, n_to)``. """ quadratic = _quadratic_tensor(values, kind=kind, thg=self.thg) trace = quadratic[0, 0] + quadratic[1, 1] @@ -1722,10 +1656,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. """ parameters = self.parameters if isinstance(parameters, SilicaBundle): @@ -1898,17 +1829,8 @@ def _hellwarth_parts( ]: """Weight Lin and Agrawal's two shapes into Hellwarth's ``a`` and ``b``. - Parameters - ---------- - shape_a : numpy.ndarray - ``h_a``, in 1/s, or a one-element array of its one-sided limit. - shape_b : numpy.ndarray - ``h_b``, on the same samples. - - Returns - ------- - tuple of numpy.ndarray - ``a = w_a h_a`` and ``b = w_b h_b + w_ab h_a``, in the inputs' units. + - ``shape_a``: ``h_a``, in 1/s, or a one-element array of its one-sided limit. + - Returns: ``a = w_a h_a`` and ``b = w_b h_b + w_ab h_a``, in the inputs' units. """ parameters = cast("LinAgrawalRaman", self.parameters) isotropic = parameters.isotropic_weight * shape_a @@ -1959,10 +1881,7 @@ def hellwarth( def _limits(self) -> Float[np.ndarray, "2 1"]: """Return the right-hand limits of ``a`` and ``b`` at zero delay. - Returns - ------- - numpy.ndarray - Shape ``(2, 1)`` in 1/s. + - Returns: shape ``(2, 1)`` in 1/s. """ parameters = self.parameters if isinstance(parameters, SilicaBundle): @@ -1984,18 +1903,9 @@ def _channels( ) -> Float[np.ndarray, "2 n_channel_samples"]: """Combine Hellwarth's ``a`` and ``b`` into the trace and rank-two channels. - Parameters - ---------- - hellwarth_a : numpy.ndarray - ``a``, in 1/s, or a one-element array of its one-sided limit. - hellwarth_b : numpy.ndarray - ``b``, on the same samples. - - Returns - ------- - numpy.ndarray - ``a + b/3`` and ``2 b/3`` stacked along a leading axis, in the inputs' - units. + - ``hellwarth_a``: ``a``, in 1/s, or a one-element array of its one-sided limit. + - Returns: ``a + b/3`` and ``2 b/3`` stacked along a leading axis, in the + inputs' units. """ return np.stack([hellwarth_a + hellwarth_b / 3.0, 2.0 * hellwarth_b / 3.0]) @@ -2183,30 +2093,7 @@ def _optmat_response( # noqa: PLR0917 - called positionally ) -> RamanResponse: """Build a gas's Raman response from optmat for :func:`response_for`. - Parameters - ---------- - gas : str - The species' name. - options : MolecularOptions or None - Term flags and temperature. - selection : MaterialSelection - The selection. - fraction : float or None - Refused: the response is absolute. - thg : bool - Whether a field grid keeps the third harmonic. - density : float or None - The fill's constant density in m^-3. - - Returns - ------- - RamanResponse - The response from optmat, on solprop's terms. - - Raises - ------ - MaterialError - With a fraction, or as optmat's adapter refuses. + - ``density``: the fill's constant density in m^-3. """ from solprop.nonlinear.optmat_raman import gas_raman # noqa: PLC0415 - a cycle @@ -2229,15 +2116,7 @@ def _right_limit( rounds alike), so it cancels to an exact zero, and a jump, the boson peak's ``2 / tau_b``, is the same at both and survives. - Parameters - ---------- - function : callable - The response of a delay array in s, in 1/s. - - Returns - ------- - numpy.ndarray - The limit, shape ``(1,)``. + - ``function``: the response of a delay array in s, in 1/s. """ tiny = np.finfo(float).tiny return 2.0 * function(np.full(1, tiny)) - function(np.full(1, 2.0 * tiny)) @@ -2494,24 +2373,10 @@ def _molecular_models( ) -> tuple[RamanTensorTerm, ...]: """Build the requested terms of a molecular gas's Raman response. - Parameters - ---------- - parameters : MolecularRaman - The species' available molecular data. - options : MolecularOptions - Which parts to build, and the thermodynamic state; a term flag of ``None`` - takes the term when the species has it, ``True`` requires it. - - Returns - ------- - tuple of RamanTensorTerm - The rotational ladder first, then the vibrational mode; either may be absent. - - Raises - ------ - MaterialError - If a required term has no model, both parts are switched off, or neither - exists for this gas. + - ``options``: which parts to build, and the thermodynamic state; a term flag of + ``None`` takes the term when the species has it, ``True`` requires it. + - Returns: the rotational ladder first, then the vibrational mode; either may be + absent. """ for selected, data, name in ( (options.rotation, parameters.rotational, "rotational"), @@ -2656,34 +2521,7 @@ def _supplied_response( # noqa: PLR0917 - called positionally selection: MaterialSelection | None, density: float | None, ) -> RamanResponse: - """Build a caller's species' Raman response from its own molecular constants. - - Parameters - ---------- - material : Species - A supplied species, never matched to a built-in gas by its name. - options : MolecularOptions or None - Term flags and temperature. - fraction : float or None - Refused: the response is absolute. - thg : bool - Whether a field grid keeps the third harmonic. - selection : MaterialSelection or None - Refused: a selection names optmat's models, not a caller's. - density : float or None - Refused: it belongs to an optmat response. - - Returns - ------- - RamanResponse - One term per part of the spectrum. - - Raises - ------ - MaterialError - If the species has no Raman constants, or a fraction, selection or density - is given. - """ + """Build a caller's species' Raman response from its own molecular constants.""" if selection is not None or density is not None: msg = ( f"{material.name} is a caller's species: its Raman response is built from " diff --git a/src/solprop/nonlinear/responses.py b/src/solprop/nonlinear/responses.py index ef513986..dc460856 100644 --- a/src/solprop/nonlinear/responses.py +++ b/src/solprop/nonlinear/responses.py @@ -179,25 +179,8 @@ def _check_evaluate( reason given in :func:`solprop.fourier._check_kind`: this is the run-time guard, and the test session's ``beartype`` hook would otherwise reject a bad value first. - Parameters - ---------- - values : numpy.ndarray - The samples passed as ``field_t``. - kind : str - Layout name to check. - carrier : numpy.ndarray or None - The carrier passed by the caller, if any. - needs_carrier : bool - Whether this response and layout require one. - max_pol : int, optional - The most field components the response takes: two for a transverse field, three - for the general modal route's full vector field. Default :data:`MAX_N_POL`. - - Raises - ------ - GridError - If the layout is unknown, the array shape or dtype does not match the layout, - or the carrier is missing, unwanted or the wrong length. + - ``carrier``: the carrier passed by the caller, if any. + - ``needs_carrier``: whether this response and layout require one. """ if kind not in ("field", "envelope"): msg = f"kind must be 'field' or 'envelope', got {kind!r}" @@ -427,15 +410,8 @@ def _isotropic( contracted over the polarisation axis without conjugation; ``x y z`` for one component and ``(E . E) E`` when the three coincide (SEP 6, Phase 0, decision Q3). - Parameters - ---------- - x, y, z : numpy.ndarray - Three fields of one shape, the polarisation axis second to last. - - Returns - ------- - numpy.ndarray - The contraction, of the same shape. + - ``x``, ``y``, ``z``: three fields of one shape, the polarisation axis second to + last. """ if x.shape[-2] == 1: return x * y * z @@ -452,19 +428,8 @@ def _filtered( ) -> Num[np.ndarray, "*nodes n_pol n_to"]: """Apply a spectral multiplier to a field on the fine time grid. - Parameters - ---------- - values : numpy.ndarray - A real field (field grid) or a complex envelope (envelope grid). - multiplier : numpy.ndarray - Its multiplier on the transform's spectral layout. - kind : {'field', 'envelope'} - The layout. - - Returns - ------- - numpy.ndarray - The filtered field, of the input's dtype. + - ``multiplier``: its multiplier on the transform's spectral layout. + - ``kind``: the layout. """ n_to = values.shape[-1] if kind == "field": @@ -478,17 +443,7 @@ def _analytic( ) -> Complex[np.ndarray, "*nodes n_pol n_to"]: """Return the analytic signal of a real field: ``Pi_+`` doubled, DC and Nyquist out. - Parameters - ---------- - values : numpy.ndarray - A real field on the fine time grid. - positive : numpy.ndarray - The one-sided bins of strictly positive frequency. - - Returns - ------- - numpy.ndarray - ``u`` with ``Re u = E`` wherever the field has no DC or Nyquist content. + - ``positive``: the one-sided bins of strictly positive frequency. """ n_to = values.shape[-1] spectrum = np.zeros((*values.shape[:-1], n_to), dtype=np.complex128) @@ -518,29 +473,14 @@ def _product_spectrum( carries ``exp(+2 i omega0 t)`` and the third ``exp(-2 i omega0 t)``, and ``Pi_+`` acts on absolute frequency. - Parameters - ---------- - x, y, z : numpy.ndarray - The three fields on the fine time grid: real on a field grid, envelopes on an - envelope grid. - positive : numpy.ndarray - ``Pi_+`` on the transform's spectral layout. - kind : {'field', 'envelope'} - The layout. - sector : {'full', 'conserving'} - Which quartets are kept. - carrier : numpy.ndarray or None - ``exp(2 i omega0 t)``, required for the full sector on an envelope grid. - window : numpy.ndarray or None, optional - The temporal collar, multiplying the product in time before its transform, - ahead of ``Pi_+`` and every output multiplier; ``None`` is a unit collar. - - Returns - ------- - numpy.ndarray - The product's spectrum on the transform's layout: the one-sided spectrum of - the real polarisation on a field grid, the envelope of the analytic - polarisation on an envelope grid. + - ``positive``: ``Pi_+`` on the transform's spectral layout. + - ``kind``: the layout. + - ``window``: the temporal collar, multiplying the product in time before its + transform, ahead of ``Pi_+`` and every output multiplier; ``None`` is a unit + collar. + - Returns: the product's spectrum on the transform's layout: the one-sided spectrum + of the real polarisation on a field grid, the envelope of the analytic + polarisation on an envelope grid. """ collar = 1.0 if window is None else window if kind == "field" and sector == "full": @@ -635,37 +575,26 @@ def dispersive_cubic( n_to = values.shape[-1] positive = weights.positive - def product( + def _product( x: np.ndarray, y: np.ndarray, z: np.ndarray ) -> Complex[np.ndarray, "*nodes n_pol n_omega_fine"]: - """Return one product's spectrum in this call's sector and layout. - - Parameters - ---------- - x, y, z : numpy.ndarray - The three fields of the product. - - Returns - ------- - numpy.ndarray - Its spectrum (:func:`_product_spectrum`). - """ + """Return one product's spectrum in this call's sector and layout.""" return _product_spectrum( x, y, z, positive, kind=kind, sector=sector, carrier=carrier, window=window ) if weights.evaluator == "symmetric": filtered = _filtered(values, weights.filter, kind) - spectrum = product(filtered, filtered, filtered) * weights.filter + spectrum = _product(filtered, filtered, filtered) * weights.filter else: assert weights.output is not None first = _filtered(values, weights.filter, kind) second = _filtered(first, weights.filter, kind) spectrum = ( - product(values, values, values) * weights.output[0] - + product(values, values, first) * weights.output[1] - + weights.products[0] * product(values, values, second) - + weights.products[1] * product(values, first, first) + _product(values, values, values) * weights.output[0] + + _product(values, values, first) * weights.output[1] + + weights.products[0] * _product(values, values, second) + + weights.products[1] * _product(values, first, first) ) if kind == "field": return np.fft.irfft(spectrum, n=n_to, axis=-1) @@ -1010,18 +939,7 @@ def evaluate( def _frozen(values: np.ndarray) -> np.ndarray: - """Return a C-contiguous read-only copy of an array. - - Parameters - ---------- - values : numpy.ndarray - The array. - - Returns - ------- - numpy.ndarray - Its values, not writeable. - """ + """Return a C-contiguous read-only copy of an array.""" frozen = np.array(values, order="C") frozen.setflags(write=False) return frozen @@ -1035,20 +953,7 @@ def _check_plasma(values: np.ndarray, kind: str, dt: float) -> None: being real. What follows is the shape and dtype half of :func:`_check_evaluate`, reused rather than repeated; its carrier arguments never apply here. - Parameters - ---------- - values : numpy.ndarray - The samples passed as ``field_t``. - kind : str - Layout name to check. - dt : float - Sample spacing of the fine time grid, s. - - Raises - ------ - GridError - If the layout is not ``'field'``, the array shape or dtype does not match it, or - the time step is not positive and finite. + - ``dt``: sample spacing of the fine time grid, s. """ if kind != "field": msg = ( @@ -1403,23 +1308,8 @@ def _rule(self, integral: RunningIntegral | None, dt: float) -> RunningIntegral: The response never designs taps itself and never looks at the field's spectrum: the band rule's taps belong to the grid, and a direct call is handed them. - Parameters - ---------- - integral : RunningIntegral or None - The rule passed to the call. - dt : float - The call's sample spacing, s. - - Returns - ------- - RunningIntegral - `integral`, or the trapezoid when it is omitted under that rule. - - Raises - ------ - GridError - If the band rule has no `integral`, `integral` is another rule than - ``integration``, or it was designed for another ``dt``. + - ``dt``: the call's sample spacing, s. + - Returns: `integral`, or the trapezoid when it is omitted under that rule. """ if integral is None: if self.integration == "band": @@ -1450,15 +1340,7 @@ def _magnitude( unit in the last place of ``|E|`` into a relative difference of 1e-13 in the rate, and the stored reference data are built with ``hypot`` too. - Parameters - ---------- - values : numpy.ndarray - The real field, shape ``(n_pol, n_to)``. - - Returns - ------- - numpy.ndarray - ``|E|`` in V/m, shape ``(n_to,)``. + - Returns: ``|E|`` in V/m, shape ``(n_to,)``. """ if values.shape[0] == 1: return np.abs(values[0]) @@ -1476,28 +1358,9 @@ def _fraction( ]: """Return the field magnitude, the rate and the fraction ionised. - Parameters - ---------- - values : numpy.ndarray - The real field, shape ``(n_pol, n_to)``. - dt : float - Sample spacing of the fine time grid, s. - rule : RunningIntegral - The running-integral rule of the exposure. - - Returns - ------- - tuple of numpy.ndarray - ``|E|`` in V/m, the rate in s^-1 and the fraction ionised, each shape - ``(n_to,)``. - - Raises - ------ - GridError - If the rate returns the wrong shape, or a value that is not finite and - non-negative. - MaterialError - If the preionisation is a path along z rather than one plane's value. + - ``dt``: sample spacing of the fine time grid, s. + - Returns: ``|E|`` in V/m, the rate in s^-1 and the fraction ionised, each shape + ``(n_to,)``. """ if isinstance(self.preionisation, ZProfile): raise MaterialError(_PREIONISATION_PATH) @@ -1535,21 +1398,7 @@ def _loss( where the field vanishes -- the whole point of the rate being identically zero below threshold, and the reason a clipped-but-non-zero table would be unusable. - Parameters - ---------- - values : numpy.ndarray - The real field, shape ``(n_pol, n_to)``. - magnitude : numpy.ndarray - ``|E|``, shape ``(n_to,)``. - rate : numpy.ndarray - The ionisation rate in s^-1, shape ``(n_to,)``. - fraction : numpy.ndarray - The fraction ionised, shape ``(n_to,)``. - - Returns - ------- - numpy.ndarray - The loss current per unit density, shape ``(n_pol, n_to)``. + - ``rate``: the ionisation rate in s^-1, shape ``(n_to,)``. """ # The divisor rather than the field itself: |E| squared underflows to zero # below about 1e-162 V/m, and this is the condition the division needs. @@ -1571,16 +1420,6 @@ def _sum_of_squares( Squares, not squared moduli: this is the combination that produces the third harmonic, and for a single component it is simply ``a**2``. - - Parameters - ---------- - values : numpy.ndarray - Samples, shape ``(n_pol, n_to)``. - - Returns - ------- - numpy.ndarray - ``sum_q values_q**2``, shape ``(1, n_to)``, broadcasting against the input. """ return np.sum(values**2, axis=0, keepdims=True) @@ -1595,16 +1434,6 @@ def _fundamental_cubic( collapses to ``(3/4)|a|^2 a`` and for two it expands to ``(3/4)[(|a_x|^2 + (2/3)|a_y|^2) a_x + (1/3) conj(a_x) a_y^2]``; the algebra is in ``docs/guide/media/nonlinear.md``. - - Parameters - ---------- - analytic : numpy.ndarray - Analytic signal or envelope, complex, shape ``(n_pol, n_to)``. - - Returns - ------- - numpy.ndarray - The cubic, complex, shape ``(n_pol, n_to)``. """ sum_abs = np.sum(np.abs(analytic) ** 2, axis=0, keepdims=True) conjugate_term = 0.25 * _sum_of_squares(analytic) * np.conj(analytic) diff --git a/src/solprop/nonlinear/tdse.py b/src/solprop/nonlinear/tdse.py index 30b30a08..82d34f2b 100644 --- a/src/solprop/nonlinear/tdse.py +++ b/src/solprop/nonlinear/tdse.py @@ -58,31 +58,13 @@ def _positive(value: float, name: str) -> None: - """Require a finite positive real scalar, excluding booleans. - - Parameters - ---------- - value : float - Candidate scalar in the calling field's declared SI units. - name : str - Field name used in the diagnostic. - """ + """Require a finite positive real scalar, excluding booleans.""" if isinstance(value, bool) or not math.isfinite(value) or value <= 0: raise MaterialError(f"{name} must be positive and finite") def _count(value: int, name: str, minimum: int = 1) -> None: - """Require a plain integer above the declared lower bound. - - Parameters - ---------- - value : int - Candidate dimensionless count. - name : str - Field name used in the diagnostic. - minimum : int, optional - Inclusive lower bound, default 1. - """ + """Require a plain integer above the declared lower bound.""" if type(value) is not int or value < minimum: raise MaterialError(f"{name} must be an integer >= {minimum}") @@ -90,12 +72,7 @@ def _count(value: int, name: str, minimum: int = 1) -> None: def _text(value: str, name: str) -> None: """Require a nonempty identifier or source description. - Parameters - ---------- - value : str - Candidate text, which must contain a non-whitespace character. - name : str - Field name used in the diagnostic. + - ``value``: candidate text, which must contain a non-whitespace character. """ if not isinstance(value, str) or not value.strip(): raise MaterialError(f"{name} must be a nonempty string") @@ -104,12 +81,7 @@ def _text(value: str, name: str) -> None: def _strings(values: tuple[str, ...], name: str) -> None: """Keep sequence metadata immutable and free of empty entries. - Parameters - ---------- - values : tuple of str - Candidate immutable nonempty strings; the tuple itself may be empty. - name : str - Field name used in the diagnostic. + - ``values``: candidate immutable nonempty strings; the tuple itself may be empty. """ if type(values) is not tuple: raise MaterialError(f"{name} must be a tuple") @@ -253,12 +225,9 @@ def identity(self) -> str: def _radial_axis(edges: Sequence[float], orders: Sequence[int]) -> None: """Validate an immutable finite-element axis starting at zero. - Parameters - ---------- - edges : sequence of float - Candidate element boundaries in m, beginning with zero; must be a tuple. - orders : sequence of int - Candidate points per element, each between 2 and 64; must be a tuple. + - ``edges``: candidate element boundaries in m, beginning with zero; must be a + tuple. + - ``orders``: candidate points per element, each between 2 and 64; must be a tuple. """ if type(edges) is not tuple or type(orders) is not tuple: raise MaterialError("radial_edges and radial_orders must be tuples") @@ -834,13 +803,7 @@ def _validate_records(self) -> None: raise MaterialError("consistency must be a TDSEConsistency record") def _validate_hacc(self, basis: HaccBasis) -> None: - """Apply the whole-atom rules of SEP 20 section 5.1 to a haCC response. - - Parameters - ---------- - basis : HaccBasis - The response's validated haCC basis. - """ + """Apply the whole-atom rules of SEP 20 section 5.1 to a haCC response.""" if self.kerr_model != "tdse_native": raise MaterialError( f"haCC refuses kerr_model={self.kerr_model!r}: the provider has no " From cf2bf9e5fca8462d371eb84e690275dfa05ed95d Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:17:46 +0000 Subject: [PATCH 23/50] Slim private docstrings in solprop.recipe Mechanical: uv run python tools/docstring_diet.py src/solprop/recipe --write --check --report --rename-closures Co-Authored-By: Claude Opus 5.5 --- src/solprop/recipe/core.py | 279 ++---------------- src/solprop/recipe/document.py | 74 +---- src/solprop/recipe/export.py | 119 +------- src/solprop/recipe/metadata.py | 477 +++---------------------------- src/solprop/recipe/paths.py | 92 +----- src/solprop/recipe/pins.py | 238 ++------------- src/solprop/recipe/preview.py | 97 ++----- src/solprop/recipe/provenance.py | 53 +--- src/solprop/recipe/quantity.py | 159 +---------- src/solprop/recipe/registry.py | 170 +---------- 10 files changed, 167 insertions(+), 1591 deletions(-) diff --git a/src/solprop/recipe/core.py b/src/solprop/recipe/core.py index 8a00c2ff..e2cc1a93 100644 --- a/src/solprop/recipe/core.py +++ b/src/solprop/recipe/core.py @@ -441,11 +441,8 @@ def __eq__(self, other: object) -> bool: def _canonical_document(self) -> Document: """Return the tables with each quantity spelled in its display unit. - Returns - ------- - dict - The document a canonical save writes, so that two spellings of one value, - ``"1 cm"`` and ``0.01``, compare equal. + - Returns: the document a canonical save writes, so that two spellings of one + value, ``"1 cm"`` and ``0.01``, compare equal. """ document = self.to_dict() table = self._table() @@ -510,23 +507,11 @@ def set(self, path: str, value: object) -> None: # ------------------------------------------------------------------ checking def _table(self) -> Mapping[str, Parameter]: - """Return the route's parameter metadata. - - Returns - ------- - collections.abc.Mapping - :class:`~solprop.recipe.metadata.Parameter` by keyword. - """ + """Return the route's parameter metadata.""" return parameters(self.route) def _check_keys(self) -> None: - """Refuse a build or execution key the route does not have. - - Raises - ------ - RecipeError - Naming the key and the nearest valid one. - """ + """Refuse a build or execution key the route does not have.""" table = self._table() for key in self._document["build"]: if key in SETTINGS_KEYWORDS and self.route in OBJECT_ROUTES: @@ -544,22 +529,7 @@ def _check_keys(self) -> None: ) def _decode(self, name: str, value: object, context: BuildContext) -> object: - """Return one build keyword's value as the constructor takes it. - - Parameters - ---------- - name : str - The keyword. - value : object - Its value as written. - context : BuildContext - The data files' directory and the route's grid. - - Returns - ------- - object - The decoded value. - """ + """Return one build keyword's value as the constructor takes it.""" table = self._table() if name not in table: raise unknown_key( @@ -669,24 +639,13 @@ def _grid_first(self) -> bool: An object route takes its grid as a keyword of its own. A guided route builds its grid when it is called, and what needs that grid is decoded in a second pass, except a guide, which the route cannot be called without. - - Returns - ------- - bool - True for an object route, or a ``provider`` built on the grid. """ return self.route in OBJECT_ROUTES or _needs_grid( self._document["build"].get("provider") ) def _missing(self) -> list[str]: - """Return the route's required keywords this recipe neither sets nor pins. - - Returns - ------- - list of str - Their names, in the route's order. - """ + """Return the route's required keywords this recipe neither sets nor pins.""" pinned = self._document.get("pins", {}) return [ name @@ -703,11 +662,6 @@ def _require_keywords(self) -> None: cannot hold against a parameter. Called on the recipe as written: the copies that solve its pins have dropped ``[pins]``, so a pinned keyword looks missing on them. - - Raises - ------ - RecipeError - Naming every missing keyword, its ``parameter`` the first of them. """ missing = self._missing() if len(missing) == 1: @@ -905,18 +859,7 @@ def preview_input(self) -> "InputPreview": return preview_input(self) def _copy(self, drop: tuple[str, ...]) -> "Recipe": - """Return a copy without some top-level tables, its data files kept. - - Parameters - ---------- - drop : tuple of str - The tables to leave out. - - Returns - ------- - Recipe - The copy. - """ + """Return a copy without some top-level tables, its data files kept.""" document = { name: table for name, table in self.to_dict().items() if name not in drop } @@ -926,18 +869,7 @@ def _copy(self, drop: tuple[str, ...]) -> "Recipe": return recipe def _solve(self, pin: "Pin") -> "DesignRoots": - """Solve one pin on this (unpinned) recipe's keywords. - - Parameters - ---------- - pin : solprop.recipe.pins.Pin - The pin. - - Returns - ------- - solprop.tools.DesignRoots - Its roots. - """ + """Solve one pin on this (unpinned) recipe's keywords.""" from solprop.recipe.pins import ( # noqa: PLC0415 - cycle design_target, solve_pin, @@ -1000,10 +932,7 @@ def record( def _execution(self) -> dict[str, object]: """Return the ``[execution]`` table's values as the settings take them. - Returns - ------- - dict - The values, ``"none"`` read as ``None`` where a keyword takes it. + - Returns: the values, ``"none"`` read as ``None`` where a keyword takes it. """ table = parameters("execution") out: dict[str, object] = {} @@ -1117,25 +1046,7 @@ def scan(self) -> "RecipeScan": return RecipeScan(scan, builder, keywords) def _axis(self, path: str, values: object) -> list[object]: - """Return a scan axis's values, read in the unit of the path's keyword. - - Parameters - ---------- - path : str - The parameter path. - values : object - A list, or ``{start, stop, num, spacing}``. - - Returns - ------- - list - The values. - - Raises - ------ - RecipeError - If the path's keyword is not the route's, or the values do not read. - """ + """Return a scan axis's values, read in the unit of the path's keyword.""" steps = parse_path(path) keyword = format_path(steps[:1]) table = self._table() @@ -1169,16 +1080,8 @@ def _axis(self, path: str, values: object) -> list[object]: def _unit_at(self, path: str) -> str | None: """Return the unit of the keyword a path ends on. - Parameters - ---------- - path : str - The parameter path. - - Returns - ------- - str or None - The unit of a top-level keyword's number form, or of a registered type's - keyword; ``None`` for a value without one. + - Returns: the unit of a top-level keyword's number form, or of a registered + type's keyword; ``None`` for a value without one. """ steps = parse_path(path) if len(steps) == 1: @@ -1210,18 +1113,7 @@ def to_text(self) -> str: return dump_text(self._canonical_document()) def _context(self, grid: "TimeGrid | None" = None) -> BuildContext: - """Return the context the recipe's values are decoded in. - - Parameters - ---------- - grid : solprop.grid.TimeGrid or None, optional - The route's grid, for a value built on it. - - Returns - ------- - BuildContext - The data files' directory, the embedded files and the grid. - """ + """Return the context the recipe's values are decoded in.""" return BuildContext( base=self._base, allow_outside=self._allow_outside, @@ -1279,18 +1171,7 @@ def to_python(self, output: str | None = None) -> str: def _deferred(context: BuildContext) -> BuildContext: - """Return a context for checking values built on the grid before it exists. - - Parameters - ---------- - context : BuildContext - The recipe's context. - - Returns - ------- - BuildContext - The same, with a stand-in grid, so a factory on the grid can be checked. - """ + """Return a context for checking values built on the grid before it exists.""" from solprop.grid import EnvelopeGrid # noqa: PLC0415 - only for the stand-in grid = EnvelopeGrid.from_limits(1.0, 800e-9, (400e-9, 2e-6), 200e-15) @@ -1300,15 +1181,7 @@ def _deferred(context: BuildContext) -> BuildContext: def _needs_grid(value: object) -> bool: """Return whether a value holds a factory built on the route's grid. - Parameters - ---------- - value : object - A value as written. - - Returns - ------- - bool - True if a table in it names a factory whose context is the grid. + - Returns: True if a table in it names a factory whose context is the grid. """ if isinstance(value, Mapping): factory = REGISTRY.get(str(value.get("type"))) @@ -1323,18 +1196,8 @@ def _needs_grid(value: object) -> bool: def _issue(error: SolpropError, keyword: str | None = None) -> RecipeIssue: """Return the issue a refusal makes. - Parameters - ---------- - error : solprop.exceptions.SolpropError - The refusal. - keyword : str or None, optional - The build keyword whose value was refused, named when the refusal names no - recipe path of its own. - - Returns - ------- - RecipeIssue - Its message, parameter and rule, as an error. + - ``keyword``: the build keyword whose value was refused, named when the refusal + names no recipe path of its own. """ if isinstance(error, RecipeError): return RecipeIssue(error.message, error.parameter, "error", error.rule) @@ -1343,25 +1206,7 @@ def _issue(error: SolpropError, keyword: str | None = None) -> RecipeIssue: def _plain(value: object, path: str) -> object: - """Return a value as a recipe holds it, refusing an object or a callable. - - Parameters - ---------- - value : object - A Python value. - path : str - Its path, for the refusal. - - Returns - ------- - object - The value, with tuples as lists and complex numbers as ``{re, im}``. - - Raises - ------ - RecipeError - If the value is an object or a callable. - """ + """Return a value as a recipe holds it, refusing an object or a callable.""" if value is None: return NONE if isinstance(value, bool | int | float | str): @@ -1383,19 +1228,8 @@ def _plain(value: object, path: str) -> object: def _canonical(entry: Parameter, value: object) -> object: """Spell a value in its keyword's display unit, for a canonical recipe. - Parameters - ---------- - entry : Parameter - The keyword's metadata. - value : object - The value as written. - - Returns - ------- - object - A quantity string in the display unit for a number of a keyword that has - one, element by element for a list; a two-row list and anything else as it - stands. + - Returns: a quantity string in the display unit for a number of a keyword that has + one, element by element for a list; a two-row list and anything else as it stands. """ unit = next((form.unit for form in entry.forms if form.unit is not None), None) if entry.display is None or unit is None: @@ -1415,20 +1249,7 @@ def _canonical(entry: Parameter, value: object) -> object: def _renamed(value: object, names: Mapping[str, str]) -> object: - """Return a value with its data-file references renamed. - - Parameters - ---------- - value : object - A value as the recipe writes it. - names : collections.abc.Mapping - The new name of each file, by the name the recipe refers to it by. - - Returns - ------- - object - A copy, each ``file`` that `names` holds replaced. - """ + """Return a value with its data-file references renamed.""" if isinstance(value, Mapping): out = {key: _renamed(item, names) for key, item in value.items()} if "sha256" in out and isinstance(out.get("file"), str): @@ -1445,15 +1266,7 @@ def _with_pulse_placeholders(document: Document) -> dict[str, object]: A pulse in ``pulses`` whose energy is pinned cannot be built without one, and the pin is solved only after the values are checked. - Parameters - ---------- - document : dict - The recipe's tables. - - Returns - ------- - dict - The ``[build]`` table, unchanged where no pulse pin is unset. + - Returns: the ``[build]`` table, unchanged where no pulse pin is unset. """ from solprop.recipe.pins import PULSE_PINS # noqa: PLC0415 - cycle @@ -1470,43 +1283,14 @@ def _with_pulse_placeholders(document: Document) -> dict[str, object]: def _placeholder(path: str) -> float: - """Return the value a pulse pin stands at while the guide is solved. - - Parameters - ---------- - path : str - The pulse pin's path. - - Returns - ------- - float - Its placeholder, in SI. - """ + """Return the value a pulse pin stands at while the guide is solved.""" from solprop.recipe.pins import placeholder # noqa: PLC0415 - cycle return placeholder(path) def _pick(pin: "Pin", roots: "DesignRoots") -> float: - """Return a pin's chosen root, an unmet target refused on its path. - - Parameters - ---------- - pin : solprop.recipe.pins.Pin - The pin. - roots : solprop.tools.DesignRoots - Its roots. - - Returns - ------- - float - The root its ``root`` key chooses. - - Raises - ------ - RecipeError - Naming the attainable range when there is no root, or an index past them. - """ + """Return a pin's chosen root, an unmet target refused on its path.""" try: return roots.pick(pin.root) except SolpropError as error: @@ -1575,19 +1359,8 @@ def resume_file( def _number_in(value: object, unit: str | None, path: str) -> object: """Return one scan value as a number in its unit, or as it stands. - Parameters - ---------- - value : object - The value as written. - unit : str or None - The keyword's unit. - path : str - Its path. - - Returns - ------- - object - A float in `unit` for a number or quantity string; anything else unchanged. + - Returns: a float in `unit` for a number or quantity string; anything else + unchanged. """ if ( unit is None diff --git a/src/solprop/recipe/document.py b/src/solprop/recipe/document.py index 026705dd..f70db2d7 100644 --- a/src/solprop/recipe/document.py +++ b/src/solprop/recipe/document.py @@ -151,20 +151,7 @@ def unknown_key( def _location(table: str, keys: Sequence[str | int]) -> str: - """Write where a value is, as ``[table] key.sub[0]``, for an error message. - - Parameters - ---------- - table : str - The top-level table. - keys : sequence of str or int - The keys and indices from the table to the value. - - Returns - ------- - str - The location. - """ + """Write where a value is, as ``[table] key.sub[0]``, for an error message.""" parts: list[str] = [] for key in keys: if isinstance(key, int): @@ -179,20 +166,7 @@ def _location(table: str, keys: Sequence[str | int]) -> str: def _check_values(node: object, table: str, keys: tuple[str | int, ...]) -> None: """Refuse a value that is not a recipe value: a date, a wide integer, an object. - Parameters - ---------- - node : object - The value, checked with everything inside it. - table : str - The top-level table it is in, for the error. - keys : tuple of str or int - The keys and indices from the table to it, for the error. - - Raises - ------ - RecipeError - If ``node`` or a value inside it is not a string, boolean, 64-bit integer, - float, table or array, or a table has a key that is not a string. + - ``keys``: the keys and indices from the table to it, for the error. """ match node: case dict(): @@ -264,19 +238,7 @@ def upgrade(document: Mapping[str, Mapping[str, object]]) -> Document: def _check_format(document: Mapping[str, object]) -> None: - """Check ``[recipe] format`` first, so a newer recipe is refused as newer. - - Parameters - ---------- - document : Mapping - The document. - - Raises - ------ - RecipeError - If ``[recipe]`` or its format is missing, the format is not an integer, or - it is newer than :data:`FORMAT` or below 1. - """ + """Check ``[recipe] format`` first, so a newer recipe is refused as newer.""" table = document.get("recipe") if table is None: raise RecipeError("a recipe needs a [recipe] table with format and route") @@ -299,15 +261,7 @@ def _check_format(document: Mapping[str, object]) -> None: def _check_recipe_table(table: Mapping[str, object]) -> None: """Check ``[recipe]``'s keys and their types; the format is already checked. - Parameters - ---------- - table : Mapping - The ``[recipe]`` table. - - Raises - ------ - RecipeError - If a key is unknown, ``route`` is missing, or a value is not a string. + - ``table``: the ``[recipe]`` table. """ for key in table: if key not in RECIPE_KEYS: @@ -507,24 +461,8 @@ def encode_none(value: object | None) -> object: def _real(value: object, part: str, path: str | None) -> float: """Read one part of a complex number. - Parameters - ---------- - value : object - The part, an ``int`` or ``float``. - part : str - ``"real"`` or ``"imaginary"``, for the error. - path : str or None - The parameter path, for the error. - - Returns - ------- - float - The part. - - Raises - ------ - RecipeError - If ``value`` is a boolean or not a number. + - ``value``: the part, an ``int`` or ``float``. + - ``part``: ``"real"`` or ``"imaginary"``, for the error. """ if isinstance(value, bool) or not isinstance(value, int | float): raise RecipeError(f"the {part} part {value!r} is not a number", path=path) diff --git a/src/solprop/recipe/export.py b/src/solprop/recipe/export.py index c4192dd9..1dfaea4a 100644 --- a/src/solprop/recipe/export.py +++ b/src/solprop/recipe/export.py @@ -34,13 +34,7 @@ class _Writer: - """Render recipe values as Python expressions, and collect what they import. - - Parameters - ---------- - recipe : Recipe - The recipe being written. - """ + """Render recipe values as Python expressions, and collect what they import.""" def __init__(self, recipe: "Recipe") -> None: """Start with no imports.""" @@ -50,42 +44,13 @@ def __init__(self, recipe: "Recipe") -> None: self.grid: str | None = None def need(self, module: str, name: str) -> None: - """Record an import. - - Parameters - ---------- - module : str - The module. - name : str - The name it provides. - """ + """Record an import.""" self.imports.setdefault(module, set()).add(name) def expression( # noqa: PLR0911 - one return per kind of value self, value: object, unit: UnitSpec, path: str ) -> str: - """Return a value as a Python expression. - - Parameters - ---------- - value : object - The value as the recipe writes it. - unit : str, callable or None - Its unit. - path : str - Its parameter path. - - Returns - ------- - str - The expression, numbers in SI. - - Raises - ------ - RecipeError - For a value built on the route's grid, which the script cannot name - before the route has built it. - """ + """Return a value as a Python expression.""" if isinstance(value, Mapping): if "type" in value: return self.factory(value, path) @@ -113,22 +78,7 @@ def expression( # noqa: PLR0911 - one return per kind of value def factory(self, table: Mapping[str, object], path: str) -> str: """Return a registered type's call. - Parameters - ---------- - table : collections.abc.Mapping - The table with its ``type``. - path : str - Its parameter path. - - Returns - ------- - str - The call, with the name imported. - - Raises - ------ - RecipeError - For a factory built on the route's grid. + - ``table``: the table with its ``type``. """ factory = REGISTRY[str(table["type"])] if factory.context and self.grid is None: @@ -160,18 +110,8 @@ def factory(self, table: Mapping[str, object], path: str) -> str: def _comment(value: object, unit: UnitSpec) -> str: """Return the comment that states a quantity as the recipe wrote it. - Parameters - ---------- - value : object - The value as written. - unit : str, callable or None - Its unit; a value without one gets no comment. - - Returns - ------- - str - ``" # 125 um"`` for a quantity string, or a list holding one; empty - otherwise. + - Returns: ``" # 125 um"`` for a quantity string, or a list holding one; empty + otherwise. """ if unit is None: return "" @@ -277,14 +217,7 @@ class _Pinned: keywords: list[str] def __bool__(self) -> bool: - """Return whether there are pins. - - Returns - ------- - bool - Whether any line is written: a solver call, or the comment of a pin - written at its value. - """ + """Return whether there are pins.""" return bool(self.lines) @@ -299,18 +232,6 @@ def _pins( the keywords with the guide's value. A pin on one of ``pulses`` is written at the value it solves to now, with the pin in a comment: the pulse object cannot be built without its energy. - - Parameters - ---------- - recipe : Recipe - The recipe. - writer : _Writer - The writer, which collects the imports. - - Returns - ------- - _Pinned - The lines. """ from solprop.recipe.pins import read_pins # noqa: PLC0415 - cycle @@ -362,17 +283,8 @@ def _array_pins( ) -> dict[str, object]: """Return ``[build]`` with each pin on one of ``pulses`` set to its solved value. - Parameters - ---------- - recipe : Recipe - The recipe. - build : dict - Its ``[build]`` table. - - Returns - ------- - dict - The table, unchanged when no such pin exists. + - ``build``: its ``[build]`` table. + - Returns: the table, unchanged when no such pin exists. """ from solprop.recipe.paths import parse_path, set_path # noqa: PLC0415 - cycle @@ -386,18 +298,7 @@ def _array_pins( def _imports(writer: _Writer) -> list[str]: - """Return the script's import lines: plain imports first, then from-imports. - - Parameters - ---------- - writer : _Writer - The writer that collected the names. - - Returns - ------- - list of str - The lines, each group sorted by module. - """ + """Return the script's import lines: plain imports first, then from-imports.""" plain = [ f"import {module}" for module in sorted(writer.imports) diff --git a/src/solprop/recipe/metadata.py b/src/solprop/recipe/metadata.py index 0e5359b3..19bc0ac9 100644 --- a/src/solprop/recipe/metadata.py +++ b/src/solprop/recipe/metadata.py @@ -96,13 +96,7 @@ class _Required: """The default of a keyword that has none: the caller must give it.""" def __repr__(self) -> str: - """Return the sentinel's name. - - Returns - ------- - str - ``"REQUIRED"``. - """ + """Return the sentinel's name.""" return "REQUIRED" @@ -261,16 +255,9 @@ class Parameter: def _precision(values: Mapping[str, object]) -> str: """Return the precision a recipe asks for. - Parameters - ---------- - values : collections.abc.Mapping - The recipe's values; on an object route the precision is a keyword of its - ``settings`` table. - - Returns - ------- - str - ``"double"`` unless the recipe sets ``"single"``. + - ``values``: the recipe's values; on an object route the precision is a keyword of + its ``settings`` table. + - Returns: ``"double"`` unless the recipe sets ``"single"``. """ settings = values.get("settings") if isinstance(settings, Mapping): @@ -285,15 +272,7 @@ def _precision(values: Mapping[str, object]) -> str: def _is_slab(value: object) -> bool: """Return whether a ``modes`` value, as a recipe or a script gives it, holds a slab. - Parameters - ---------- - value : object - A table with a ``type``, a list of them, or the mode objects themselves. - - Returns - ------- - bool - True when any element is a slab mode or a collection of them. + - Returns: True when any element is a slab mode or a collection of them. """ if isinstance(value, Mapping): return value.get("type") in _SLAB_TYPES @@ -342,15 +321,7 @@ def per_unit_width(route: str, values: Mapping[str, object]) -> bool: def _slab_note(route: str) -> str: """Return what a route's energy summary adds for slab sets. - Parameters - ---------- - route : str - The route. - - Returns - ------- - str - `` (per metre of y on a slab set)`` on the waveguide route, else nothing. + - Returns: `` (per metre of y on a slab set)`` on the waveguide route, else nothing. """ return " (per metre of y on a slab set)" if route == "waveguide" else "" @@ -358,18 +329,8 @@ def _slab_note(route: str) -> str: def _row(route: str, values: Mapping[str, object]) -> str: """Return the defaults-table row a recipe reads. - Parameters - ---------- - route : str - The keyword route. - values : collections.abc.Mapping - The recipe's values. - - Returns - ------- - str - ``"capillary tdse"`` for a capillary beside a TDSE response, and - ``"waveguide slab"`` for a waveguide of slab modes; the route otherwise. + - Returns: ``"capillary tdse"`` for a capillary beside a TDSE response, and + ``"waveguide slab"`` for a waveguide of slab modes; the route otherwise. """ if route == "capillary" and values.get("tdse") is not None: return "capillary tdse" @@ -379,32 +340,10 @@ def _row(route: str, values: Mapping[str, object]) -> str: def _rtol(route: str) -> Resolver: - """Return the resolver of a route's ``rtol``. - - Parameters - ---------- - route : str - The keyword route. - - Returns - ------- - callable - The resolver. - """ + """Return the resolver of a route's ``rtol``.""" def resolve(values: Mapping[str, object]) -> Resolution: - """Return the route's default in the recipe's precision. - - Parameters - ---------- - values : collections.abc.Mapping - The recipe's values. - - Returns - ------- - Resolution - The tolerance and its rule. - """ + """Return the route's default in the recipe's precision.""" row = _row(route, values) precision = _precision(values) return Resolution( @@ -417,32 +356,10 @@ def resolve(values: Mapping[str, object]) -> Resolution: def _integrator(route: str) -> Resolver: - """Return the resolver of a route's ``integrator``. - - Parameters - ---------- - route : str - The keyword route. - - Returns - ------- - callable - The resolver. - """ + """Return the resolver of a route's ``integrator``.""" def resolve(values: Mapping[str, object]) -> Resolution: - """Return the route's default method in the recipe's precision. - - Parameters - ---------- - values : collections.abc.Mapping - The recipe's values. - - Returns - ------- - Resolution - The integrator and its rule. - """ + """Return the route's default method in the recipe's precision.""" row = _row(route, values) precision = _precision(values) rule = ( @@ -456,31 +373,12 @@ def resolve(values: Mapping[str, object]) -> Resolution: def _noise(route: str) -> Resolver: - """Return the resolver of a route's ``noise="default"``. - - Parameters - ---------- - route : str - The keyword route. - - Returns - ------- - callable - The resolver. - """ + """Return the resolver of a route's ``noise="default"``.""" def resolve(values: Mapping[str, object]) -> Resolution: """Return the model the default draws, and why. - Parameters - ---------- - values : collections.abc.Mapping - The recipe's values. - - Returns - ------- - Resolution - The model (``None`` for none) and the reason. + - Returns: the model (``None`` for none) and the reason. """ row = _row(route, values) model, resolution = resolve_noise(row, "default", None, _precision(values)) @@ -493,18 +391,7 @@ def resolve(values: Mapping[str, object]) -> Resolution: def _scheme(values: Mapping[str, object]) -> Resolution: - """Return the collar scheme a keyword route takes when none is named. - - Parameters - ---------- - values : collections.abc.Mapping - The recipe's values. - - Returns - ------- - Resolution - The scheme and its rule. - """ + """Return the collar scheme a keyword route takes when none is named.""" del values return Resolution( default_scheme("gnlse", interpolate_planes=False), @@ -515,30 +402,11 @@ def _scheme(values: Mapping[str, object]) -> Resolution: def _by_envelope(name: str) -> Resolver: """Return the resolver of a keyword that defaults to the opposite of ``envelope``. - Parameters - ---------- - name : str - ``"thg"`` or ``"plasma"``. - - Returns - ------- - callable - The resolver. + - ``name``: ``"thg"`` or ``"plasma"``. """ def resolve(values: Mapping[str, object]) -> Resolution: - """Return on for a field grid, off for an envelope grid. - - Parameters - ---------- - values : collections.abc.Mapping - The recipe's values. - - Returns - ------- - Resolution - The switch and its rule. - """ + """Return on for a field grid, off for an envelope grid.""" envelope = bool(values.get("envelope", False)) return Resolution( not envelope, @@ -551,15 +419,7 @@ def resolve(values: Mapping[str, object]) -> Resolution: def _step_planes(values: Mapping[str, object]) -> Resolution: """Return the capillary's ``step_planes`` default. - Parameters - ---------- - values : collections.abc.Mapping - The recipe's values. - - Returns - ------- - Resolution - 1 beside a TDSE response, else 0. + - Returns: 1 beside a TDSE response, else 0. """ tdse = values.get("tdse") is not None return Resolution( @@ -571,15 +431,7 @@ def _step_planes(values: Mapping[str, object]) -> Resolution: def _requested_planes(values: Mapping[str, object]) -> Resolution: """Return the ``step_planes`` default of the GNLSE and the waveguide. - Parameters - ---------- - values : collections.abc.Mapping - The recipe's values. - - Returns - ------- - Resolution - 0: the requested planes only. + - Returns: 0: the requested planes only. """ del values return Resolution(0, "the requested planes only") @@ -588,16 +440,8 @@ def _requested_planes(values: Mapping[str, object]) -> Resolution: def _raman(values: Mapping[str, object]) -> Resolution: """Return the capillary's ``raman`` default, which depends on the species. - Parameters - ---------- - values : collections.abc.Mapping - The recipe's values. - - Returns - ------- - Resolution - The rule; the value is ``None`` here, because it is decided by building the - fill: on for a molecular gas whose Raman data are curated, off otherwise. + - Returns: the rule; the value is ``None`` here, because it is decided by building + the fill: on for a molecular gas whose Raman data are curated, off otherwise. """ del values return Resolution( @@ -610,15 +454,7 @@ def _raman(values: Mapping[str, object]) -> Resolution: def _pressure(values: Mapping[str, object]) -> Resolution: """Return the capillary's default pressure. - Parameters - ---------- - values : collections.abc.Mapping - The recipe's values. - - Returns - ------- - Resolution - One bar. + - Returns: one bar. """ del values return Resolution(1.0, "one bar when no pressure is given") @@ -643,15 +479,7 @@ def _length_fraction(fraction: float, text: str) -> Resolver: def resolve(values: Mapping[str, object]) -> Resolution: """Return the fraction of the recipe's length. - Parameters - ---------- - values : collections.abc.Mapping - The recipe's values. - - Returns - ------- - Resolution - The step, m, or ``None`` without a length, and the rule. + - Returns: the step, m, or ``None`` without a length, and the rule. """ length = values.get("length") value = None if not isinstance(length, int | float) else fraction * length @@ -711,18 +539,7 @@ def resolve(values: Mapping[str, object]) -> Resolution: def _shared(route: str) -> tuple[Parameter, ...]: - """Return the keywords the keyword routes share, with the route's rules. - - Parameters - ---------- - route : str - The route, one of :data:`solprop.recipe.core.ROUTES`. - - Returns - ------- - tuple of Parameter - The shared entries. - """ + """Return the keywords the keyword routes share, with the route's rules.""" return ( Parameter( "length", @@ -1010,13 +827,7 @@ def _shared(route: str) -> tuple[Parameter, ...]: def _gnlse() -> tuple[Parameter, ...]: - """Return the GNLSE route's own keywords. - - Returns - ------- - tuple of Parameter - The entries not shared with the capillary. - """ + """Return the GNLSE route's own keywords.""" page = "guide/waveguides/gnlse.md" return ( Parameter( @@ -1114,13 +925,7 @@ def _gnlse() -> tuple[Parameter, ...]: def _capillary() -> tuple[Parameter, ...]: - """Return the capillary route's own keywords. - - Returns - ------- - tuple of Parameter - The entries not shared with the GNLSE. - """ + """Return the capillary route's own keywords.""" page = "guide/waveguides/capillaries.md" ionisation = "guide/media/ionisation.md" return ( @@ -1450,11 +1255,6 @@ def _waveguide() -> tuple[Parameter, ...]: The fill, the responses and the transverse rule are the capillary's entries (:func:`_route_entries`); these are the ones the waveguide has alone or reads differently. - - Returns - ------- - tuple of Parameter - The entries not shared with the capillary. """ supplied = "guide/waveguides/supplied-guides.md" provider = f"{supplied}#a-guide-instead-of-a-fill" @@ -1592,16 +1392,6 @@ def _free_space(route: str) -> tuple[Parameter, ...]: The noise, seed, material selection and boundary check are the shared entries (:func:`_route_entries`); the absorber and its scheme are keywords of the ``settings`` table (:data:`SETTINGS_KEYWORDS`). - - Parameters - ---------- - route : {'radial', 'free', 'planar'} - The route. - - Returns - ------- - tuple of Parameter - The entries not shared with the keyword routes. """ beams = "guide/free-space/beams.md" planar = "guide/waveguides/planar.md#resolving-y" @@ -1716,16 +1506,6 @@ def _route_entries(route: str) -> tuple[Parameter, ...]: The waveguide takes the capillary's entries for the fill, the responses and the rule, under its own; an entry the route's constructor does not name is dropped by :func:`parameters`. - - Parameters - ---------- - route : str - The route, already checked by :func:`surface`. - - Returns - ------- - tuple of Parameter - The entries, without defaults, the first of each name winning. """ layers = { "gnlse": (_gnlse(),), @@ -1804,72 +1584,24 @@ def _number( maximum: float | None = None, exclusive: bool = False, ) -> Form: - """Return a number's form. - - Parameters - ---------- - unit : str or None, optional - Its unit. - minimum, maximum : float or None, optional - Its range. - exclusive : bool, optional - Whether the range's ends are excluded. - - Returns - ------- - Form - The form. - """ + """Return a number's form.""" return Form( "number", unit=unit, minimum=minimum, maximum=maximum, exclusive=exclusive ) def _integer(minimum: float | None = None, maximum: float | None = None) -> Form: - """Return an integer's form. - - Parameters - ---------- - minimum, maximum : float or None, optional - Its range, inclusive. - - Returns - ------- - Form - The form. - """ + """Return an integer's form.""" return Form("integer", minimum=minimum, maximum=maximum) def _choice(*choices: str) -> Form: - """Return the form of one of fixed strings. - - Parameters - ---------- - *choices : str - The strings. - - Returns - ------- - Form - The form. - """ + """Return the form of one of fixed strings.""" return Form("choice", choices=choices) def _object(*types: str) -> Form: - """Return the form of a registered type. - - Parameters - ---------- - *types : str - The types it may name. - - Returns - ------- - Form - The form. - """ + """Return the form of a registered type.""" return Form("object", types=types) @@ -1902,26 +1634,7 @@ def _keyword( *, display: str | None = None, ) -> tuple[str, tuple[Form, ...], Level, str, str | None]: - """Return one keyword's row, its group and anchor left to its type. - - Parameters - ---------- - name : str - The keyword. - forms : Form or tuple of Form - Its forms. - level : str - A :data:`~solprop.recipe.metadata.Level`. - summary : str - One line. - display : str or None, optional - The unit it is shown in. - - Returns - ------- - tuple - The row. - """ + """Return one keyword's row, its group and anchor left to its type.""" return ( name, forms if isinstance(forms, tuple) else (forms,), @@ -1935,22 +1648,7 @@ def _keyword( def _type(group: Group, anchor: str, *rows: Row) -> tuple[Parameter, ...]: - """Return a type's entries, each with the type's group and anchor. - - Parameters - ---------- - group : str - Where the type is shown. - anchor : str - Its manual section. - *rows : tuple - Its keywords (:func:`_keyword`). - - Returns - ------- - tuple of Parameter - The entries. - """ + """Return a type's entries, each with the type's group and anchor.""" return tuple( Parameter(name, forms, group, level, summary, anchor, display=display) for name, forms, level, summary, display in rows @@ -1971,18 +1669,7 @@ def _type(group: Group, anchor: str, *rows: Row) -> tuple[Parameter, ...]: def _pulse(*extra: Row) -> tuple[Parameter, ...]: - """Return a shaped pulse's entries: the shared keywords and its own. - - Parameters - ---------- - *extra : tuple - Its own keywords' rows. - - Returns - ------- - tuple of Parameter - The entries. - """ + """Return a shaped pulse's entries: the shared keywords and its own.""" return _type( "input", _TYPE_PULSE_PAGE, @@ -2019,18 +1706,7 @@ def _pulse(*extra: Row) -> tuple[Parameter, ...]: def _gradient(*first: Row) -> tuple[Parameter, ...]: - """Return a pressure gradient's entries: its stages and the flow keywords. - - Parameters - ---------- - *first : tuple - The rows that give its stages. - - Returns - ------- - tuple of Parameter - The entries. - """ + """Return a pressure gradient's entries: its stages and the flow keywords.""" return _type( "medium", f"{_PROFILE_PAGE}#steady-flow-fills", @@ -2060,13 +1736,7 @@ def _gradient(*first: Row) -> tuple[Parameter, ...]: def _fill_rows() -> tuple[Row, ...]: - """Return the keywords a gas fill shares: its pressure path and the flow. - - Returns - ------- - tuple - The rows. - """ + """Return the keywords a gas fill shares: its pressure path and the flow.""" return ( _keyword( "floor", @@ -2117,15 +1787,7 @@ def _fill_rows() -> tuple[Row, ...]: def _grid(*rows: Row) -> tuple[Parameter, ...]: """Return a time grid's entries: the window, the band and the sampling. - Parameters - ---------- - *rows : tuple - The rows between the sample spacing and the transform sizes. - - Returns - ------- - tuple of Parameter - The entries. + - ``*rows``: the rows between the sample spacing and the transform sizes. """ return _type( "grid", @@ -2182,18 +1844,7 @@ def _grid(*rows: Row) -> tuple[Parameter, ...]: def _hollow_rows(cladding: str) -> tuple[Row, ...]: - """Return a hollow mode's loss and cladding keywords. - - Parameters - ---------- - cladding : str - What the cladding is called. - - Returns - ------- - tuple - The rows. - """ + """Return a hollow mode's loss and cladding keywords.""" return ( _keyword( "loss", @@ -2211,18 +1862,7 @@ def _hollow_rows(cladding: str) -> tuple[Row, ...]: def _slab_fill(temperature: Form | tuple[Form, ...]) -> tuple[Row, ...]: - """Return a slab channel's own fill, which the planar route reads. - - Parameters - ---------- - temperature : Form or tuple of Form - The temperature's forms. - - Returns - ------- - tuple - The rows. - """ + """Return a slab channel's own fill, which the planar route reads.""" return ( _keyword("gas", _GAS, "basic", "The filling gas; none for vacuum."), _keyword( @@ -2246,18 +1886,7 @@ def _slab_fill(temperature: Form | tuple[Form, ...]) -> tuple[Row, ...]: def _beam(*extra: Row) -> tuple[Parameter, ...]: - """Return a transverse beam's entries. - - Parameters - ---------- - *extra : tuple - Its own keywords' rows. - - Returns - ------- - tuple of Parameter - The entries. - """ + """Return a transverse beam's entries.""" return _type( "input", f"{_TYPE_PULSE_PAGE}#transverse-beams", @@ -2307,18 +1936,7 @@ def _beam(*extra: Row) -> tuple[Parameter, ...]: def _plasma(*extra: Row) -> tuple[Parameter, ...]: - """Return a plasma response's entries. - - Parameters - ---------- - *extra : tuple - Its own keywords' rows. - - Returns - ------- - tuple of Parameter - The entries. - """ + """Return a plasma response's entries.""" return _type( "nonlinear", _IONISATION_PAGE, @@ -3648,20 +3266,7 @@ def parameters(route: str) -> Mapping[str, Parameter]: def _type_parameters( name: str, entries: Sequence[Parameter] ) -> Mapping[str, Parameter]: - """Return a registered type's keyword metadata, with its target's defaults. - - Parameters - ---------- - name : str - The type, as a recipe names it. - entries : collections.abc.Sequence of Parameter - Its entries (:data:`_TYPES`). - - Returns - ------- - collections.abc.Mapping - :class:`Parameter` by keyword, in the target's order. - """ + """Return a registered type's keyword metadata, with its target's defaults.""" from solprop.recipe.registry import REGISTRY # noqa: PLC0415 - a cycle factory = REGISTRY[name] diff --git a/src/solprop/recipe/paths.py b/src/solprop/recipe/paths.py index a18dc6c0..aa093115 100644 --- a/src/solprop/recipe/paths.py +++ b/src/solprop/recipe/paths.py @@ -87,19 +87,7 @@ class Key: def _refuse(text: str, column: int, reason: str) -> RecipeError: """Return the error for a path text that cannot be read. - Parameters - ---------- - text : str - The path text. - column : int - The zero-based position where reading stopped. - reason : str - What was expected there. - - Returns - ------- - RecipeError - The error, to raise; it gives the column counted from 1. + - ``column``: the zero-based position where reading stopped. """ return RecipeError( f"cannot read the parameter path {text!r} at column {column + 1}: {reason}" @@ -201,40 +189,13 @@ def format_path(steps: Sequence[Step]) -> str: def _steps(path: str | Sequence[Step]) -> tuple[tuple[Step, ...], str]: - """Return the steps of a path given as text or steps, and its canonical text. - - Parameters - ---------- - path : str or sequence of Attribute, Index or Key - The path. - - Returns - ------- - tuple - The steps, and the canonical text. - - Raises - ------ - RecipeError - If the path cannot be read or written. - """ + """Return the steps of a path given as text or steps, and its canonical text.""" steps = parse_path(path) if isinstance(path, str) else tuple(path) return steps, format_path(steps) def _step_text(step: Step) -> str: - """Write one step as it appears after the keyword. - - Parameters - ---------- - step : Attribute, Index or Key - The step. - - Returns - ------- - str - ``.name``, ``[i]`` or ``["key"]``. - """ + """Write one step as it appears after the keyword.""" if isinstance(step, Attribute): return f".{step.name}" if isinstance(step, Index): @@ -245,27 +206,8 @@ def _step_text(step: Step) -> str: def _child(node: object, step: Step, path: str, reached: Sequence[Step]) -> object: """Return the entry of a node that one step addresses. - Parameters - ---------- - node : object - The table or array the step is taken in. - step : Attribute, Index or Key - The step. - path : str - The whole path's canonical text, for the error. - reached : sequence of Attribute, Index or Key - The steps that led to ``node``; empty at the top, where the step is a - keyword. - - Returns - ------- - object - The entry. - - Raises - ------ - RecipeError - If ``node`` has no such entry, naming what was reached and the step. + - ``reached``: the steps that led to ``node``; empty at the top, where the step is a + keyword. """ if not reached: assert isinstance(step, Attribute) # parse_path and format_path ensure it @@ -340,28 +282,8 @@ def _replace( ) -> object: """Return a copy of a node with a value set at the end of some steps. - Parameters - ---------- - node : object - The table or array the steps start in. - steps : tuple of Attribute, Index or Key - The steps from ``node``, at least one. - value : object - The value to set. - path : str - The whole path's canonical text, for the error. - reached : tuple of Attribute, Index or Key - The steps that led to ``node``. - - Returns - ------- - object - The copy; the tables and arrays on the path are new and the rest shared. - - Raises - ------ - RecipeError - If a step before the last finds no entry, or an index is past the end. + - ``steps``: the steps from ``node``, at least one. + - ``reached``: the steps that led to ``node``. """ step, rest = steps[0], steps[1:] if rest: diff --git a/src/solprop/recipe/pins.py b/src/solprop/recipe/pins.py index 106f7a9f..4ad05c31 100644 --- a/src/solprop/recipe/pins.py +++ b/src/solprop/recipe/pins.py @@ -162,27 +162,7 @@ def read_pins(recipe: "Recipe") -> tuple[Pin, ...]: def _read_pin(recipe: "Recipe", path: str, entry: object) -> Pin: - """Return one ``[pins]`` entry, checked. - - Parameters - ---------- - recipe : Recipe - The recipe. - path : str - The entry's key, a parameter path. - entry : object - Its value, a table. - - Returns - ------- - Pin - The pin. - - Raises - ------ - RecipeError - On `path`, naming the rule broken. - """ + """Return one ``[pins]`` entry, checked.""" steps = parse_path(path) path = format_path(steps) if isinstance(steps[-1], Index): @@ -255,20 +235,7 @@ def _read_pin(recipe: "Recipe", path: str, entry: object) -> Pin: def _is_set(recipe: "Recipe", path: str) -> bool: - """Return whether a path is also set in ``[build]``. - - Parameters - ---------- - recipe : Recipe - The recipe. - path : str - The path. - - Returns - ------- - bool - Whether ``recipe.get(path)`` finds a value. - """ + """Return whether a path is also set in ``[build]``.""" try: recipe.get(path) except RecipeError, KeyError, IndexError, TypeError: @@ -279,15 +246,7 @@ def _is_set(recipe: "Recipe", path: str) -> bool: def _is_pulse_path(steps: tuple[object, ...]) -> bool: """Return whether a path names a pulse's energy or peak power. - Parameters - ---------- - steps : tuple - The parsed path. - - Returns - ------- - bool - For ``energy``, ``peak_power`` or ``pulses[i].energy`` and the like. + - Returns: for ``energy``, ``peak_power`` or ``pulses[i].energy`` and the like. """ # A path starts with a keyword (parse_path), so names[0] is that keyword. names = [step.name for step in steps if isinstance(step, Attribute)] @@ -302,27 +261,7 @@ def _is_pulse_path(steps: tuple[object, ...]) -> bool: def _read_key(value: object, key: str, path: str) -> object: - """Return a target key's value, a quantity in SI. - - Parameters - ---------- - value : object - The value as written. - key : str - The key. - path : str - The pin's path, for errors. - - Returns - ------- - object - A float in SI, or a string for a choice. - - Raises - ------ - RecipeError - If the value does not read in the key's unit. - """ + """Return a target key's value, a quantity in SI.""" unit = _UNITS[key] if unit is None: if key == "order": @@ -337,24 +276,8 @@ def _read_key(value: object, key: str, path: str) -> object: def _number(value: object, unit: str | None, path: str) -> float: """Return a number written plain or as a quantity. - Parameters - ---------- - value : object - A number, or a quantity string such as ``"800 nm"``. - unit : str or None - The unit it is read in; ``None`` for a plain number. - path : str - The pin's path, for errors. - - Returns - ------- - float - The number in `unit`. - - Raises - ------ - RecipeError - If it does not read, or is not finite. + - ``value``: a number, or a quantity string such as ``"800 nm"``. + - ``unit``: the unit it is read in; ``None`` for a plain number. """ if isinstance(value, bool): msg = f"expected a number, got {value!r}" @@ -376,24 +299,8 @@ def _number(value: object, unit: str | None, path: str) -> float: def _read_search(value: object, unit: str | None, path: str) -> tuple[float, float]: """Return a search range, read in the pinned keyword's unit. - Parameters - ---------- - value : object - ``[low, high]``. - unit : str or None - The pinned keyword's unit. - path : str - The pin's path. - - Returns - ------- - tuple of float - ``(low, high)``, ordered. - - Raises - ------ - RecipeError - If it is not two ordered numbers. + - ``value``: ``[low, high]``. + - Returns: ``(low, high)``, ordered. """ if not isinstance(value, list) or len(value) != 2: msg = "a pin's search is [low, high]" @@ -406,25 +313,7 @@ def _read_search(value: object, unit: str | None, path: str) -> tuple[float, flo def _read_root(value: object, path: str) -> "RootChoice": - """Return a root choice. - - Parameters - ---------- - value : object - ``"lowest"``, ``"highest"`` or an integer index. - path : str - The pin's path. - - Returns - ------- - {'lowest', 'highest'} or int - The choice. - - Raises - ------ - RecipeError - If it is none of these. - """ + """Return a root choice.""" if value in ("lowest", "highest"): return cast("Literal['lowest', 'highest']", value) if isinstance(value, int) and not isinstance(value, bool): @@ -665,16 +554,8 @@ def solve_pulse( def _route_pulse(keywords: Mapping[str, object]) -> object: """Return the pulse a route's own pulse keywords describe. - Parameters - ---------- - keywords : collections.abc.Mapping - The route's keywords: ``wavelength``, ``fwhm`` or ``width``, ``shape`` and - ``energy`` or ``peak_power``. - - Returns - ------- - GaussianPulse or SechPulse - The pulse. + - ``keywords``: the route's keywords: ``wavelength``, ``fwhm`` or ``width``, + ``shape`` and ``energy`` or ``peak_power``. """ from solprop.pulses import GaussianPulse, SechPulse # noqa: PLC0415 @@ -702,25 +583,8 @@ def _route_pulse(keywords: Mapping[str, object]) -> object: def _build(route: str, keywords: Mapping[str, object], path: str) -> "Simulation": """Build a route for its mode, a refusal named on the pinned path. - Parameters - ---------- - route : str - The route. - keywords : collections.abc.Mapping - Its keywords; a pulse without an energy or peak power (one a later pin - solves) is built at a placeholder. - path : str - The pinned path. - - Returns - ------- - solprop.driver.simulation.Simulation - The simulation, not translated. - - Raises - ------ - RecipeError - If the route refuses the keywords. + - ``keywords``: its keywords; a pulse without an energy or peak power (one a later + pin solves) is built at a placeholder. """ from solprop.recipe.metadata import surface # noqa: PLC0415 - cycle @@ -737,37 +601,15 @@ def _build(route: str, keywords: Mapping[str, object], path: str) -> "Simulation def _launch_mode(simulation: "Simulation") -> "Mode": - """Return the mode a pin solves on: the launch mode, the first of the set. - - Parameters - ---------- - simulation : solprop.driver.simulation.Simulation - The built route. - - Returns - ------- - solprop.modes.base.Mode - Its first mode. - """ + """Return the mode a pin solves on: the launch mode, the first of the set.""" return simulation.modes[0] def _at(mode: "Mode", parameter: str, value: float) -> "Mode": """Return the launch mode at a trial value of the pinned parameter. - Parameters - ---------- - mode : solprop.modes.base.Mode - The mode as built. - parameter : str - ``"pressure"`` (bar) or a mode parameter such as ``"core_radius"`` (m). - value : float - The trial value. - - Returns - ------- - solprop.modes.base.Mode - The mode there. + - ``parameter``: ``"pressure"`` (bar) or a mode parameter such as ``"core_radius"`` + (m). """ from solprop.tools import _capillary_at # noqa: PLC0415 - the tools' own re-fill @@ -779,17 +621,8 @@ def _at(mode: "Mode", parameter: str, value: float) -> "Mode": def _gamma(mode: "Mode", wavelength: float) -> float: """Return the fill's nonlinear coefficient on a mode at the launch position. - Parameters - ---------- - mode : solprop.modes.base.Mode - The mode, filled. - wavelength : float - The pump wavelength, m. - - Returns - ------- - float - ``gamma`` in rad/(W m). + - ``wavelength``: the pump wavelength, m. + - Returns: ``gamma`` in rad/(W m). """ from solprop.tools import _fill_n2, nonlinear_coefficient # noqa: PLC0415 @@ -809,26 +642,8 @@ def _route_gamma( A GNLSE mode has no fill, so the recipe's ``gamma`` or ``n2`` says what its coefficient is, as the route reads them. - Parameters - ---------- - mode : solprop.modes.base.Mode - The launch mode. - wavelength : float - The pump wavelength, m. - keywords : collections.abc.Mapping - The route's keywords. - path : str - The pinned path, for a refusal. - - Returns - ------- - float - ``gamma`` in rad/(W m). - - Raises - ------ - RecipeError - If the route names ``n2`` and does not give a number. + - ``wavelength``: the pump wavelength, m. + - Returns: ``gamma`` in rad/(W m). """ from solprop.tools import nonlinear_coefficient # noqa: PLC0415 @@ -845,18 +660,7 @@ def _route_gamma( def _needs_gamma(target: "DesignTarget") -> bool: - """Return whether a target needs the fill's Kerr coefficient. - - Parameters - ---------- - target : ZDW, RDW or SolitonOrder - The target. - - Returns - ------- - bool - For an RDW with a peak power, and a soliton order. - """ + """Return whether a target needs the fill's Kerr coefficient.""" from solprop.tools import RDW, SolitonOrder # noqa: PLC0415 return isinstance(target, SolitonOrder) or ( diff --git a/src/solprop/recipe/preview.py b/src/solprop/recipe/preview.py index faa43d9d..e53c01ae 100644 --- a/src/solprop/recipe/preview.py +++ b/src/solprop/recipe/preview.py @@ -281,27 +281,16 @@ def _pump( # noqa: PLR0911 - one return per case of the linear condition ) -> tuple[float | None, dict[str, object]]: """Return the pump wavelength and the soliton's nonlinear numbers, where given. - Parameters - ---------- - keywords : collections.abc.Mapping - The resolved recipe's route keywords. - soliton : solprop.modes.base.Mode or None - The launch mode. - z : float, optional - The position, m: the fill's coefficient, the mode's area and its dispersion - are taken there, as the mismatch is. - - Returns - ------- - tuple - The pump wavelength in m (``None`` without a numeric ``wavelength``), and - the keywords :func:`solprop.tools.phase_mismatch` takes for the nonlinear - term (``peak_power``, ``gamma``, ``fwhm``, ``shape``), empty for the linear - condition. The nonlinear coefficient is the recipe's ``gamma`` where it - gives one (the GNLSE), else the launch mode's fill's at `z`. The condition - is linear for a chirped pulse (a ``phases`` term from second order on), a - pump in normal dispersion, and a pulse below the soliton threshold, which - has no largest constituent. + - ``z``: the position, m: the fill's coefficient, the mode's area and its dispersion + are taken there, as the mismatch is. + - Returns: the pump wavelength in m (``None`` without a numeric ``wavelength``), and + the keywords :func:`solprop.tools.phase_mismatch` takes for the nonlinear term + (``peak_power``, ``gamma``, ``fwhm``, ``shape``), empty for the linear condition. + The nonlinear coefficient is the recipe's ``gamma`` where it gives one (the + GNLSE), else the launch mode's fill's at `z`. The condition is linear for a + chirped pulse (a ``phases`` term from second order on), a pump in normal + dispersion, and a pulse below the soliton threshold, which has no largest + constituent. """ from solprop.modes.base import beta2 # noqa: PLC0415 - layer above from solprop.physdata.mixtures import Mixture # noqa: PLC0415 @@ -366,27 +355,13 @@ def _phase_matching( ) -> tuple[Float[np.ndarray, " n_omega"] | None, tuple[float, ...]]: """Return a mode's phase mismatch to the soliton, and its phase-matched waves. - Parameters - ---------- - mode : solprop.modes.base.Mode - The mode the dispersive wave is in. - soliton : solprop.modes.base.Mode - The launch mode, the soliton's. - omega : numpy.ndarray - The band, rad/s. - z : float - The position, m. - pump : float or None - The pump wavelength, m. - pulse : collections.abc.Mapping - The nonlinear term's keywords (:func:`_pump`), empty for the linear one. - limits : tuple of float - The band's shortest and longest wavelengths, m. - - Returns - ------- - tuple - ``phase_mismatch`` and ``rdw``, the :class:`ModePreview` fields. + - ``omega``: the band, rad/s. + - ``z``: the position, m. + - ``pump``: the pump wavelength, m. + - ``pulse``: the nonlinear term's keywords (:func:`_pump`), empty for the linear + one. + - ``limits``: the band's shortest and longest wavelengths, m. + - Returns: ``phase_mismatch`` and ``rdw``, the :class:`ModePreview` fields. """ from solprop.tools import lambda_rdw_all, phase_mismatch # noqa: PLC0415 @@ -416,23 +391,11 @@ def _gamma( ) -> Float[np.ndarray, " n_omega"] | None: """Return a mode's nonlinear coefficient over the band, if its fill has one. - Parameters - ---------- - mode : solprop.modes.base.Mode - The mode. - omega : numpy.ndarray - Angular frequencies, rad/s. - area : float - Its effective area, m^2. - carrier : float - The wavelength the fill's Kerr data are taken at, m. - z : float - The position, m. - - Returns - ------- - numpy.ndarray or None - ``gamma`` in rad/(W m); ``None`` without a fill or its Kerr data. + - ``omega``: angular frequencies, rad/s. + - ``area``: its effective area, m^2. + - ``carrier``: the wavelength the fill's Kerr data are taken at, m. + - ``z``: the position, m. + - Returns: ``gamma`` in rad/(W m); ``None`` without a fill or its Kerr data. """ from solprop.physdata.mixtures import Mixture # noqa: PLC0415 from solprop.tools import _fill_n2 # noqa: PLC0415 - the tools' own n2 @@ -547,20 +510,8 @@ def _beam( ) -> _Beam: """Measure a free-space launch in its medium at the launch plane. - Parameters - ---------- - simulation : solprop.driver.simulation.Simulation - The built simulation, whose medium and grids measure the plane. - field : numpy.ndarray - The launched spectrum, internal, shape ``(n_modes, n_pol, n_omega)``. - z : float - The launch position, m. - - Returns - ------- - _Beam - Its power, energy and fluence, through the functions that measure a run's - saved plane. + - ``field``: the launched spectrum, internal, shape ``(n_modes, n_pol, n_omega)``. + - ``z``: the launch position, m. """ from solprop.grid import FreeGrid, LineGrid, RadialGrid # noqa: PLC0415 from solprop.spatial import ( # noqa: PLC0415 - the free-space routes only diff --git a/src/solprop/recipe/provenance.py b/src/solprop/recipe/provenance.py index b931eefe..0f471da2 100644 --- a/src/solprop/recipe/provenance.py +++ b/src/solprop/recipe/provenance.py @@ -49,15 +49,8 @@ def _stats_table(stats: "StatsSpec") -> dict[str, object] | None: """Return a statistics selection as its registered table, if it has one. - Parameters - ---------- - stats : solprop.stats.StatsSpec - The selection. - - Returns - ------- - dict or None - The table, or ``None`` for a selection with apertures, which have no table. + - Returns: the table, or ``None`` for a selection with apertures, which have no + table. """ if stats.apertures is not None: return None @@ -78,20 +71,7 @@ def _used( recipe: "Recipe", simulation: "Simulation", ) -> dict[str, object]: - """Return the value the build used for each resolvable keyword it can name. - - Parameters - ---------- - recipe : Recipe - The recipe. - simulation : Simulation - What it built, with its settings at run start. - - Returns - ------- - dict - Values by keyword, as a recipe writes them. - """ + """Return the value the build used for each resolvable keyword it can name.""" settings = simulation.settings values: dict[str, object] = {} controller = settings.controller @@ -189,15 +169,7 @@ def resolved_document( def _references(value: object) -> Iterator[Mapping[str, object]]: """Yield every data-file reference inside a recipe value. - Parameters - ---------- - value : object - A value as the recipe writes it. - - Yields - ------ - collections.abc.Mapping - Each table holding ``file`` and ``sha256``. + - Yields: each table holding ``file`` and ``sha256``. """ if isinstance(value, Mapping): if "file" in value and "sha256" in value: @@ -215,22 +187,7 @@ def _files( *, allow_outside: bool, ) -> dict[str, RecipeFile]: - """Return the data files a recipe refers to, embedded up to :data:`EMBED_LIMIT`. - - Parameters - ---------- - recipe : Recipe - The recipe. - base : pathlib.Path or None - Its directory. - allow_outside : bool - Whether a file may lie outside it. - - Returns - ------- - dict - Each file by a generated name, with its contents while they fit. - """ + """Return the data files a recipe refers to, embedded up to :data:`EMBED_LIMIT`.""" files: dict[str, RecipeFile] = {} total = 0 for index, reference in enumerate(_references(recipe.to_dict()["build"])): diff --git a/src/solprop/recipe/quantity.py b/src/solprop/recipe/quantity.py index b95be744..902babc3 100644 --- a/src/solprop/recipe/quantity.py +++ b/src/solprop/recipe/quantity.py @@ -258,47 +258,16 @@ class _Scale: def _refuse_unit(text: str, reason: str, path: str | None) -> RecipeError: - """Return the error for a unit text that cannot be read. - - Parameters - ---------- - text : str - The unit text. - reason : str - Why it cannot be read. - path : str or None - The parameter path. - - Returns - ------- - RecipeError - The error, to raise. - """ + """Return the error for a unit text that cannot be read.""" return RecipeError(f"cannot read the unit {text!r}: {reason}", path=path) def _parse_symbol(body: str, text: str, path: str | None) -> tuple[Dimension, int]: """Split a factor's letters into an optional prefix and a symbol. - Parameters - ---------- - body : str - The letters of one factor, such as ``"fs"``. - text : str - The whole unit text, for the error. - path : str or None - The parameter path, for the error. - - Returns - ------- - tuple of Dimension and int - The symbol's dimension and the decimal exponent of the prefixed symbol's - factor to the coherent SI unit. - - Raises - ------ - RecipeError - If ``body`` has no reading, or more than one. + - ``body``: the letters of one factor, such as ``"fs"``. + - Returns: the symbol's dimension and the decimal exponent of the prefixed symbol's + factor to the coherent SI unit. """ readings: list[tuple[Dimension, int]] = [] if body in _SYMBOLS: @@ -325,25 +294,9 @@ def _parse_factors( ) -> tuple[Dimension, int, int]: """Read a product of factors joined by ``*``. - Parameters - ---------- - product : str - The product, such as ``"fs^2"`` or ``"J*s"``. - text : str - The whole unit text, for the error. - path : str or None - The parameter path, for the error. - - Returns - ------- - tuple of Dimension, int and int - The product's dimension, the decimal exponent of its factor and the number - of factors. - - Raises - ------ - RecipeError - If a factor cannot be read, is a refused temperature unit or is ``deg``. + - ``product``: the product, such as ``"fs^2"`` or ``"J*s"``. + - Returns: the product's dimension, the decimal exponent of its factor and the + number of factors. """ dimension = Dimension() exponent10 = 0 @@ -364,25 +317,7 @@ def _parse_factors( def _parse_unit(text: str, path: str | None = None) -> _Scale: - """Read a unit text into its dimension and factor (see the module docstring). - - Parameters - ---------- - text : str - The unit text. - path : str or None, optional - The parameter path, for the error. - - Returns - ------- - _Scale - The dimension and the factor to the coherent SI unit. - - Raises - ------ - RecipeError - If the text is empty, ambiguous or cannot be read. - """ + """Read a unit text into its dimension and factor (see the module docstring).""" if text == _DEGREE_SYMBOL: return _Scale(Dimension(angle=1), 0, _DEGREE) compact = re.sub(r"\s*([*/()·⋅])\s*", r"\1", text.strip()) @@ -471,18 +406,7 @@ def dimension(self) -> Dimension: @functools.cache def _unit_scale(text: str) -> _Scale: - """Read a keyword's unit text once (the texts are a small fixed set). - - Parameters - ---------- - text : str - The unit text. - - Returns - ------- - _Scale - As :func:`_parse_unit` returns it. - """ + """Read a keyword's unit text once (the texts are a small fixed set).""" return _parse_unit(text) @@ -562,16 +486,6 @@ def _exact_context(*operands: Decimal) -> decimal.Context: A product has at most as many digits as its factors together, and the exponent range is the largest :mod:`decimal` allows, so a rounding can only come from a bug; ``Inexact`` is trapped to make one loud. - - Parameters - ---------- - *operands : Decimal - The numbers to multiply or shift. - - Returns - ------- - decimal.Context - The context. """ digits = sum(len(operand.as_tuple().digits) for operand in operands) return decimal.Context( @@ -585,32 +499,7 @@ def _exact_context(*operands: Decimal) -> decimal.Context: def _to_double( number: str, scale: _Scale, unit: Unit, path: str | None, text: str ) -> float: - """Convert a decimal number from one unit to another exactly, then round once. - - Parameters - ---------- - number : str - The decimal number, in the unit ``scale`` describes. - scale : _Scale - The unit the number is in. - unit : Unit - The keyword's unit, of the same dimension. - path : str or None - The parameter path, for the error. - text : str - The quantity string, for the error. - - Returns - ------- - float - The double nearest the exact value in ``unit``. - - Raises - ------ - RecipeError - If the exponent is beyond :mod:`decimal`'s range, or the value overflows a - double or underflows one to zero. - """ + """Convert a decimal number from one unit to another exactly, then round once.""" shift = scale.exponent10 - _unit_scale(unit.text).exponent10 try: mantissa = Decimal(number) @@ -711,17 +600,9 @@ def parse_quantity(value: object, unit: Unit, *, path: str | None = None) -> flo def _decimal_text(number: Decimal) -> str: """Write a decimal as Python writes a float: plain or in e-notation. - Parameters - ---------- - number : Decimal - The number. - - Returns - ------- - str - Plain digits for a decimal exponent from -4 to 15, as :func:`repr` writes a - float, and e-notation otherwise; no trailing zeros, and ``-0`` for a negative - zero. + - Returns: plain digits for a decimal exponent from -4 to 15, as :func:`repr` writes + a float, and e-notation otherwise; no trailing zeros, and ``-0`` for a negative + zero. """ if number.is_zero(): return "-0" if number.is_signed() else "0" @@ -734,19 +615,7 @@ def _decimal_text(number: Decimal) -> str: def _reads_back(text: str, unit: Unit, value: float) -> bool: """Tell whether a quantity string reads back to exactly a value. - Parameters - ---------- - text : str - The quantity string. - unit : Unit - The keyword's unit. - value : float - The value it should read back to. - - Returns - ------- - bool - Whether it does, the sign of a zero included. + - Returns: whether it does, the sign of a zero included. """ try: parsed = parse_quantity(text, unit) diff --git a/src/solprop/recipe/registry.py b/src/solprop/recipe/registry.py index 1e56a412..f0be7caf 100644 --- a/src/solprop/recipe/registry.py +++ b/src/solprop/recipe/registry.py @@ -200,16 +200,6 @@ def _pressure_pair(index: int) -> Unit: The form is two rows, the positions and the pressures there, ``[[z...], [p...]]``, and every element of a row takes the row's unit. - - Parameters - ---------- - index : int - The row. - - Returns - ------- - Unit - ``m`` for the positions, ``bar`` for the pressures. """ return Unit("m") if index == 0 else Unit("bar") @@ -493,22 +483,8 @@ def _pressure_pair(index: int) -> Unit: def _number(value: object, unit: UnitSpec, index: int, path: str) -> float | object: """Read one number in its unit, or return a non-numeric value as it stands. - Parameters - ---------- - value : object - The recipe value. - unit : str, callable or None - The unit, or the rule giving it by element. - index : int - The element's position, for a rule. - path : str - The value's path, for errors. - - Returns - ------- - float or object - The number in the unit, or `value` unchanged when it has no unit or is not a - number or quantity string. + - Returns: the number in the unit, or `value` unchanged when it has no unit or is + not a number or quantity string. """ if unit is None or isinstance(value, bool): return value @@ -521,29 +497,7 @@ def _number(value: object, unit: UnitSpec, index: int, path: str) -> float | obj def _array( value: Sequence[object], unit: UnitSpec, context: BuildContext, path: str ) -> list[object]: - """Read a list: each element in its unit, a nested list as a row. - - Parameters - ---------- - value : collections.abc.Sequence - The list. - unit : str, callable or None - The unit of each element, or the rule by element index. - context : BuildContext - For tables inside the list. - path : str - The list's path. - - Returns - ------- - list - The decoded elements. - - Raises - ------ - RecipeError - If the list is longer than :data:`~solprop.recipe.document.MAX_ARRAY_LENGTH`. - """ + """Read a list: each element in its unit, a nested list as a row.""" if len(value) > MAX_ARRAY_LENGTH: msg = ( f"a list of {len(value)} values is longer than the {MAX_ARRAY_LENGTH} a " @@ -609,27 +563,7 @@ def decode(value: object, unit: UnitSpec, context: BuildContext, path: str) -> o def _data_bytes(reference: DataFile, context: BuildContext, path: str) -> bytes: - """Return the checked bytes of a data file, embedded or on disk. - - Parameters - ---------- - reference : DataFile - The reference. - context : BuildContext - The embedded files and the recipe's directory. - path : str - The value's path. - - Returns - ------- - bytes - The contents, whose digest is the one recorded. - - Raises - ------ - RecipeError - If the file is neither embedded nor reachable from a directory. - """ + """Return the checked bytes of a data file, embedded or on disk.""" if reference.file in context.embedded: return checked_bytes(context.embedded[reference.file], reference, path=path) if context.base is None: @@ -645,19 +579,7 @@ def _data_array( ) -> np.ndarray: """Read an array from a data file a recipe refers to. - Parameters - ---------- - value : collections.abc.Mapping - The ``{file, sha256}`` reference. - context : BuildContext - The recipe's directory and embedded files. - path : str - The value's path. - - Returns - ------- - numpy.ndarray - The array. + - ``value``: the ``{file, sha256}`` reference. """ reference = decode_data_file(value, path=path) return array_from_bytes(_data_bytes(reference, context, path), reference, path=path) @@ -762,18 +684,7 @@ def build_object( @functools.cache def _hints(target: Callable[..., object]) -> Mapping[str, object]: - """Return the annotations of a callable's parameters. - - Parameters - ---------- - target : callable - A function, class method or class. - - Returns - ------- - collections.abc.Mapping - The annotation of each parameter, read once. - """ + """Return the annotations of a callable's parameters.""" function = target.__init__ if isinstance(target, type) else target try: return typing.get_type_hints(function) @@ -784,17 +695,7 @@ def _hints(target: Callable[..., object]) -> Mapping[str, object]: def _wants(annotation: object, kind: type) -> bool: """Return whether an annotation admits a container of a kind, and not a list. - Parameters - ---------- - annotation : object - The parameter's annotation. - kind : type - ``tuple`` or :class:`numpy.ndarray`. - - Returns - ------- - bool - True when the annotation names `kind` and admits no list or sequence. + - Returns: True when the annotation names `kind` and admits no list or sequence. """ text = str(annotation) if "Sequence" in text or "list[" in text: @@ -842,19 +743,7 @@ def coerce(target: Callable[..., object], name: str, value: object) -> object: def _keywords(factory: Factory) -> frozenset[str]: - """Return the keywords a factory takes from a recipe. - - Parameters - ---------- - factory : Factory - The entry. - - Returns - ------- - frozenset of str - Its signature's names, less its context, its data-file keyword and what it - omits. - """ + """Return the keywords a factory takes from a recipe.""" import inspect # noqa: PLC0415 - only here names = set(inspect.signature(factory.target).parameters) @@ -961,20 +850,7 @@ def decode_keyword(entry: Parameter, value: object, context: BuildContext) -> ob def _python_type(entry: Parameter, value: object) -> bool: - """Return whether a value names a type of a keyword that only Python holds. - - Parameters - ---------- - entry : Parameter - The keyword. - value : object - The value as written. - - Returns - ------- - bool - True for a table whose ``type`` a form with ``recipe=False`` lists. - """ + """Return whether a value names a type of a keyword that only Python holds.""" return isinstance(value, Mapping) and any( not form.recipe and value.get("type") in form.types for form in entry.forms ) @@ -987,18 +863,8 @@ def _python_array(entry: Parameter, value: object) -> bool: form is marked as not a recipe's, so a list or a data file given there is refused rather than passed on. - Parameters - ---------- - entry : Parameter - The keyword's metadata. - value : object - The value as TOML read it. - - Returns - ------- - bool - True when the value is a list or a data-file reference and every array form - of the keyword is one a recipe cannot hold. + - Returns: True when the value is a list or a data-file reference and every array + form of the keyword is one a recipe cannot hold. """ arrays = [form for form in entry.forms if form.kind in ("pair", "array")] reference = isinstance(value, Mapping) and set(value) == {"file", "sha256"} @@ -1012,18 +878,8 @@ def _python_array(entry: Parameter, value: object) -> bool: def _numeric_rows(entry: Parameter, value: object) -> bool: """Return whether a value is a list of rows for a keyword of numeric arrays. - Parameters - ---------- - entry : Parameter - The keyword's metadata. - value : object - The value as TOML read it. - - Returns - ------- - bool - True when the value is a list of lists and the keyword has an array form - whose elements carry units; such a value matched none of its forms. + - Returns: True when the value is a list of lists and the keyword has an array form + whose elements carry units; such a value matched none of its forms. """ rows = isinstance(value, list) and bool(value) and isinstance(value[0], list) return rows and any( From 7790961363794019eba561b1cf8ac0e5a350be6f Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:17:46 +0000 Subject: [PATCH 24/50] Shorten _length_fraction's summary and rename solve_pin's closure by hand The tool kept solve_pin's nested build, whose underscore name clashes with the module's _build; it is now _trial. Co-Authored-By: Claude Opus 5.5 --- src/solprop/recipe/metadata.py | 14 ++------------ src/solprop/recipe/pins.py | 14 +++----------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/src/solprop/recipe/metadata.py b/src/solprop/recipe/metadata.py index 19bc0ac9..dbcabbf0 100644 --- a/src/solprop/recipe/metadata.py +++ b/src/solprop/recipe/metadata.py @@ -461,19 +461,9 @@ def _pressure(values: Mapping[str, object]) -> Resolution: def _length_fraction(fraction: float, text: str) -> Resolver: - """Return the resolver of a step length that defaults to a fraction of the length. + """Return the resolver of a step length. - Parameters - ---------- - fraction : float - The fraction of ``length``. - text : str - The rule. - - Returns - ------- - callable - The resolver. + The step defaults to `fraction` of ``length``; `text` is the rule. """ def resolve(values: Mapping[str, object]) -> Resolution: diff --git a/src/solprop/recipe/pins.py b/src/solprop/recipe/pins.py index 4ad05c31..4db35cb3 100644 --- a/src/solprop/recipe/pins.py +++ b/src/solprop/recipe/pins.py @@ -419,23 +419,15 @@ def solve_pin( wavelength = cast("float", keywords["wavelength"]) nonlinear = _needs_gamma(target) - def build(value: float) -> "Mode | tuple[Mode, float]": + def _trial(value: float) -> "Mode | tuple[Mode, float]": """Return the launch mode at a trial value, with its Kerr coefficient. - Parameters - ---------- - value : float - The trial value of the pinned parameter. - - Returns - ------- - Mode or tuple - The mode, or the mode and ``gamma`` where the target needs it. + - Returns: the mode, or the mode and ``gamma`` where the target needs it. """ trial = _at(mode, parameter, value) return (trial, _gamma(trial, wavelength)) if nonlinear else trial - return solve_parameter(build, search, target) + return solve_parameter(_trial, search, target) def solve_pulse( From cea21d0f279f1492eb0a1f1e00ac0c3bcf1d860c Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:18:09 +0000 Subject: [PATCH 25/50] Restore conventions and reasons in solprop.translate private docs The slimming dropped a few entries the code does not say: the _channel_index_table coupled/include_area semantics, the loss policy's place before integration, the mode attributes _dispersion_phi reads, the taps' normalisation of the plasma band rule, the C-order flattening of the expansion products, the [1.0] constant prefactor weight, zero-off-band generators, and a few whys (_state's rank check, _degree_axis, the general flow's gauge identity, binary32 range). Also completes the tuple returns of _split/_split_segments and recapitalises Chebyshev, Cartesian and E. Co-Authored-By: Claude Opus 5.5 --- src/solprop/translate/chebyshev.py | 8 +++++--- src/solprop/translate/coupled.py | 4 ++++ src/solprop/translate/expansion.py | 1 + src/solprop/translate/free.py | 2 +- src/solprop/translate/general.py | 1 + src/solprop/translate/linop.py | 8 ++++++++ src/solprop/translate/modal_path.py | 2 +- src/solprop/translate/modal_tables.py | 9 +++++++++ src/solprop/translate/plasma_integration.py | 10 +++++++--- src/solprop/translate/problem.py | 22 ++++++++++++++++++--- src/solprop/translate/radial.py | 1 + src/solprop/translate/responses.py | 3 +++ src/solprop/translate/stage.py | 2 ++ src/solprop/translate/transverse.py | 6 +++++- 14 files changed, 67 insertions(+), 12 deletions(-) diff --git a/src/solprop/translate/chebyshev.py b/src/solprop/translate/chebyshev.py index 9b63ddba..b19427c9 100644 --- a/src/solprop/translate/chebyshev.py +++ b/src/solprop/translate/chebyshev.py @@ -949,7 +949,8 @@ def _split_segments( - ``edges``: mandatory edges in increasing order, m. - ``min_width``: narrowest interval allowed, m. - - Returns: True when the budget was exceeded. + - Returns: the resolved intervals, in increasing z, and True when the budget was + exceeded. """ pieces: list[_Piece] = [] exceeded = False @@ -1050,7 +1051,8 @@ def _split( - ``lo``, ``hi``: ends of the segment, m. - ``min_width``: narrowest interval allowed, m. - - Returns: True when the budget was exceeded. + - Returns: the resolved intervals, in increasing z, and True when the budget was + exceeded. """ # An explicit stack, never recursion: a square-root entrance refines geometrically # and would otherwise risk the recursion limit. Pushing the right half first and @@ -1286,7 +1288,7 @@ def _resolve( """Raise the degree on ``[lo, hi]`` until the coefficient tail is negligible. - ``lo``, ``hi``: ends of the interval, m. - - Returns: chebyshev coefficients on ``[lo, hi]``, shape ``(n_deg, *value_shape)``. + - Returns: Chebyshev coefficients on ``[lo, hi]``, shape ``(n_deg, *value_shape)``. - Returns: True when the interval is resolved to ``tol``. """ degree = min(INITIAL_DEGREE, n_max) diff --git a/src/solprop/translate/coupled.py b/src/solprop/translate/coupled.py index 376ad8cd..ba21e37d 100644 --- a/src/solprop/translate/coupled.py +++ b/src/solprop/translate/coupled.py @@ -173,6 +173,9 @@ def _owned( def _degree_axis(array: np.ndarray, name: str) -> None: """Refuse a coefficient block that declares no degree at all. + A block of ``n_deg = 0`` evaluates to nothing at every position, so it is refused + here rather than read as a zero. + - ``array``: a coefficient array whose second axis is the Chebyshev degree along z. """ if not array.shape[1]: @@ -1501,6 +1504,7 @@ def _checked_wall_pair( compared, because the dense generator's diagonal also carries the transverse term, which the pair does not describe. + - ``wall_vectors``: the dimensionless rank-one vector. - ``wall_weights``: its weight in 1/m, shape ``(n_omega,)``. - Returns: the pair in the leaf's own layout -- ``(1, n_modes, n_omega)`` and ``(1, 1, n_omega)`` -- or ``None`` when none was supplied. diff --git a/src/solprop/translate/expansion.py b/src/solprop/translate/expansion.py index b927ffaa..001c8f38 100644 --- a/src/solprop/translate/expansion.py +++ b/src/solprop/translate/expansion.py @@ -954,6 +954,7 @@ def products( """Return the tensor product of the basis polynomials at each position. - ``z``: positions in m, shape ``(n_z,)``. + - Returns: shape ``(n_z, n_terms)``, the multi-index flattened in C order. """ columns = [ chebyshev_basis.chebvander( diff --git a/src/solprop/translate/free.py b/src/solprop/translate/free.py index 63d957fd..b195bb56 100644 --- a/src/solprop/translate/free.py +++ b/src/solprop/translate/free.py @@ -40,7 +40,7 @@ def _validate_span(n_pol: int, z_max: float, margin: float) -> float: def _warn_grazing(space: FreeGrid, grid: TimeGrid, data: SeparableOperator) -> None: """Report the first active cutoff inside the split-square uncertainty bound. - - ``space``: cartesian shape for the reported column coordinates. + - ``space``: Cartesian shape for the reported column coordinates. - ``grid``: frequency bins in rad/s and their active mask. - ``data``: validated square high/low parts in m^-2. """ diff --git a/src/solprop/translate/general.py b/src/solprop/translate/general.py index 90110012..03b80a31 100644 --- a/src/solprop/translate/general.py +++ b/src/solprop/translate/general.py @@ -1908,6 +1908,7 @@ def _gas_responses( """Resolve a region's gas fill under the run's electronic policy. - ``omega_reference``: the run's carrier in rad/s. + - ``form``: the cubic's form; ``"real"`` keeps the full cubic. - Returns: per population, its electronic response, labelled ``":"``, and its number density in m^-3. """ diff --git a/src/solprop/translate/linop.py b/src/solprop/translate/linop.py index ffb9d8e8..1ee8919d 100644 --- a/src/solprop/translate/linop.py +++ b/src/solprop/translate/linop.py @@ -1117,6 +1117,8 @@ def _dispersion_phi( mode whose parameters do not resolve; the only approximation anywhere in ``Phi`` is the interpolation of ``D`` (``docs/guide/numerics/linear-operator.md``). + - ``mode``: only ``neff``, ``dneff_domega``, the declared ``core_radius`` and + ``density`` paths and, for the expansion, ``at_parameters`` are used. - ``grid``: the grid; supplies the frequency axis, the active band and the layout. - ``z_max``: length the run covers, m, positive and finite. - ``n_pol``: polarisation components, 1 or 2. ``D`` is the same for both components @@ -1124,12 +1126,15 @@ def _dispersion_phi( layout. - ``absorber_rate``: spectral absorber rate in 1/m, shape ``(n_omega,)``, folded into ``D`` before the integration so the collar is exact as a rate. + - ``loss``: applied to ``D`` before ``Phi`` is integrated, never to an accumulated + exponent afterwards. - ``tol``: target bound on the uniform error of the table relative to the value scale. The request is raised to :data:`ROUNDOFF_SAFETY` times the measured round-off floor of ``D`` when that is larger, because no interpolant can resolve a function below the noise of its own evaluation, and relaxed further, by decades up to :data:`MAX_TOL`, if the interval budget is exhausted anyway (``docs/dev/stepper.md`` section 4, step 4). Both are logged. + - ``max_intervals``: exceeding it sets the fallback flag and logs a warning. - ``representation``: which representation to build. ``"auto"``, the default, prefers the expansion; ``"table"`` forces the z table, which is what an equivalence test between the two needs; ``"expansion"`` refuses to fall back. A @@ -1966,6 +1971,9 @@ def _operator_of_parameters( # noqa: PLR0917 - called positionally - ``modes``: the mode set, in order; each must implement :class:`solprop.modes.protocol.ParameterisedMode`. + - ``frame``: its ``beta_0`` and ``beta_1`` follow the frozen reference mode, so the + frame subtraction is a function of the same parameters and needs no dimension + of its own. - ``absorber_rate``: the spectral absorber rate, or ``None``. - Returns: ``values -> D`` of shape ``(1, n_modes, n_pol, n_omega)``. """ diff --git a/src/solprop/translate/modal_path.py b/src/solprop/translate/modal_path.py index a9a30e3e..cddf60f3 100644 --- a/src/solprop/translate/modal_path.py +++ b/src/solprop/translate/modal_path.py @@ -535,7 +535,7 @@ def _stacked( ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """Stack the knots' E, H and generator of several snapshots, position first. - - Returns: e and H of shape ``(n_positions, n_knots, n_nodes, 3, n_modes)`` and the + - Returns: E and H of shape ``(n_positions, n_knots, n_nodes, 3, n_modes)`` and the generator of shape ``(n_positions, n_knots, n_modes, n_modes)``. """ return tuple( # type: ignore[return-value] diff --git a/src/solprop/translate/modal_tables.py b/src/solprop/translate/modal_tables.py index 96b0ace8..833af9e9 100644 --- a/src/solprop/translate/modal_tables.py +++ b/src/solprop/translate/modal_tables.py @@ -1212,6 +1212,15 @@ def _channel_index_table( """Read one channel's dispersion out of a tracked dense table. - ``channel``: channel index on the mode axis. + - ``include_area``: sample :func:`channel_area` at every frequency knot. + - ``coupled``: admit a generator that couples its channels and read ``i G_cc``, + the channel's **mean** propagation constant, off its diagonal; false refuses + one by name. Not a tuning knob: a coupled basis has no per-channel propagation + constant, because its channels beat, and a run that declares one propagates + the whole block through ``linear.coupled_flow``. The table is still built for + what the flow does not do -- the surrogate effective area, the dimension + limits, the labels and the cumulative ``Phi`` of the one reference channel -- + by a caller that has said which of the two it is asking for. """ modes = table.samples[0][0].electric.shape[2] if not 0 <= channel < modes: diff --git a/src/solprop/translate/plasma_integration.py b/src/solprop/translate/plasma_integration.py index cf56bed9..9928caec 100644 --- a/src/solprop/translate/plasma_integration.py +++ b/src/solprop/translate/plasma_integration.py @@ -207,6 +207,7 @@ def _design(dt: float, band_edge: float) -> RunningIntegral: - ``dt``: fine sample spacing, s. - ``band_edge``: upper edge of the apodised window, rad/s. + - Returns: the band rule with the smallest admitted ``K``. """ theta_b = band_edge * dt if not 0.0 < theta_b < math.pi: @@ -261,8 +262,8 @@ def _fit(half: int, theta_b: float) -> tuple[Float[np.ndarray, " n_taps"], float which leaves a ``K``-unknown linear least-squares problem. Its columns are scaled to unit norm before the SVD-based solve. - - Returns ``condition``: the scaled design matrix's condition number; one when ``K - <= 1``. + - Returns: the taps ``(c_0, ..., c_K)``, with ``c_0 = 1 - 2 sum c_k``, and the + scaled design matrix's condition number, one when ``K <= 1``. """ if half == 0: return np.ones(1), 1.0 @@ -281,7 +282,10 @@ def _fit(half: int, theta_b: float) -> tuple[Float[np.ndarray, " n_taps"], float def _composite( taps: Float[np.ndarray, " n_taps"], theta: Float[np.ndarray, " n"] ) -> Float[np.ndarray, " n"]: - """Return ``H = C rho``, the filtered trapezoid's multiplier.""" + """Return ``H = C rho``, the filtered trapezoid's multiplier. + + ``theta`` is ``omega dt``; ``H`` is relative to the exact integral. + """ theta = np.asarray(theta, dtype=np.float64) response = np.full_like(theta, taps[0]) for k in range(1, taps.size): diff --git a/src/solprop/translate/problem.py b/src/solprop/translate/problem.py index 8c68822e..0dcf89df 100644 --- a/src/solprop/translate/problem.py +++ b/src/solprop/translate/problem.py @@ -2668,6 +2668,7 @@ def _crossing_clamped( - ``dz_max``: the requested largest step, m. - ``dz_init``: the requested first step, m. + - ``single``: whether the state is binary32, whose range is smaller. - Returns: the largest step and the first step, m. """ if design is None: @@ -2937,6 +2938,8 @@ def _general_flow( the method. A guide that varies along z needs the transform maps to move with it and is built by :func:`_moving_flow` instead. + - ``route``: its gauge identity is carried by the flow, so that a resume comparing + the flow compares the basis it was built in. - ``coupled``: the block relative to the reference channel in 1/m, shape ``(n_modes, n_modes, n_omega)``. - ``grid``: the run's grid, whose coarse axis the leaf is tabulated on. @@ -3754,7 +3757,8 @@ def _taper_generator_at( - ``block``: the contiguous ``HE_11`` to ``HE_1N`` set. - ``z``: position in m. - - Returns: the generator in 1/m, complex, shape ``(n_modes, n_modes, n_omega)``. + - Returns: the generator in 1/m, complex, shape ``(n_modes, n_modes, n_omega)``, + exactly zero off the grid's active band. """ band = grid.omega[grid.active_mask] reference = np.asarray(block[0].neff_minus_one(band, z)) @@ -3850,6 +3854,8 @@ def _wall_generator_at( - ``block``: the contiguous ``HE_11`` to ``HE_1N`` set. - ``z``: position in m. + - ``sampled``: collects the limits of every position sampled, for the admission + that follows. - ``impedance``: which block to sample, ``"mean"`` or ``"te_tm"``. - Returns: the generator in 1/m, complex, shape ``(n_modes, n_modes, n_omega)``, exactly zero off the active band. @@ -3887,7 +3893,8 @@ def _channel_normalisation_at( - ``block``: the contiguous ``HE_11`` to ``HE_1N`` set. - ``z``: position in m. - - Returns: the rate in 1/m, shape ``(n_modes, n_omega)``. + - Returns: the rate in 1/m, shape ``(n_modes, n_omega)``, exactly zero off the + grid's active band. """ band = grid.omega[grid.active_mask] logarithmic = np.stack( @@ -4481,7 +4488,10 @@ def _general_prefactor( def _check_scalar_domain( table: PiecewiseChebyshev, name: str, *, upper: float = math.inf ) -> None: - """Check a scalar polynomial at endpoints and every real stationary point.""" + """Check a scalar polynomial at endpoints and every real stationary point. + + ``upper`` is inclusive, in the table's value units. + """ if table.value_shape != (): raise TranslationError(f"{name} must be a scalar table") for row, degree in zip(table.coefficients, table.degrees, strict=True): @@ -4610,6 +4620,10 @@ def _state( ) -> Complex[np.ndarray, "n_modes n_pol n_omega"]: """Bring an initial field to the canonical single-trajectory layout. + The shape is checked here rather than by an annotation, so that a wrong rank is + reported by name in production too. A group of trajectories is a sequence of + problems (SEP 47 section 6.7), never a stacked field. + - Returns: the state, complex128, the problem's own: copied once here as :class:`Initial` holds it, so that it is not copied again there, and taken as it is when the problem already holds it. @@ -5541,6 +5555,8 @@ def _power_initial( entrance was measured at 3e-15 of the launch. - ``initial``: fresh true-power launch and noise in canonical shape. + - ``linear``: its modal power-weight row is the entrance weight; its absence marks + spatial geometry, which carries no modal conversion. - ``index``: constant index override, or None for the physical effective index. - ``reused``: previously scaled launch retained only when its arrays match. - Returns: internal vacuum-normalised launch and noise. diff --git a/src/solprop/translate/radial.py b/src/solprop/translate/radial.py index 529a7d6a..f2ee7d9a 100644 --- a/src/solprop/translate/radial.py +++ b/src/solprop/translate/radial.py @@ -584,6 +584,7 @@ def _linear_roundoff_target( - ``sampled``: return the shaped operator and prefactor for a model and position. - ``span``: extended propagation interval in m. - ``requested``: requested dimensionless expansion tolerance, positive. + - Returns: the request raised to the derived arithmetic floor. """ # For s=sqrt(k^2-kperp^2), a relative perturbation in k^2 gives absolute # error |k^2|*epsilon/(2|s|). The following gamma(64) envelope covers both diff --git a/src/solprop/translate/responses.py b/src/solprop/translate/responses.py index 18b9ecf1..abf00e61 100644 --- a/src/solprop/translate/responses.py +++ b/src/solprop/translate/responses.py @@ -2190,6 +2190,9 @@ def _prefactor_at( # noqa: PLR0917 - called positionally ``mode_average_prefactor(mode, grid, 0.0) * crop_scale``, which is what ``prefactor_constant`` and every backend's fast path expect. + - ``mode_averaged``: fold the effective-area conversion in (the one-node case). + - ``shock``: keep the ``omega`` dependence of the numerator. + - ``index``: replace the effective index by this constant; the GNLSE passes 1.0. - Returns: ``z -> p`` of shape ``(n_modes, n_pol, n_omega)``. """ areas = [sqrtw_to_vm(one.aeff(0.0)) if mode_averaged else 1.0 for one in modes] diff --git a/src/solprop/translate/stage.py b/src/solprop/translate/stage.py index b0e4c5c3..bd32af23 100644 --- a/src/solprop/translate/stage.py +++ b/src/solprop/translate/stage.py @@ -767,6 +767,8 @@ def _prefactor_weights( """Return the prefactor's weights at a position, or ``None`` for the z table. - ``z``: position, m. + - Returns: exactly ``[1.0]`` for a constant, so that combining returns the stored + array's own values. """ if responses.prefactor_constant is not None or responses.separable is not None: low, high = responses.prefactor_span diff --git a/src/solprop/translate/transverse.py b/src/solprop/translate/transverse.py index fe14475a..65bd1a5a 100644 --- a/src/solprop/translate/transverse.py +++ b/src/solprop/translate/transverse.py @@ -87,7 +87,10 @@ def _axes(space: RadialGrid | FreeGrid | PlanarGrid) -> tuple[_Axis, ...]: def _nodes_window(space: RadialGrid | FreeGrid | PlanarGrid) -> np.ndarray: - """Return the grid's window on the nodes the collar's rate is applied at.""" + """Return the grid's window on the nodes the collar's rate is applied at. + + One inside the requested size, falling to zero at the outer size. + """ if isinstance(space, PlanarGrid): return np.repeat(space.window_y, int(space.x.size)) return np.asarray(space.window) @@ -155,6 +158,7 @@ def _column_power( """Return each spectral column's launch energy over the requested band. - ``field0``: the noise-free launch, canonical layout. + - Returns: energy per column in the field's own units. """ density = np.empty(field0.shape[1]) From 1a40933930539d33064f8dfbe0592b4f90921054 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:18:35 +0000 Subject: [PATCH 26/50] Slim private docstrings in solprop.backends Mechanical: python tools/docstring_diet.py $(find src/solprop/backends -name '*.py' | grep -v -e numpy_ref/single.py -e numpy_ref/transforms.py) --write --check --report --rename-closures numpy_ref/single.py and numpy_ref/transforms.py are left out (they go in the next PR). The D200 skip (numpy_ref.tdse._roundoff_terms) is rewritten by hand. Co-Authored-By: Claude Opus 5.5 --- src/solprop/backends/events.py | 14 +- src/solprop/backends/exponential.py | 84 +- src/solprop/backends/export.py | 29 +- src/solprop/backends/host/adams.py | 260 +---- src/solprop/backends/host/driver.py | 798 +++------------- src/solprop/backends/host/general.py | 85 +- src/solprop/backends/host/group.py | 131 +-- src/solprop/backends/host/group_session.py | 137 +-- src/solprop/backends/host/low_storage.py | 35 +- src/solprop/backends/host/session.py | 107 +-- src/solprop/backends/low_storage.py | 55 +- src/solprop/backends/memory.py | 44 +- src/solprop/backends/multistep.py | 476 +--------- src/solprop/backends/node_chunk.py | 187 +--- src/solprop/backends/numpy_ref/admission.py | 100 +- src/solprop/backends/numpy_ref/backend.py | 890 +++--------------- src/solprop/backends/numpy_ref/batch.py | 14 +- .../backends/numpy_ref/envelope_plasma.py | 34 +- src/solprop/backends/numpy_ref/passes.py | 197 +--- src/solprop/backends/numpy_ref/tdse.py | 271 +----- src/solprop/backends/populations.py | 17 +- src/solprop/backends/protocol.py | 29 +- src/solprop/backends/registry.py | 123 +-- src/solprop/backends/spatial.py | 178 +--- 24 files changed, 535 insertions(+), 3760 deletions(-) diff --git a/src/solprop/backends/events.py b/src/solprop/backends/events.py index 4afd1cc1..efaeeaf7 100644 --- a/src/solprop/backends/events.py +++ b/src/solprop/backends/events.py @@ -438,19 +438,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. """ return np.concatenate( [np.empty(0, dtype=dtype), *(getattr(part, name) for part in histories)] diff --git a/src/solprop/backends/exponential.py b/src/solprop/backends/exponential.py index 006a0b20..210f9620 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 @@ -616,17 +584,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 +601,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 f1a76c36..edc4c0f5 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 f123f11a..802390e5 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,11 @@ 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. + - ``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 +650,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 +667,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 +698,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 +747,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 +763,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 +774,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 +797,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 +813,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 +841,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 +858,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 +891,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 7970bcf2..c2055e9d 100644 --- a/src/solprop/backends/host/driver.py +++ b/src/solprop/backends/host/driver.py @@ -140,20 +140,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) @@ -175,13 +164,7 @@ def append(self, z: float, step: int, values: Mapping[str, np.ndarray]) -> None: self._n_rows = index + 1 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. - """ + """Return the rows as a frozen record and forget them.""" rows = self._n_rows drained = StepStats( z=self._z[:rows].copy(), @@ -194,10 +177,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) @@ -226,12 +207,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__( @@ -247,13 +223,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) @@ -276,17 +248,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: @@ -469,19 +432,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 @@ -493,15 +448,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( @@ -522,17 +469,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( @@ -540,29 +478,11 @@ 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. - """ + """Reserve a complete trial's external source calls when a service is bound.""" 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( @@ -584,25 +504,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" @@ -624,24 +531,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 = ( @@ -660,17 +553,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 @@ -680,16 +565,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)) @@ -700,10 +578,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() @@ -714,17 +589,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 @@ -735,12 +600,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) @@ -751,11 +611,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 @@ -769,15 +624,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) @@ -809,15 +656,7 @@ 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. - """ + """Discard periodic rows at a load or at the start of a plane.""" if release: self._rows.clear() else: @@ -830,38 +669,20 @@ 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. """ 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. - """ + """Return owning host rows at observation and retain reusable capacity.""" return self._rows.drain() @property 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: @@ -1143,10 +964,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() @@ -1167,17 +985,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] @@ -1196,17 +1005,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: @@ -1223,16 +1023,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( @@ -1245,15 +1035,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( @@ -1269,16 +1051,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) @@ -1450,13 +1222,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: @@ -1755,10 +1521,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() @@ -1850,17 +1613,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) @@ -1873,10 +1628,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 @@ -1904,28 +1656,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: @@ -1943,11 +1679,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 @@ -1964,20 +1695,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) @@ -2025,15 +1743,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 @@ -2095,16 +1805,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 @@ -2121,15 +1824,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): @@ -2234,15 +1929,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. - """ + """Read the transverse edge where its spacing or a saved plane asks (SEP 40).""" edge = monitor.transverse if edge is None: return @@ -2296,23 +1983,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]), @@ -2331,16 +2002,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: @@ -2356,11 +2019,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: @@ -2376,15 +2036,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 @@ -2400,15 +2053,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) @@ -2423,10 +2069,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 @@ -2436,10 +2079,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 @@ -2454,21 +2095,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: @@ -2488,15 +2114,7 @@ def _boundary_energies(self, state: _StateT) -> BoundaryEnergies: def _check_boundaries( self, monitor: BoundaryMonitor, *, plane: bool = False ) -> 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. - """ + """Account the committed step at the current position with the monitor.""" self._check_at( monitor, self._boundary_energies(self._require_state()), @@ -2516,18 +2134,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) @@ -2550,19 +2157,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 @@ -2592,11 +2189,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: @@ -2615,15 +2207,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: @@ -2640,10 +2226,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 @@ -2731,19 +2314,8 @@ 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. """ if not math.isfinite(norm): norm = math.inf @@ -2759,15 +2331,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 @@ -2785,15 +2349,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 @@ -2804,10 +2361,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 @@ -2829,24 +2383,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 = ( @@ -2892,23 +2434,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") @@ -2932,15 +2461,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]) @@ -2952,18 +2474,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 " @@ -2974,19 +2485,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" @@ -3001,11 +2500,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 @@ -3030,13 +2524,7 @@ def closed(self) -> bool: 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) @@ -3044,20 +2532,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): @@ -3071,24 +2546,7 @@ 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. """ schedule = self._stepper.schedule if schedule is None: @@ -3106,15 +2564,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}" @@ -3145,19 +2595,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 @@ -3167,15 +2605,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 diff --git a/src/solprop/backends/host/general.py b/src/solprop/backends/host/general.py index fb652d65..e61c111c 100644 --- a/src/solprop/backends/host/general.py +++ b/src/solprop/backends/host/general.py @@ -65,23 +65,7 @@ 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. + - ``z``, ``dz``: the anchor and the step, m. """ recent = history[::-1] nodes = np.array([(entry.z - z) / dz for entry in recent]) @@ -104,33 +88,11 @@ 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)``. - """ + """Return the lab-frame field at the end of the step for an increment.""" 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. - """ + """Return the weak norm of a defect, carried to the end of the step.""" return self.scale(self.propagator * defect) @@ -375,22 +337,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 +365,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 +377,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 6126e7f6..a155ef6f 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): @@ -348,13 +288,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 +440,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 +500,7 @@ 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. - - 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 +510,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 +545,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 88f38883..568fb9bb 100644 --- a/src/solprop/backends/host/group_session.py +++ b/src/solprop/backends/host/group_session.py @@ -398,45 +398,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 +436,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 +455,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 +469,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 +486,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 +498,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 +536,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 +573,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 +619,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 +635,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 = {} diff --git a/src/solprop/backends/host/low_storage.py b/src/solprop/backends/host/low_storage.py index 86764725..72628286 100644 --- a/src/solprop/backends/host/low_storage.py +++ b/src/solprop/backends/host/low_storage.py @@ -185,12 +185,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 +196,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 +216,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 ce55ab5e..2f1f35f8 100644 --- a/src/solprop/backends/host/session.py +++ b/src/solprop/backends/host/session.py @@ -585,13 +585,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 +594,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 +610,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 +625,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 +636,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 +644,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 +676,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 +694,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 +723,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 diff --git a/src/solprop/backends/low_storage.py b/src/solprop/backends/low_storage.py index 17bfb3e7..5096097b 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 1fb50500..db0957b2 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 62a7cf88..e41a87a3 100644 --- a/src/solprop/backends/multistep.py +++ b/src/solprop/backends/multistep.py @@ -286,24 +286,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 +357,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 +612,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 +629,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 +637,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 +648,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 +665,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 +818,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 +946,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 +1014,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 +1128,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 +1206,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) @@ -1624,17 +1453,7 @@ class _Compensation: def add_endpoint( self, rate: _State, state: _State, residual: _State | None = None ) -> 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. - """ + """Add the new sample's compensation at the predicted state; no transport.""" self.endpoint_rate = rate self.endpoint = (rate - self.exponent / self.dz) * state if residual is not None: @@ -1648,11 +1467,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 +1480,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 +1505,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 +1525,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] @@ -2652,19 +2412,7 @@ 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. + - ``dz``: the step, m. """ assert self._states is not None and self._rates is not None reference = exponent / dz @@ -2681,26 +2429,8 @@ 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. + - ``nodes``: the history's normalised positions, newest first. + - ``dz``: the step, m. """ assert self._states is not None and self._rates is not None assert self._exponential is not None @@ -2779,15 +2509,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 +2561,9 @@ 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). + - ``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 +2595,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 +2608,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 +2625,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 +2646,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 +2891,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 +2911,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 +2925,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 4bb018e2..7b8ba31b 100644 --- a/src/solprop/backends/node_chunk.py +++ b/src/solprop/backends/node_chunk.py @@ -247,17 +247,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 +269,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 +288,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 +298,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 +307,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 +324,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 +338,7 @@ 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. - - 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 7735848c..15743bb2 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 179f66c9..7c3e611c 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,8 @@ 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. """ responses, grids = self._problem.responses, self._problem.grids sources = passes.nonlinear_sources( @@ -2213,15 +1881,8 @@ 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. + - Returns: cropped raw-DFT polarisation-equivalent source, zero on inactive + bins. """ grids = self._problem.grids return passes.held_plasma_spectrum( @@ -2231,15 +1892,7 @@ 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. + - 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 +1903,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 +1942,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 +2043,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 +2146,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 +2185,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 +2224,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 ``coarse``: 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 +2326,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 +2381,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 +2395,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 +2474,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 +2496,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 +2585,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 +2614,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: @@ -3238,26 +2751,7 @@ 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`. - """ + """Return how many squarings an exponent of this row-sum norm needs, or refuse.""" squarings = 0 if largest > _TAYLOR_SCALE: squarings = math.ceil(math.log2(largest / _TAYLOR_SCALE)) @@ -3278,22 +2772,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 +2802,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 +2835,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 +2881,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 +2913,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 +2999,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 +3016,9 @@ 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,)``. - - 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 +3053,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 +3081,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 +3110,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 +3170,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 @@ -3846,21 +3199,8 @@ def _harmonic_carrier( 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. - - 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 +3225,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 b900a95d..2ff88ba6 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 da4dad2a..43d97e1a 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/passes.py b/src/solprop/backends/numpy_ref/passes.py index c12c1c13..6ac86718 100644 --- a/src/solprop/backends/numpy_ref/passes.py +++ b/src/solprop/backends/numpy_ref/passes.py @@ -609,23 +609,10 @@ 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)``. + - ``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 +788,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 +880,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 +1103,12 @@ 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. """ if len(densities) != len(channels): raise BackendError("plasma channels require one density each") @@ -1695,15 +1643,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 +1651,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 +1659,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 +1677,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 +1693,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 +1704,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 +1712,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 9be8b6e3..b951100c 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,8 @@ 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. + - ``factor``: p or J spectral factor including crop scale, shape ``(n_modes, 1, + n_omega)``. """ scaled, accumulation = _roundoff_terms( magnitudes, projection, factor, problem, bundle @@ -275,27 +237,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 +263,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 +342,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 +408,10 @@ 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)``. """ grids = problem.grids edge_magnitude, derivative_magnitude = magnitudes @@ -542,17 +443,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 +464,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 +516,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 +555,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 +614,10 @@ 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. """ descriptor, grids = tdse_descriptor(problem.route), problem.grids assert descriptor is not None @@ -805,16 +645,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 +798,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 49d88d20..f894ea97 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 6ed6bf3b..c026f178 100644 --- a/src/solprop/backends/protocol.py +++ b/src/solprop/backends/protocol.py @@ -333,15 +333,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 +343,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 +495,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 " diff --git a/src/solprop/backends/registry.py b/src/solprop/backends/registry.py index 8efc6134..5845fdab 100644 --- a/src/solprop/backends/registry.py +++ b/src/solprop/backends/registry.py @@ -173,18 +173,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. - """ + """Import the object a registry path names.""" module, _, attribute = path.partition(":") found: object = importlib.import_module(module) for part in attribute.split("."): @@ -239,18 +228,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 +338,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 +348,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 +362,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 +419,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 []) @@ -484,18 +434,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. - """ + """Load the native package's processor check from its file, as its 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 raise ImportError(f"cannot load the native processor check {source}") @@ -670,15 +609,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 +629,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/spatial.py b/src/solprop/backends/spatial.py index 43085dfc..cbd700de 100644 --- a/src/solprop/backends/spatial.py +++ b/src/solprop/backends/spatial.py @@ -168,22 +168,8 @@ 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. """ transform = problem.transform n_omega = field.shape[-1] @@ -684,34 +670,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 +882,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 +900,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 +925,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 +949,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 +967,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 +996,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 +1123,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) From 2f46f19c64a18b9081fff29d1ac95b5a7643fa32 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:18:44 +0000 Subject: [PATCH 27/50] Restore flags, conventions and warnings in solprop.nonlinear private docs Co-Authored-By: Claude Opus 5.5 --- src/solprop/nonlinear/mixtures.py | 4 ++++ src/solprop/nonlinear/optmat_raman.py | 10 +++++++++- src/solprop/nonlinear/optmat_rates.py | 9 ++++++++- src/solprop/nonlinear/raman.py | 6 +++++- src/solprop/nonlinear/responses.py | 5 +++++ 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/solprop/nonlinear/mixtures.py b/src/solprop/nonlinear/mixtures.py index e8177b5e..6a4b7f28 100644 --- a/src/solprop/nonlinear/mixtures.py +++ b/src/solprop/nonlinear/mixtures.py @@ -88,6 +88,9 @@ def _component_raman( ) -> RamanResponse | None: """Select available terms by default and honour explicit per-term requests. + - ``mixture``: its temperature (for a temperature path, the reference + :func:`raman_temperature` takes) sets the rotational populations. + - ``explicit``: whether absence of the selected response is an error. - ``length``: the run's length in m, over which a temperature path is averaged. - Returns: selected component terms, or None for an allowed omission. """ @@ -361,6 +364,7 @@ def _composition_at( """Place a composition's one response at the label its leaf carries. - ``composition``: the composition's response, if any. + - Returns: the response at its leaf's label, and None at the others. """ if composition is None: return None diff --git a/src/solprop/nonlinear/optmat_raman.py b/src/solprop/nonlinear/optmat_raman.py index dc17e217..5b65a3f4 100644 --- a/src/solprop/nonlinear/optmat_raman.py +++ b/src/solprop/nonlinear/optmat_raman.py @@ -907,6 +907,7 @@ def _terms( anisotropic ones to the rank-two term unchanged. An all-zero row is dropped. - ``response``: their response, for the width laws in Hz. + - Returns: the terms in group order, isotropic before rank-two within a group. """ terms = [] for group in groups: @@ -932,7 +933,13 @@ def _terms( def _assemble( request: _Request, *, thg: bool, labels: tuple[str, ...], leaf: str | None ) -> OptmatRamanResponse: - """Build, check and map one optmat response onto solprop's terms.""" + """Build, check and map one optmat response onto solprop's terms. + + - ``labels``: the labels whose summed density drives the response. + - ``leaf``: the label the compiled leaf carries. + - Warns once per run each if the temperature or a declared density is outside the + response's validated range, and once per run for an admitted provisional response. + """ built, caught = _build(request) response = _with_dephasing(built, request) entry = _optmat.record(response) @@ -1206,6 +1213,7 @@ def _keep( ) -> tuple[dict[str, MolecularOptions], dict[str, Dephasing]]: """Return the species whose lines are kept, their flags and supplied laws. + - ``explicit``: the labels were named, so one without a response is refused. - Returns: species to options, and species to a supplied dephasing. """ keep: dict[str, MolecularOptions] = {} diff --git a/src/solprop/nonlinear/optmat_rates.py b/src/solprop/nonlinear/optmat_rates.py index 750374e4..e84cc3be 100644 --- a/src/solprop/nonlinear/optmat_rates.py +++ b/src/solprop/nonlinear/optmat_rates.py @@ -157,7 +157,10 @@ def _is_rate_model(value: object) -> bool: def _resolve(name: str, wavelength: float, form: str) -> object: """Resolve a species name and a model name to optmat's rate model. + - ``name``: an atom of ``ionisation_rate_model`` or a molecule of + ``molecular_rate_model``. - ``wavelength``: vacuum carrier wavelength in m. + - ``form``: ``"recommended"`` or a model name of the species' registry. """ from optmat.exceptions import OptmatError # noqa: PLC0415 - optmat is lazy from optmat.gas.ionisation_rates import ( # noqa: PLC0415 - optmat is lazy @@ -218,7 +221,11 @@ def _resolve(name: str, wavelength: float, form: str) -> object: @contextmanager def _quiet(*, reemit: bool) -> Iterator[list[warnings.WarningMessage]]: - """Evaluate optmat with its range warnings captured, and pass on any other.""" + """Evaluate optmat with its range warnings captured, and pass on any other. + + - ``reemit``: the caller handles the captured range warnings itself (the yielded + list) rather than dropping them. + """ from optmat.exceptions import ModelRangeWarning # noqa: PLC0415 - optmat is lazy ranged: list[warnings.WarningMessage] = [] diff --git a/src/solprop/nonlinear/raman.py b/src/solprop/nonlinear/raman.py index 5965bc89..87d8e62a 100644 --- a/src/solprop/nonlinear/raman.py +++ b/src/solprop/nonlinear/raman.py @@ -1104,6 +1104,7 @@ def _quadratic_tensor( """Form the real molecular drive, retaining delayed off-diagonal coherence. - ``values``: local field in V/m, shape ``(n_pol, n_to)``. + - ``thg``: keep the instantaneous real-field drive; ignored for envelopes. - Returns: real symmetric quadratic tensor in V^2/m^2, shape ``(n_pol, n_pol, n_to)``. """ @@ -2521,7 +2522,10 @@ def _supplied_response( # noqa: PLR0917 - called positionally selection: MaterialSelection | None, density: float | None, ) -> RamanResponse: - """Build a caller's species' Raman response from its own molecular constants.""" + """Build a caller's species' Raman response from its own molecular constants. + + A supplied species is never matched to a built-in gas by its name. + """ if selection is not None or density is not None: msg = ( f"{material.name} is a caller's species: its Raman response is built from " diff --git a/src/solprop/nonlinear/responses.py b/src/solprop/nonlinear/responses.py index dc460856..b458df86 100644 --- a/src/solprop/nonlinear/responses.py +++ b/src/solprop/nonlinear/responses.py @@ -181,6 +181,8 @@ def _check_evaluate( - ``carrier``: the carrier passed by the caller, if any. - ``needs_carrier``: whether this response and layout require one. + - ``max_pol``: the most field components the response takes: two for a transverse + field, three for the general modal route's full vector field. """ if kind not in ("field", "envelope"): msg = f"kind must be 'field' or 'envelope', got {kind!r}" @@ -475,6 +477,9 @@ def _product_spectrum( - ``positive``: ``Pi_+`` on the transform's spectral layout. - ``kind``: the layout. + - ``sector``: which quartets are kept. + - ``carrier``: ``exp(2 i omega0 t)``, required for the full sector on an envelope + grid. - ``window``: the temporal collar, multiplying the product in time before its transform, ahead of ``Pi_+`` and every output multiplier; ``None`` is a unit collar. From 84d7228d419ce4a3437711afa95eb618501cdf09 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:19:09 +0000 Subject: [PATCH 28/50] Slim private docstrings in solprop.physdata Mechanical: python tools/docstring_diet.py src/solprop/physdata --write --check --report --rename-closures By hand: _warn_dilute_scaling's sections (D200 skip) and one f-string reflowed after the _span rename lengthened its line. Co-Authored-By: Claude Opus 5.5 --- src/solprop/physdata/_optmat.py | 375 +++------------------- src/solprop/physdata/equation_of_state.py | 45 +-- src/solprop/physdata/glasses.py | 189 ++--------- src/solprop/physdata/mixtures.py | 228 +++---------- src/solprop/physdata/nonlinear_data.py | 80 +---- src/solprop/physdata/selection.py | 107 +----- src/solprop/physdata/sellmeier.py | 213 ++---------- src/solprop/physdata/species.py | 82 +---- src/solprop/physdata/transport.py | 55 +--- 9 files changed, 197 insertions(+), 1177 deletions(-) diff --git a/src/solprop/physdata/_optmat.py b/src/solprop/physdata/_optmat.py index 3b31cf31..825cbbe3 100644 --- a/src/solprop/physdata/_optmat.py +++ b/src/solprop/physdata/_optmat.py @@ -81,15 +81,7 @@ def bar_to_pa(pressure_bar: float) -> float: """Convert a pressure from bar, solprop's interface unit, to optmat's pascals. - Parameters - ---------- - pressure_bar : float - Pressure in bar. - - Returns - ------- - float - Pressure in Pa, exactly ``1e5`` times the input. + - Returns: pressure in Pa, exactly ``1e5`` times the input. """ return float(pressure_bar) * BAR @@ -97,15 +89,7 @@ def bar_to_pa(pressure_bar: float) -> float: def pa_to_bar(pressure_pa: float) -> float: """Convert a pressure from optmat's pascals to bar. - Parameters - ---------- - pressure_pa : float - Pressure in Pa. - - Returns - ------- - float - Pressure in bar. + - ``pressure_pa``: pressure in Pa. """ return float(pressure_pa) / BAR @@ -115,15 +99,9 @@ def omega_to_wavelength( ) -> Float[np.ndarray, "*shape"]: """Return the vacuum wavelengths of non-negative angular frequencies. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s, >= 0, already validated. - - Returns - ------- - numpy.ndarray - ``2 pi c / omega`` in m; ``inf`` (optmat's static limit) where ``omega = 0``. + - ``omega``: angular frequencies in rad/s, >= 0, already validated. + - Returns: ``2 pi c / omega`` in m; ``inf`` (optmat's static limit) where ``omega = + 0``. """ values = np.asarray(omega, dtype=float) with np.errstate(divide="ignore"): @@ -135,66 +113,26 @@ def wavelength_to_omega( ) -> Float[np.ndarray, "*shape"]: """Return the angular frequencies of positive vacuum wavelengths. - Parameters - ---------- - wavelength : numpy.ndarray - Vacuum wavelengths in m, > 0; ``inf`` is the static limit. - - Returns - ------- - numpy.ndarray - ``2 pi c / lambda`` in rad/s; 0 at ``inf``. + - ``wavelength``: vacuum wavelengths in m, > 0; ``inf`` is the static limit. + - Returns: ``2 pi c / lambda`` in rad/s; 0 at ``inf``. """ return np.asarray(2 * math.pi * C / np.asarray(wavelength, dtype=float)) def untyped(model: object) -> Untyped: - """Return an optmat object for attribute access, which its protocol cannot type. - - Parameters - ---------- - model : object - An optmat model, response or equation of state. - - Returns - ------- - Any - The same object. - """ + """Return an optmat object for attribute access, which its protocol cannot type.""" return model def optmat_version() -> str: - """Return the installed optmat version. - - Returns - ------- - str - ``optmat.__version__``. - """ + """Return the installed optmat version.""" import optmat # noqa: PLC0415 - optmat is imported lazily, see the module docstring return str(optmat.__version__) def record(model: object) -> MaterialRecord: - """Return the record of a resolved optmat model. - - Parameters - ---------- - model : OptmatModel - Any optmat object with an ``identity``. - - Returns - ------- - MaterialRecord - Its identity in plain values. - - Raises - ------ - MaterialError - If the object has no optmat identity. - """ + """Return the record of a resolved optmat model.""" if not isinstance(model, OptmatModel): msg = ( f"a {type(model).__name__} is not an optmat model: a model object carries " @@ -207,10 +145,7 @@ def record(model: object) -> MaterialRecord: def open_log() -> MaterialLog | None: """Return the log of the innermost open evaluation context, if any. - Returns - ------- - MaterialLog or None - The log, or ``None`` outside every context. + - Returns: the log, or ``None`` outside every context. """ logs = _LOGS.get() return logs[-1] if logs else None @@ -227,15 +162,7 @@ def material_context(log: MaterialLog | None = None) -> Iterator[MaterialLog]: warning either has already raised is not raised again, and both record what happens inside. - Parameters - ---------- - log : MaterialLog or None, optional - The log to resume; ``None`` shares the open context's, or starts one. - - Yields - ------ - MaterialLog - The context's log. + - ``log``: the log to resume; ``None`` shares the open context's, or starts one. """ logs = _LOGS.get() if log is None and logs: @@ -257,16 +184,8 @@ def warn_once( ) -> None: """Raise a warning unless an open evaluation context has raised it already. - Parameters - ---------- - key : tuple of str - What makes two warnings the same one: a kind and a material's digest, say. - message : str - The warning's text. - category : type - The warning's class. - stacklevel : int, optional - Passed to :func:`warnings.warn`. + - ``key``: what makes two warnings the same one: a kind and a material's digest, + say. """ logs = _LOGS.get() if any(key in log.seen for log in logs): @@ -281,14 +200,7 @@ def _record_range( ) -> None: """Record, in every open context, where a band left a model's intervals. - Parameters - ---------- - model : OptmatModel - The model evaluated, with ``intervals`` and possibly ``gap_reasons``. - material : str - The material it serves. - wavelength : numpy.ndarray or None - The evaluated vacuum wavelengths in m, where known. + - ``wavelength``: the evaluated vacuum wavelengths in m, where known. """ logs = _LOGS.get() if not logs: @@ -312,23 +224,6 @@ def check_status(model: object, material: str) -> MaterialRecord: A ``provisional`` or ``untested`` model warns once per evaluation context; a ``validated``, ``comparison`` or ``user`` one does not, since the first is a curated default and the other two were chosen by the caller. - - Parameters - ---------- - model : OptmatModel - The resolved model. - material : str - The material it serves, for the message. - - Returns - ------- - MaterialRecord - The model's record. - - Warns - ----- - MaterialStatusWarning - If the model is ``provisional`` or ``untested``. """ entry = record(model) if entry.status in _FLAGGED: @@ -344,15 +239,7 @@ def check_status(model: object, material: str) -> MaterialRecord: def _nm(value: float) -> str: """Return a wavelength in nm as text. - Parameters - ---------- - value : float - Wavelength in m; ``inf`` is the static limit. - - Returns - ------- - str - Four significant figures and the unit, or ``inf``. + - ``value``: wavelength in m; ``inf`` is the static limit. """ return "inf" if math.isinf(value) else f"{value * 1e9:.4g} nm" @@ -364,20 +251,12 @@ def outside_parts( ) -> tuple[str, ...]: """Describe the parts of a band that lie outside a model's qualified intervals. - Parameters - ---------- - wavelength : numpy.ndarray - The evaluated vacuum wavelengths in m. - intervals : sequence of tuple of float - The model's disjoint, ascending qualified intervals in m. - gap_reasons : sequence of str, optional - The reason of each gap between two consecutive intervals, where known. - - Returns - ------- - tuple of str - One phrase per part outside: below the first interval, in each gap the band - reaches (with its reason), and beyond the last; empty if the band is inside. + - ``wavelength``: the evaluated vacuum wavelengths in m. + - ``intervals``: the model's disjoint, ascending qualified intervals in m. + - ``gap_reasons``: the reason of each gap between two consecutive intervals, where + known. + - Returns: one phrase per part outside: below the first interval, in each gap the + band reaches (with its reason), and beyond the last; empty if the band is inside. """ values = np.ravel(np.asarray(wavelength, dtype=float)) values = values[~np.isnan(values)] @@ -408,19 +287,7 @@ def _range_message( ) -> str: """Return the text of a range warning for a band outside a model's intervals. - Parameters - ---------- - model : OptmatModel - The model, with ``intervals`` and, for a fit with several, ``gap_reasons``. - material : str - The material, for the message. - wavelength : numpy.ndarray or None - The evaluated vacuum wavelengths in m, where known. - - Returns - ------- - str - The model, its intervals, and the parts of the band outside them. + - ``wavelength``: the evaluated vacuum wavelengths in m, where known. """ entry = record(model) intervals: tuple[tuple[float, float], ...] = tuple( @@ -449,29 +316,7 @@ def _range_message( def _captured( model: object, quantity: str, *, material: str, state: str = "" ) -> Iterator[list[warnings.WarningMessage]]: - """Capture optmat's warnings and translate its refusals. - - Parameters - ---------- - model : OptmatModel - The model the body evaluates. - quantity : str - What it evaluates, for messages. - material : str - The material, for messages. - state : str, optional - The state asked for, for error messages. - - Yields - ------ - list of warnings.WarningMessage - The warnings the body raises, filled as it runs. - - Raises - ------ - MaterialError - If optmat refuses the evaluation. - """ + """Capture optmat's warnings and translate its refusals.""" from optmat.exceptions import OptmatError # noqa: PLC0415 - optmat is lazy entry = record(model) @@ -499,34 +344,9 @@ def translated( ) -> Iterator[None]: """Evaluate optmat under solprop's warning and error policy. - Parameters - ---------- - model : OptmatModel - The model the body evaluates. - quantity : str - What it evaluates, for messages: ``"polarizability"``, ``"number density"``. - material : str - The material, for messages. - wavelength : numpy.ndarray or None, optional - The vacuum wavelengths in m the body evaluates, for range messages. - state : str, optional - The state asked for, for error messages: ``"30 bar and 293.15 K"``. - - Yields - ------ - None - Control, with optmat's warnings captured. - - Raises - ------ - MaterialError - If optmat refuses the evaluation. - - Warns - ----- - MaterialRangeWarning - Once per material and evaluation context, if the band leaves the model's - qualified intervals or an absorbing model's real part is asked for. + - ``wavelength``: the vacuum wavelengths in m the body evaluates, for range + messages. + - ``state``: the state asked for, for error messages: ``"30 bar and 293.15 K"``. """ with _captured(model, quantity, material=material, state=state) as caught: yield @@ -556,24 +376,8 @@ class EvaluationCache: beyond :attr:`size`. The cache is not copied with its adapter: a pickled or deep-copied adapter starts with an empty one. - Parameters - ---------- - size : int, optional - The most entries kept, :data:`EVALUATION_CACHE_SIZE` by default; at least - one. - - Raises - ------ - ValueError - If `size` is less than one. - - Examples - -------- - >>> import numpy as np - >>> from solprop.physdata._optmat import EvaluationCache - >>> cache = EvaluationCache(size=2) - >>> len(cache), cache.size - (0, 2) + - ``size``: the most entries kept, :data:`EVALUATION_CACHE_SIZE` by default; at + least one. """ __slots__ = ("_entries", "_lock", "size") @@ -581,15 +385,7 @@ class EvaluationCache: def __init__(self, size: int = EVALUATION_CACHE_SIZE) -> None: """Start empty. - Parameters - ---------- - size : int, optional - The most entries kept; at least one. - - Raises - ------ - ValueError - If `size` is less than one. + - ``size``: the most entries kept; at least one. """ if size < 1: msg = f"an evaluation cache keeps at least one entry, not {size}" @@ -602,32 +398,17 @@ def __init__(self, size: int = EVALUATION_CACHE_SIZE) -> None: self._lock = threading.Lock() def __len__(self) -> int: - """Return the number of entries held. - - Returns - ------- - int - At most :attr:`size`. - """ + """Return the number of entries held.""" return len(self._entries) def __repr__(self) -> str: - """Return the size and occupancy. - - Returns - ------- - str - For example ``EvaluationCache(2 of 8)``. - """ + """Return the size and occupancy.""" return f"EvaluationCache({len(self)} of {self.size})" def __reduce__(self) -> tuple[type["EvaluationCache"], tuple[int]]: """Pickle and copy as an empty cache of the same size. - Returns - ------- - tuple - The class and its size. + - Returns: the class and its size. """ return (type(self), (self.size,)) @@ -648,36 +429,8 @@ def evaluate( ) -> tuple[np.ndarray, ...]: """Return an evaluation at `omega`, computing it only if it is not held. - Parameters - ---------- - quantity : str - What `compute` evaluates, the key's first part and the messages' word. - omega : numpy.ndarray - The validated angular frequencies in rad/s, the key's second part. - model : OptmatModel - The model `compute` evaluates. - material : str - The material, for messages. - wavelength : numpy.ndarray - The vacuum wavelengths of `omega` in m, for range messages. - compute : callable - Evaluates optmat and returns its arrays; called under - :func:`translated`'s policy. - - Returns - ------- - tuple of numpy.ndarray - Fresh, writeable copies of the arrays `compute` returns. - - Raises - ------ - MaterialError - If optmat refuses the evaluation; nothing is stored. - - Warns - ----- - MaterialRangeWarning - As :func:`translated`, at every use whether stored or not. + - ``omega``: the validated angular frequencies in rad/s, the key's second part. + - ``wavelength``: the vacuum wavelengths of `omega` in m, for range messages. """ values = np.asarray(omega) key = (quantity, values.shape, values.dtype.str, values.tobytes()) @@ -712,16 +465,7 @@ def _reemit( ) -> None: """Raise captured optmat warnings as solprop's, once each per context. - Parameters - ---------- - caught : sequence of warnings.WarningMessage - What the evaluation raised. - model : OptmatModel - The model evaluated. - material : str - The material, for messages. - wavelength : numpy.ndarray or None - The evaluated vacuum wavelengths in m, where known. + - ``wavelength``: the evaluated vacuum wavelengths in m, where known. """ from optmat.exceptions import ( # noqa: PLC0415 - optmat is imported lazily ModelAbsorptionWarning, @@ -751,23 +495,7 @@ def _reemit( def composition_fractions(name: str) -> Mapping[str, float]: - """Return a named optmat composition's species and mole fractions. - - Parameters - ---------- - name : str - A composition optmat names, such as ``"dry air (N2, O2, Ar)"``. - - Returns - ------- - collections.abc.Mapping - Gas to mole fraction, in optmat's order, summing to one. - - Raises - ------ - MaterialError - If optmat has no composition of that name. - """ + """Return a named optmat composition's species and mole fractions.""" from optmat.exceptions import OptmatError # noqa: PLC0415 - lazily, see above from optmat.gas.composition import composition # noqa: PLC0415 - as above @@ -790,21 +518,11 @@ def record_dropped( ) -> None: """Record, in every open context, the loss a transparent index dropped. - Parameters - ---------- - model : OptmatModel - The solid model whose imaginary index was dropped. - bound : float - The transparent approximation's absorption bound in 1/m. - extinction : float - The largest ``k`` dropped, dimensionless. - absorption : float - The largest ``4 pi k / lambda`` dropped, 1/m. - wavelength : float - The vacuum wavelength in m of that absorption, or NaN. - digest : str, optional - The model's record digest, when the caller holds it; otherwise it is formed - from the model's identity. + - ``bound``: the transparent approximation's absorption bound in 1/m. + - ``absorption``: the largest ``4 pi k / lambda`` dropped, 1/m. + - ``wavelength``: the vacuum wavelength in m of that absorption, or NaN. + - ``digest``: the model's record digest, when the caller holds it; otherwise it is + formed from the model's identity. """ logs = _LOGS.get() if not logs: @@ -820,15 +538,6 @@ def record_outside(model: object, material: str, parts: Sequence[str]) -> None: For a range the model warns about at every evaluation, which an adapter records rather than emits (an ionisation rate tabulated above its maximum field), or one that is not of wavelength (a Raman response's densities and temperature). - - Parameters - ---------- - model : OptmatModel - The model evaluated. - material : str - The material it serves. - parts : sequence of str - The phrases naming what lay outside. """ logs = _LOGS.get() if not logs: diff --git a/src/solprop/physdata/equation_of_state.py b/src/solprop/physdata/equation_of_state.py index e84f104c..a7f81bbe 100644 --- a/src/solprop/physdata/equation_of_state.py +++ b/src/solprop/physdata/equation_of_state.py @@ -38,19 +38,9 @@ def _optmat_density(gas: Gas, pressure_bar: float, temperature: float) -> float: """Return optmat's real-gas number density (cached). - Parameters - ---------- - gas : Gas - The species. - pressure_bar : float - Absolute pressure in bar, > 0. - temperature : float - Temperature in K, > 0. - - Returns - ------- - float - Number density in m^-3. + - ``pressure_bar``: absolute pressure in bar, > 0. + - ``temperature``: temperature in K, > 0. + - Returns: number density in m^-3. """ # species imports this module for the ideal-gas law, so the adapter is imported # where it is needed. @@ -63,19 +53,9 @@ def _optmat_density(gas: Gas, pressure_bar: float, temperature: float) -> float: def _optmat_pressure(gas: Gas, number_density: float, temperature: float) -> float: """Return optmat's real-gas pressure at a number density (cached). - Parameters - ---------- - gas : Gas - The species. - number_density : float - Number density in m^-3, > 0. - temperature : float - Temperature in K, > 0. - - Returns - ------- - float - Absolute pressure in bar. + - ``number_density``: number density in m^-3, > 0. + - ``temperature``: temperature in K, > 0. + - Returns: absolute pressure in bar. """ from solprop.physdata.species import OptmatGas # noqa: PLC0415 - as above @@ -85,17 +65,8 @@ def _optmat_pressure(gas: Gas, number_density: float, temperature: float) -> flo def _check_temperature(temperature: object) -> None: """Reject a non-physical temperature at the boundary rather than inside CoolProp. - Parameters - ---------- - temperature : object - Temperature in K; typed ``object`` because refusing what is not a number, - a temperature path above all, is this check's job. - - Raises - ------ - MaterialError - If the temperature is not a positive number: a temperature path along z is - refused by name, since one state has one temperature. + - ``temperature``: temperature in K; typed ``object`` because refusing what is not a + number, a temperature path above all, is this check's job. """ if isinstance(temperature, bool) or not isinstance(temperature, numbers.Real): raise MaterialError( diff --git a/src/solprop/physdata/glasses.py b/src/solprop/physdata/glasses.py index d67837fd..9a791347 100644 --- a/src/solprop/physdata/glasses.py +++ b/src/solprop/physdata/glasses.py @@ -82,20 +82,8 @@ def _as_omega(omega: ArrayLike | float | int) -> Float[np.ndarray, "*shape"]: """Validate an angular-frequency argument and return it as a float array. - Parameters - ---------- - omega : array_like - Angular frequency in rad/s, >= 0 and finite. A scalar, a sequence or an array. - - Returns - ------- - ndarray - The same values as a float array, 0-d for scalar input. - - Raises - ------ - MaterialError - If the values are not real, or any of them is negative or not finite. + - ``omega``: angular frequency in rad/s, >= 0 and finite. A scalar, a sequence or an + array. """ # numpy casts a complex argument to float with a warning rather than an error, so # it is rejected here before the conversion. @@ -147,29 +135,14 @@ def _index_and_derivative( ) -> tuple[Complex[np.ndarray, "*shape"], Complex[np.ndarray, "*shape"]]: """Return the complex index and its frequency derivative. - Parameters - ---------- - omega : ndarray - Angular frequencies in rad/s, >= 0, already validated. - - Returns - ------- - tuple of ndarray - ``n(omega)`` and ``dn/domega`` in s/rad. + - ``omega``: angular frequencies in rad/s, >= 0, already validated. + - Returns: ``n(omega)`` and ``dn/domega`` in s/rad. """ def _check_range(self, omega: Float[np.ndarray, "*shape"]) -> None: """Warn if any frequency lies outside the validity range of the source. - Parameters - ---------- - omega : ndarray - Angular frequencies in rad/s, >= 0. - - Warns - ----- - MaterialRangeWarning - If any requested wavelength lies outside ``wavelength_range``. + - ``omega``: angular frequencies in rad/s, >= 0. """ shortest_valid, longest_valid = self.wavelength_range highest, lowest = float(np.max(omega)), float(np.min(omega)) @@ -347,15 +320,8 @@ def _index_and_derivative( ) -> tuple[Complex[np.ndarray, "*shape"], Complex[np.ndarray, "*shape"]]: """Return the index and its frequency derivative from the pole sum. - Parameters - ---------- - omega : ndarray - Angular frequencies in rad/s, >= 0. - - Returns - ------- - tuple of ndarray - ``n(omega)`` and ``dn/domega``. + - ``omega``: angular frequencies in rad/s, >= 0. + - Returns: ``n(omega)`` and ``dn/domega``. """ # u = 1/lambda[um]^2 = (omega/_MICRON_OMEGA)^2, so du/domega = 2 u / omega. u = np.asarray((omega / _MICRON_OMEGA) ** 2) @@ -405,15 +371,8 @@ def _index_and_derivative( ) -> tuple[Complex[np.ndarray, "*shape"], Complex[np.ndarray, "*shape"]]: """Return the index and its frequency derivative from the splines. - Parameters - ---------- - omega : ndarray - Angular frequencies in rad/s, >= 0. - - Returns - ------- - tuple of ndarray - ``n(omega)`` and ``dn/domega``. + - ``omega``: angular frequencies in rad/s, >= 0. + - Returns: ``n(omega)`` and ``dn/domega``. """ lowest, highest = self._log_wavelength_limits with np.errstate(divide="ignore"): @@ -460,15 +419,8 @@ def _index_and_derivative( ) -> tuple[Complex[np.ndarray, "*shape"], Complex[np.ndarray, "*shape"]]: """Return the combined index and its frequency derivative. - Parameters - ---------- - omega : ndarray - Angular frequencies in rad/s, >= 0. - - Returns - ------- - tuple of ndarray - ``n(omega)`` and ``dn/domega``. + - ``omega``: angular frequencies in rad/s, >= 0. + - Returns: ``n(omega)`` and ``dn/domega``. """ real, dreal = self.real_part._index_and_derivative(omega) imaginary, dimaginary = self.absorption._index_and_derivative(omega) @@ -598,10 +550,8 @@ def __post_init__(self) -> None: def _identity_key(self) -> tuple[str, bool, float]: """Return what the index depends on: the digest and the approximation. - Returns - ------- - tuple - The model's identity digest, whether it is transparent, and the bound. + - Returns: the model's identity digest, whether it is transparent, and the + bound. """ return ( _optmat.record(self.model).digest, @@ -838,11 +788,8 @@ def material_records(self) -> tuple[MaterialRecord, ...]: def _check_range(self, omega: Float[np.ndarray, "*shape"]) -> None: """Leave the range check to optmat's qualified intervals. - Parameters - ---------- - omega : ndarray - Angular frequencies in rad/s; the bridge reports any part outside the - model's intervals when the index is evaluated. + - ``omega``: angular frequencies in rad/s; the bridge reports any part outside + the model's intervals when the index is evaluated. """ def _record_dropped( @@ -852,18 +799,7 @@ def _record_dropped( ) -> None: """Record the loss a transparent index drops, and warn once beyond the bound. - Parameters - ---------- - extinction : numpy.ndarray - The dropped ``k``, dimensionless. - wavelength : numpy.ndarray - The vacuum wavelengths in m it was dropped at. - - Warns - ----- - MaterialRangeWarning - When ``4 pi k / lambda`` exceeds the bound: once per run inside a run's - evaluation context, and once per adapter outside every context. + - ``wavelength``: the vacuum wavelengths in m it was dropped at. """ kappa = np.ravel(np.asarray(extinction, dtype=float)) if not kappa.size: @@ -922,16 +858,9 @@ def _index_and_derivative( ) -> tuple[Complex[np.ndarray, "*shape"], Complex[np.ndarray, "*shape"]]: """Return optmat's complex index and its exact frequency derivative. - Parameters - ---------- - omega : ndarray - Angular frequencies in rad/s, > 0. - - Returns - ------- - tuple of ndarray - ``n(omega)`` and ``dn/domega`` in s/rad; their real parts only for a - transparent index. + - ``omega``: angular frequencies in rad/s, > 0. + - Returns: ``n(omega)`` and ``dn/domega`` in s/rad; their real parts only for a + transparent index. """ wavelength = _optmat.omega_to_wavelength(omega) model = _optmat.untyped(self.model) @@ -939,10 +868,7 @@ def _index_and_derivative( def evaluate() -> tuple[np.ndarray, np.ndarray]: """Evaluate optmat's index and its derivative at the band. - Returns - ------- - tuple of numpy.ndarray - ``n(omega)`` and ``dn/domega``, complex. + - Returns: ``n(omega)`` and ``dn/domega``, complex. """ return ( np.asarray(model.refractive_index(wavelength), dtype=complex), @@ -1030,18 +956,7 @@ def lossless_index[Material: GlassIndex | float | complex]( def _material_id(name: str) -> str: - """Return optmat's material id of a name, or the name for a caller's own. - - Parameters - ---------- - name : str - A material id or alias (``"sapphire"``), or a caller's own name. - - Returns - ------- - str - The canonical id, such as ``"Al2O3"``. - """ + """Return optmat's material id of a name, or the name for a caller's own.""" from optmat.exceptions import OptmatError # noqa: PLC0415 - optmat is lazy from optmat.materials import as_material # noqa: PLC0415 - as above @@ -1052,21 +967,7 @@ def _material_id(name: str) -> str: def _check_axis(material: str, axis: str | None) -> None: - """Refuse an axis that is not a ray of the material. - - Parameters - ---------- - material : str - The material's optmat id. - axis : str or None - The ray asked for. - - Raises - ------ - MaterialError - If a uniaxial crystal is given anything but ``"o"`` or ``"e"``, or an - isotropic material is given an axis. - """ + """Refuse an axis that is not a ray of the material.""" if material in _UNIAXIAL: if axis not in (None, "o", "e"): msg = f"{material} is uniaxial: axis is 'o' or 'e', not {axis!r}" @@ -1081,26 +982,10 @@ def _optmat_solid( ) -> OptmatGlass: """Resolve a built-in solid's optmat model from a name or an object. - Parameters - ---------- - material : str - The material's optmat id: ``"SiO2"``, ``"CaF2"``, ``"MgF2"``, ``"BaF2"``, - ``"Al2O3"``, ``"N-BK7"``, ``"Ag"`` or ``"Al"``. - model : str or OptmatModel - ``"recommended"``, an optmat model name, or a model object, taken as it is. - axis : str or None, optional - ``"o"`` or ``"e"`` for a uniaxial crystal; required with ``"recommended"``. - - Returns - ------- - OptmatGlass - The adapted model. - - Raises - ------ - MaterialError - If a uniaxial crystal's recommended model is asked for without an axis, - optmat has no such model, or the model describes another material or ray. + - ``material``: the material's optmat id: ``"SiO2"``, ``"CaF2"``, ``"MgF2"``, + ``"BaF2"``, ``"Al2O3"``, ``"N-BK7"``, ``"Ag"`` or ``"Al"``. + - ``axis``: ``"o"`` or ``"e"`` for a uniaxial crystal; required with + ``"recommended"``. """ from optmat.exceptions import OptmatError # noqa: PLC0415 - optmat is lazy from optmat.solid.models import registry # noqa: PLC0415 - as above @@ -1146,19 +1031,7 @@ def _resolve( ) -> OptmatGlass: """Resolve a factory's ``model=`` to an optmat solid. - Parameters - ---------- - material : str - The material's optmat id. - model : str or OptmatModel or None - ``None`` or ``"recommended"``, an optmat model name, or a model object. - axis : str or None, optional - The ray of a uniaxial crystal. - - Returns - ------- - OptmatGlass - The adapted model. + - ``model``: ``None`` or ``"recommended"``, an optmat model name, or a model object. """ return _optmat_solid(material, "recommended" if model is None else model, axis) @@ -1631,13 +1504,7 @@ def resolve( @property def _bundle(self) -> _optmat.Untyped: - """Return the optmat bundle for attribute access. - - Returns - ------- - optmat.solid.bundle.SilicaNonlinearBundle - The model. - """ + """Return the optmat bundle for attribute access.""" return _optmat.untyped(self.model) @property diff --git a/src/solprop/physdata/mixtures.py b/src/solprop/physdata/mixtures.py index 7663e111..2ff85b94 100644 --- a/src/solprop/physdata/mixtures.py +++ b/src/solprop/physdata/mixtures.py @@ -67,17 +67,7 @@ def _resolved(key: Gas | str | Species, selection: MaterialSelection | None) -> Species: """Return a supplied species as it is, or resolve a name under a selection. - Parameters - ---------- - key : Gas or str or Species - A built-in gas's name or a supplied species. - selection : MaterialSelection or None - The selection for names; ``None`` is ``MaterialSelection()``. - - Returns - ------- - Species - The species. A supplied one is never matched to a built-in by its name. + - ``selection``: the selection for names; ``None`` is ``MaterialSelection()``. """ if isinstance(key, Species): return key @@ -89,19 +79,11 @@ def _largest_density( ) -> tuple[float, float]: """Return a density path's largest sampled value and the temperature there. - Parameters - ---------- - path : ZProfile - Number density in m^-3 along z. - temperature : float or ZProfile - Temperature in K, or its path along z. - - Returns - ------- - tuple of float - The largest density in m^-3 at the path's breakpoints and evenly spaced - probes (the same samples a :class:`Component` is validated at), and the - temperature in K at that position. + - ``path``: number density in m^-3 along z. + - ``temperature``: temperature in K, or its path along z. + - Returns: the largest density in m^-3 at the path's breakpoints and evenly spaced + probes (the same samples a :class:`Component` is validated at), and the + temperature in K at that position. """ start, end = path.z_span probes = np.unique( @@ -140,22 +122,13 @@ def _dilute_scaling_checks( ) -> tuple[DiluteScalingCheck, ...]: """Measure the error of linear density scaling for optmat-selected populations. - Parameters - ---------- - populations : sequence of tuple - ``(label, species, density path)`` of each population; only those whose - linear law is an :class:`~solprop.physdata.sellmeier.OptmatSusceptibility` - and whose density is not identically zero are checked. - temperature : float or ZProfile - Temperature in K, or its path along z. - omega : numpy.ndarray - Angular frequencies in rad/s of the band, > 0. - - Returns - ------- - tuple of DiluteScalingCheck - One per checked population, at its largest density; one optmat could not - evaluate is returned unmeasured, with its reason. + - ``populations``: ``(label, species, density path)`` of each population; only those + whose linear law is an :class:`~solprop.physdata.sellmeier.OptmatSusceptibility` + and whose density is not identically zero are checked. + - ``temperature``: temperature in K, or its path along z. + - ``omega``: angular frequencies in rad/s of the band, > 0. + - Returns: one per checked population, at its largest density; one optmat could not + evaluate is returned unmeasured, with its reason. """ values = np.asarray(omega, dtype=float).ravel() values = values[values > 0] @@ -194,15 +167,7 @@ def _dilute_scaling_checks( def _warn_dilute_scaling(checks: Sequence[DiluteScalingCheck]) -> None: """Warn once per gas and evaluation context where dilute scaling exceeds its bound. - Parameters - ---------- - checks : sequence of DiluteScalingCheck - The measured checks. - - Warns - ----- - DiluteScalingWarning - For each gas whose error exceeds :data:`DILUTE_SCALING_THRESHOLD`. + The bound is :data:`DILUTE_SCALING_THRESHOLD`. """ for check in checks: if check.exceeded: @@ -229,32 +194,18 @@ def _pressure_path( ) -> ZProfile: """Resolve the pressure vocabulary without introducing a driver dependency. - Parameters - ---------- - value : float or sequence or ZProfile - Total pressure in bar. - length : float or None - Fibre length in m, required for endpoint pairs. - floor : float or None - Minimum endpoint pressure in bar. - radius : float, ZProfile or None, optional - Core radius in m. Given, an endpoint pair or stage table becomes the - flow-aware fill of :func:`solprop.gasflow.steady_fill`; ``None`` keeps the - uniform law and the numerical floor. - vacuum_end : {'choked', 'floor'} or None, optional - How an outer end below its choked pressure is treated; ``None`` chokes - where a radius is given and floors otherwise. - composition : Mapping or None, optional - Species to mole fraction of the fill, whose transport data the flow uses. - temperature : float or ZProfile, optional - Temperature in K, validated, or a varying path of it along z: a gradient then - follows the flow weight ``eta(T) T`` of the composition - (:func:`solprop.gasflow.temperature_weight`) with or without a radius. - - Returns - ------- - ZProfile - The total-pressure profile in bar. + - ``value``: total pressure in bar. + - ``length``: fibre length in m, required for endpoint pairs. + - ``floor``: minimum endpoint pressure in bar. + - ``radius``: core radius in m. Given, an endpoint pair or stage table becomes the + flow-aware fill of :func:`solprop.gasflow.steady_fill`; ``None`` keeps the uniform + law and the numerical floor. + - ``vacuum_end``: how an outer end below its choked pressure is treated; ``None`` + chokes where a radius is given and floors otherwise. + - ``temperature``: temperature in K, validated, or a varying path of it along z: a + gradient then follows the flow weight ``eta(T) T`` of the composition + (:func:`solprop.gasflow.temperature_weight`) with or without a radius. + - Returns: the total-pressure profile in bar. """ if vacuum_end not in (None, "choked", "floor"): raise MaterialError( @@ -336,33 +287,17 @@ def _endless_path( # noqa: PLR0917 - called positionally ) -> ZProfile: """Return a constant pressure or a supplied profile, which have no end to choke. - Parameters - ---------- - value : float or ZProfile - Total pressure in bar. - length : float or None - Fibre length in m, the span of a constant. - vacuum_end : str or None - Refused unless ``None``. - radius : float, ZProfile or None - The guide's core radius in m. A constant pressure ignores it; a varying - pressure profile must already follow a varying radius. - temperature : float or ZProfile, optional - The validated temperature in K. A varying path beside a varying pressure - profile needs the profile to carry its flow weight (:func:`_check_flow_weight`). - composition : Mapping or None, optional - Species to mole fraction of the fill, whose flow weight the profile must carry. - - Returns - ------- - ZProfile - The total-pressure profile in bar. - - Raises - ------ - MaterialError - If the pressure is invalid, `vacuum_end` is given, or a varying radius or - temperature is given beside a varying profile built without it. + - ``value``: total pressure in bar. + - ``length``: fibre length in m, the span of a constant. + - ``vacuum_end``: refused unless ``None``. + - ``radius``: the guide's core radius in m. A constant pressure ignores it; a + varying pressure profile must already follow a varying radius. + - ``temperature``: the validated temperature in K. A varying path beside a varying + pressure profile needs the profile to carry its flow weight + (:func:`_check_flow_weight`). + - ``composition``: species to mole fraction of the fill, whose flow weight the + profile must carry. + - Returns: the total-pressure profile in bar. """ if isinstance(value, ZProfile): if vacuum_end is not None: @@ -427,22 +362,8 @@ def _check_flow_weight( means, at the fill's breakpoints and evenly spaced probes. One check for every door that takes a pressure profile beside a temperature. - Parameters - ---------- - pressure : ZProfile - The pressure profile in bar; only a ``PressureGradient`` is checked. - temperature : float or ZProfile - The validated temperature in K; a number needs nothing. - composition : Gas, str, Species or Mapping - The gas of the flow, or species to mole fractions. - owner : str - What carries the profile, for the message. - - Raises - ------ - MaterialError - If the fill varies and carries no weight, a weight for another gas or - temperature path, or the gas has no transport data to confirm it. + - ``pressure``: the pressure profile in bar; only a ``PressureGradient`` is checked. + - ``temperature``: the validated temperature in K; a number needs nothing. """ if ( not isinstance(temperature, ZProfile) @@ -489,18 +410,7 @@ def _check_flow_weight( def _fractions(values: Sequence[float]) -> tuple[float, ...]: - """Validate a distribution without silently renormalising supplied fractions. - - Parameters - ---------- - values : sequence of float - Non-negative mole fractions summing to one. - - Returns - ------- - tuple of float - Validated fractions in the supplied order. - """ + """Validate a distribution without silently renormalising supplied fractions.""" result = tuple(float(v) for v in values) if not result or any(not math.isfinite(v) or v < 0 for v in result): raise MaterialError("fractions must be finite, non-negative and non-empty") @@ -611,15 +521,7 @@ def check_preionisation(value: object, owner: str = "") -> float | ZProfile: def _preionisation_text(value: float | ZProfile) -> str: """Return a number as ``%g`` and a path by its own short ``repr``, for printing. - Parameters - ---------- - value : float or ZProfile - An initial ionised fraction, or a path of it along z. - - Returns - ------- - str - For example ``'0.1'`` or ``'Linear(0 to 0.2 over 1 m)'``. + - Returns: for example ``'0.1'`` or ``'Linear(0 to 0.2 over 1 m)'``. """ return repr(value) if isinstance(value, ZProfile) else f"{value:g}" @@ -693,15 +595,8 @@ def _checked_density( ) -> Float[np.ndarray, " n_z"]: """Reject negative or non-finite population densities at the requested points. - Parameters - ---------- - z : numpy.ndarray - Positions in m. - - Returns - ------- - numpy.ndarray - Non-negative finite densities in m^-3. + - ``z``: positions in m. + - Returns: Non-negative finite densities in m^-3. """ values = self.density(z) if np.any(~np.isfinite(values)) or np.any(values < 0): @@ -751,10 +646,7 @@ class Mixture(Displayed): def _pressure_text(self) -> str: """Return the shared pressure at the entrance, for printing. - Returns - ------- - str - The pressure in bar, or a note that the populations are independent. + - Returns: the pressure in bar, or a note that the populations are independent. """ if self.shared is None: return "independent populations" @@ -780,10 +672,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. """ fractions = self.fractions or (None,) * len(self.components) rows: list[tuple[str, object]] = [ @@ -1184,19 +1073,9 @@ def _optical_sum( ) -> Num[np.ndarray, "n_z n_omega"]: """Sum the active species in component order without promoting real data. - Parameters - ---------- - omega : numpy.ndarray - Angular frequencies in rad/s. - z : numpy.ndarray - Positions in m. - derivative : bool - Select the frequency derivative instead of susceptibility. - - Returns - ------- - numpy.ndarray - Susceptibility or its derivative, in dimensionless units or s. + - ``omega``: angular frequencies in rad/s. + - ``z``: positions in m. + - Returns: susceptibility or its derivative, in dimensionless units or s. """ result = None for component, density in zip(self.components, self.density(z), strict=True): @@ -1520,15 +1399,8 @@ def serialise(self) -> dict[str, object]: def _path_record(path: ZProfile) -> dict[str, object]: """Preserve a profile's samples and declarations without serialising a callable. - Parameters - ---------- - path : ZProfile - The profile, with its own value units and z in m. - - Returns - ------- - dict - Sampled values, positions in m and breakpoint declarations. + - ``path``: the profile, with its own value units and z in m. + - Returns: sampled values, positions in m and breakpoint declarations. """ start, end = path.z_span positions = np.unique( diff --git a/src/solprop/physdata/nonlinear_data.py b/src/solprop/physdata/nonlinear_data.py index cd1ae859..bb8210c5 100644 --- a/src/solprop/physdata/nonlinear_data.py +++ b/src/solprop/physdata/nonlinear_data.py @@ -111,23 +111,7 @@ def gamma3(gas: Gas | str, *, wavelength: float) -> float: @functools.cache def _kerr(gas: Gas) -> "OptmatKerr": - """Return the Kerr adapter of a gas, once per gas. - - Parameters - ---------- - gas : Gas - The species. - - Returns - ------- - OptmatKerr - Its adapter. - - Raises - ------ - MaterialError - If optmat has no hyperpolarizability model of the gas. - """ + """Return the Kerr adapter of a gas, once per gas.""" if gas is Gas.Air: msg = ( "air has no Kerr coefficient of its own: its populations (N2, O2, Ar) each " @@ -360,22 +344,7 @@ def carriers_agree(first: float, second: float) -> bool: def _checked_carrier(wavelength: float, material: str) -> float: """Validate a carrier wavelength. - Parameters - ---------- - wavelength : float - Vacuum wavelength in m. - material : str - The gas, for the message. - - Returns - ------- - float - The wavelength as a float. - - Raises - ------ - MaterialError - If it is not positive and finite. + - ``wavelength``: vacuum wavelength in m. """ value = float(wavelength) if not (math.isfinite(value) and value > 0.0): @@ -769,18 +738,11 @@ def describe(self) -> str: The gas, its band and each region's wavelengths in nm. """ - def span(interval: tuple[float, float] | None) -> str | None: + def _span(interval: tuple[float, float] | None) -> str | None: """Return an interval of wavelengths in nm, or ``None``. - Parameters - ---------- - interval : tuple of float or None - Shortest and longest wavelength, m. - - Returns - ------- - str or None - The interval in nm, or ``None`` for no interval. + - ``interval``: shortest and longest wavelength, m. + - Returns: the interval in nm, or ``None`` for no interval. """ if interval is None: return None @@ -789,9 +751,9 @@ def span(interval: tuple[float, float] | None) -> str | None: parts = [ f"{name} {text}" for name, text in ( - ("supported", span(self.supported)), - ("bounded", span(self.bounded)), - ("best effort", span(self.best_effort)), + ("supported", _span(self.supported)), + ("bounded", _span(self.bounded)), + ("best effort", _span(self.best_effort)), ) if text is not None ] @@ -799,8 +761,8 @@ def span(interval: tuple[float, float] | None) -> str | None: "undefined" if self.band_edge is None else f"{1e9 / self.band_edge:.1f} nm" ) return ( - f"{self.material}: band {span(self.band)} is {', '.join(parts)} (legs held " - f"at {1e9 / self.leg_bound:.1f} nm, band edge {edge}, " + f"{self.material}: band {_span(self.band)} is {', '.join(parts)} " + f"(legs held at {1e9 / self.leg_bound:.1f} nm, band edge {edge}, " f"{self.polarisation} polarisation)" ) @@ -1131,15 +1093,8 @@ def regions( def _edge(value: float | None) -> float | None: """Return a band edge as a float, keeping an undefined one undefined. - Parameters - ---------- - value : float or None - The edge optmat's record derives, m^-1. - - Returns - ------- - float or None - The edge as a Python float, or ``None``. + - ``value``: the edge optmat's record derives, m^-1. + - Returns: the edge as a Python float, or ``None``. """ return None if value is None else float(value) @@ -1147,15 +1102,8 @@ def _edge(value: float | None) -> float | None: def _wavelengths(interval: tuple[float, float]) -> tuple[float, float]: """Return an interval of wavenumbers as its shortest and longest wavelengths. - Parameters - ---------- - interval : tuple of float - Lower and upper vacuum wavenumber, m^-1, positive. - - Returns - ------- - tuple of float - ``(1 / upper, 1 / lower)``, m. + - ``interval``: lower and upper vacuum wavenumber, m^-1, positive. + - Returns: ``(1 / upper, 1 / lower)``, m. """ return (1.0 / interval[1], 1.0 / interval[0]) diff --git a/src/solprop/physdata/selection.py b/src/solprop/physdata/selection.py index dd38b736..cf301023 100644 --- a/src/solprop/physdata/selection.py +++ b/src/solprop/physdata/selection.py @@ -181,25 +181,7 @@ def identity(self) -> ModelIdentityLike: def _choice(value: object, field: str) -> ModelChoice: - """Validate one model choice: a non-empty name or an optmat model object. - - Parameters - ---------- - value : object - The choice as given. - field : str - The selection field, for the message. - - Returns - ------- - ModelChoice - The choice, a name stripped of surrounding space. - - Raises - ------ - MaterialError - If the choice is neither a non-empty string nor an object with an identity. - """ + """Validate one model choice: a non-empty name or an optmat model object.""" if isinstance(value, str): name = value.strip() if not name: @@ -216,18 +198,7 @@ def _choice(value: object, field: str) -> ModelChoice: def _choice_record(value: ModelChoice) -> str | dict[str, object]: - """Return a choice as plain values: its name, or a model object's record. - - Parameters - ---------- - value : ModelChoice - A validated choice. - - Returns - ------- - str or dict - The name, or the canonical record of the model's identity. - """ + """Return a choice as plain values: its name, or a model object's record.""" if isinstance(value, str): return value return MaterialRecord.from_identity(cast("OptmatModel", value).identity).to_dict() @@ -571,32 +542,13 @@ def to_dict(self) -> Mapping[str, object]: def _canonical(value: object) -> str: """Return plain values as canonical JSON, for ordering and deduplication. - Parameters - ---------- - value : object - Plain values: mappings, sequences, strings, numbers, None. - - Returns - ------- - str - Sorted-key, compact JSON. + - ``value``: plain values: mappings, sequences, strings, numbers, None. """ return json.dumps(value, sort_keys=True, separators=(",", ":"), default=str) def _plain(value: object) -> object: - """Return a value with tuples as lists and mappings as dicts, recursively. - - Parameters - ---------- - value : object - Plain values, possibly nested in tuples and mappings. - - Returns - ------- - object - The same values in JSON's own containers. - """ + """Return a value with tuples as lists and mappings as dicts, recursively.""" if isinstance(value, Mapping): return {str(key): _plain(item) for key, item in value.items()} if isinstance(value, (list, tuple)): @@ -605,18 +557,7 @@ def _plain(value: object) -> object: def _entries(values: Iterable[Mapping[str, object]]) -> tuple[dict[str, object], ...]: - """Return entries as plain dicts, each once, in canonical order. - - Parameters - ---------- - values : iterable of collections.abc.Mapping - The entries, possibly repeated. - - Returns - ------- - tuple of dict - The distinct entries sorted by their canonical JSON. - """ + """Return entries as plain dicts, each once, in canonical order.""" distinct = { _canonical(_plain(entry)): cast("dict[str, object]", _plain(entry)) for entry in values @@ -627,21 +568,7 @@ def _entries(values: Iterable[Mapping[str, object]]) -> tuple[dict[str, object], def _keyed( entries: Sequence[Mapping[str, object]], fields: Sequence[str] ) -> dict[str, object]: - """Key entries by the named fields, so a difference names the entry that moved. - - Parameters - ---------- - entries : sequence of collections.abc.Mapping - Distinct entries in canonical order. - fields : sequence of str - The fields whose values, joined by ``:``, make an entry's key. Entries - that share a key take a numbered suffix, ``#1``, ``#2``, in their order. - - Returns - ------- - dict - The entries by key. - """ + """Key entries by the named fields, so a difference names the entry that moved.""" names = [ ":".join(str(entry.get(name) or "-") for name in fields) for entry in entries ] @@ -661,17 +588,7 @@ def _stored_entries( ) -> list[Mapping[str, object]]: """Return a stored manifest field's entries, keyed or listed. - Parameters - ---------- - document : collections.abc.Mapping - A stored manifest. - name : str - The field. - - Returns - ------- - list of collections.abc.Mapping - Its entries; none when it is absent. + - Returns: its entries; none when it is absent. """ stored = document.get(name) or {} items = stored.values() if isinstance(stored, Mapping) else stored @@ -685,15 +602,7 @@ def _stored_entries( def _solid_identity(solid: Mapping[str, object]) -> dict[str, object]: """Return the hashed part of a solid's entry: identity and policy. - Parameters - ---------- - solid : collections.abc.Mapping - One entry of :attr:`MaterialManifest.solids`. - - Returns - ------- - dict - Its identifying fields, and the absorption bound of a transparent one. + - Returns: its identifying fields, and the absorption bound of a transparent one. """ identity = {name: solid.get(name) for name in _SOLID_POLICY} if solid.get("transparent"): diff --git a/src/solprop/physdata/sellmeier.py b/src/solprop/physdata/sellmeier.py index de4187b0..80d4cda8 100644 --- a/src/solprop/physdata/sellmeier.py +++ b/src/solprop/physdata/sellmeier.py @@ -101,17 +101,7 @@ class ReferenceState: def _reference_density(gas: Gas, state: ReferenceState) -> float: """Return the number density at a model's reference state (cached). - Parameters - ---------- - gas : Gas - The species. - state : ReferenceState - The state the source reports its refractivity at. - - Returns - ------- - float - Number density in m^-3, from optmat's equation of state. + - Returns: number density in m^-3, from optmat's equation of state. """ return density(gas, state.pressure_bar, state.temperature) @@ -119,20 +109,8 @@ def _reference_density(gas: Gas, state: ReferenceState) -> float: def _as_omega(omega: ArrayLike | float | int) -> Float[np.ndarray, "*shape"]: """Validate an angular-frequency argument and return it as a float array. - Parameters - ---------- - omega : array_like - Angular frequency in rad/s, >= 0 and finite. A scalar, a sequence or an array. - - Returns - ------- - ndarray - The same values as a float array, 0-d for scalar input. - - Raises - ------ - MaterialError - If the values are not real, or any of them is negative or not finite. + - ``omega``: angular frequency in rad/s, >= 0 and finite. A scalar, a sequence or an + array. """ # numpy casts a complex argument to float with a warning rather than an error, so # it is rejected here before the conversion. @@ -155,16 +133,9 @@ def _as_omega(omega: ArrayLike | float | int) -> Float[np.ndarray, "*shape"]: def _wavelength_extremes(omega: Float[np.ndarray, "*shape"]) -> tuple[float, float]: """Return the shortest and longest wavelength of a set of angular frequencies. - Parameters - ---------- - omega : ndarray - Angular frequencies in rad/s, >= 0. - - Returns - ------- - tuple of float - Shortest and longest wavelength in m; the longest is infinite if any - frequency is zero. + - ``omega``: angular frequencies in rad/s, >= 0. + - Returns: shortest and longest wavelength in m; the longest is infinite if any + frequency is zero. """ highest, lowest = float(np.max(omega)), float(np.min(omega)) shortest = 2.0 * math.pi * C / highest if highest > 0.0 else math.inf @@ -208,15 +179,8 @@ def _chi_and_derivative( ) -> tuple[Float[np.ndarray, "*shape"], Float[np.ndarray, "*shape"]]: """Return the susceptibility at the reference state and its derivative in `u`. - Parameters - ---------- - u : ndarray - Inverse squared wavelength in um^-2. - - Returns - ------- - tuple of ndarray - ``chi(u)`` at the reference state (dimensionless) and ``dchi/du``. + - ``u``: inverse squared wavelength in um^-2. + - Returns: ``chi(u)`` at the reference state (dimensionless) and ``dchi/du``. """ @property @@ -244,15 +208,7 @@ def absorbing(self) -> bool: def _check_range(self, omega: Float[np.ndarray, "*shape"]) -> None: """Warn if any frequency lies outside the validity range of the source. - Parameters - ---------- - omega : ndarray - Angular frequencies in rad/s. - - Warns - ----- - MaterialRangeWarning - If any requested wavelength lies outside ``wavelength_range``. + - ``omega``: angular frequencies in rad/s. """ shortest_valid, longest_valid = self.wavelength_range shortest, longest = _wavelength_extremes(omega) @@ -374,15 +330,8 @@ def _chi_and_derivative( ) -> tuple[Float[np.ndarray, "*shape"], Float[np.ndarray, "*shape"]]: """Return ``sum b_i / (1 - c_i u)`` and its derivative in `u`. - Parameters - ---------- - u : ndarray - Inverse squared wavelength in um^-2. - - Returns - ------- - tuple of ndarray - The susceptibility at the reference state and its derivative. + - ``u``: inverse squared wavelength in um^-2. + - Returns: the susceptibility at the reference state and its derivative. """ chi = np.zeros_like(u) dchi_du = np.zeros_like(u) @@ -409,15 +358,8 @@ def _refractivity_and_derivative( ) -> tuple[Float[np.ndarray, "*shape"], Float[np.ndarray, "*shape"]]: """Return ``n - 1`` at the reference state and its derivative in `u`. - Parameters - ---------- - u : ndarray - Inverse squared wavelength in um^-2. - - Returns - ------- - tuple of ndarray - The refractivity and its derivative with respect to `u`. + - ``u``: inverse squared wavelength in um^-2. + - Returns: the refractivity and its derivative with respect to `u`. """ def _chi_and_derivative( @@ -425,15 +367,8 @@ def _chi_and_derivative( ) -> tuple[Float[np.ndarray, "*shape"], Float[np.ndarray, "*shape"]]: """Return the susceptibility and its derivative from the refractivity. - Parameters - ---------- - u : ndarray - Inverse squared wavelength in um^-2. - - Returns - ------- - tuple of ndarray - ``(1 + dn)^2 - 1`` and its derivative with respect to `u`. + - ``u``: inverse squared wavelength in um^-2. + - Returns: ``(1 + dn)^2 - 1`` and its derivative with respect to `u`. """ refractivity, drefractivity_du = self._refractivity_and_derivative(u) # (1 + dn)^2 - 1 written as dn(2 + dn): no cancellation for dn ~ 1e-4. @@ -470,15 +405,8 @@ def _refractivity_and_derivative( ) -> tuple[Float[np.ndarray, "*shape"], Float[np.ndarray, "*shape"]]: """Return ``offset + sum b_i / (c_i - u)`` and its derivative in `u`. - Parameters - ---------- - u : ndarray - Inverse squared wavelength in um^-2. - - Returns - ------- - tuple of ndarray - The refractivity and its derivative with respect to `u`. + - ``u``: inverse squared wavelength in um^-2. + - Returns: the refractivity and its derivative with respect to `u`. """ refractivity = np.full_like(u, self.offset) derivative = np.zeros_like(u) @@ -516,15 +444,8 @@ def _refractivity_and_derivative( ) -> tuple[Float[np.ndarray, "*shape"], Float[np.ndarray, "*shape"]]: """Return ``n - 1`` and its derivative in `u` for the wavenumber form. - Parameters - ---------- - u : ndarray - Inverse squared wavelength in um^-2. - - Returns - ------- - tuple of ndarray - The refractivity and its derivative with respect to `u`. + - ``u``: inverse squared wavelength in um^-2. + - Returns: the refractivity and its derivative with respect to `u`. """ # nu[cm^-1] = 1e4 / lambda[um], so nu^2 = 1e8 u; the 1e-8 of the published # form and the 1e8 of the chain rule cancel in the derivative. @@ -538,16 +459,7 @@ def _refractivity_and_derivative( def _model_absorbs(model: object) -> bool: """Return whether an optmat gas model, or an air model's constituents, absorb. - Parameters - ---------- - model : OptmatModel - A gas dispersion model (with ``absorbing``) or an air model (with - ``models``, its constituents'). - - Returns - ------- - bool - True if any part of the model carries an imaginary polarizability. + - Returns: True if any part of the model carries an imaginary polarizability. """ constituents = getattr(model, "models", None) if constituents is not None: @@ -558,23 +470,7 @@ def _model_absorbs(model: object) -> bool: def _optmat_gas_model(gas: Gas, choice: ModelChoice) -> _optmat.Untyped: """Resolve an optmat dispersion model of a gas from a name or an object. - Parameters - ---------- - gas : Gas - The species the model must describe. - choice : str or OptmatModel - ``"recommended"``, an optmat model name (for air, a composition name), or a - model object. - - Returns - ------- - OptmatModel - The model, checked to describe `gas`. - - Raises - ------ - MaterialError - If optmat has no such model, or the model describes another gas. + - ``gas``: the species the model must describe. """ from optmat.exceptions import OptmatError # noqa: PLC0415 - optmat is lazy from optmat.gas.air import AIR_COMPOSITIONS, DEFAULT_AIR, air_model # noqa: PLC0415 @@ -742,20 +638,7 @@ def _chi_and_derivative( ) -> tuple[Float[np.ndarray, "*shape"], Float[np.ndarray, "*shape"]]: """Refuse a reference-state susceptibility, which a polarizability has not. - Parameters - ---------- - u : ndarray - Inverse squared wavelength in um^-2. - - Returns - ------- - tuple of ndarray - Never returns. - - Raises - ------ - MaterialError - Always: use :meth:`susceptibility`, which divides by no density. + - ``u``: inverse squared wavelength in um^-2. """ msg = ( f"{self.gas.value} {self.source}: an optmat polarizability is per particle " @@ -801,19 +684,8 @@ def _evaluate( ) -> Inexact[np.ndarray, "*shape"]: """Evaluate one optmat method at angular frequencies, divided by eps0. - Parameters - ---------- - omega : array_like - Angular frequency in rad/s, >= 0; zero is the static limit. - method : str - ``"complex_polarizability"`` or ``"dpolarizability_domega"``. - quantity : str - What is evaluated, for messages. - - Returns - ------- - ndarray - The method's value over eps0, real for a transparent model. + - ``omega``: angular frequency in rad/s, >= 0; zero is the static limit. + - ``method``: ``"complex_polarizability"`` or ``"dpolarizability_domega"``. """ values = _as_omega(omega) wavelength = _optmat.omega_to_wavelength(values) @@ -970,18 +842,7 @@ def dilute_scaling_error( @functools.cache def _default_law(gas: Gas) -> "OptmatSusceptibility": - """Return optmat's recommended law of a gas, one adapter per gas. - - Parameters - ---------- - gas : Gas - The species. - - Returns - ------- - OptmatSusceptibility - The adapter, which keeps its own evaluations. - """ + """Return optmat's recommended law of a gas, one adapter per gas.""" return OptmatSusceptibility.resolve(gas) @@ -1113,27 +974,9 @@ def _one_plus_chi( ) -> Float[np.ndarray, "*shape"]: """Return ``1 + chi1``, checked to be positive so that the square root is real. - Parameters - ---------- - gas : Gas or str - The species. - omega : array_like - Angular frequency in rad/s. - pressure : float - Absolute pressure in bar. - temperature : float - Temperature in K. - - Returns - ------- - ndarray - ``1 + chi1``. - - Raises - ------ - MaterialError - If the susceptibility is at or below -1, which happens only when a model is - extrapolated onto a pole. + - ``omega``: angular frequency in rad/s. + - ``pressure``: absolute pressure in bar. + - ``temperature``: temperature in K. """ values = np.asarray( 1.0 + chi1(gas, omega, pressure=pressure, temperature=temperature) diff --git a/src/solprop/physdata/species.py b/src/solprop/physdata/species.py index 45b013ee..932e630e 100644 --- a/src/solprop/physdata/species.py +++ b/src/solprop/physdata/species.py @@ -199,13 +199,7 @@ def key(self) -> Gas: @functools.cached_property def _equation(self) -> _optmat.Untyped: - """Return optmat's equation of state of the gas, resolved once. - - Returns - ------- - optmat.gas.eos.EquationOfState - The gas's, or the pseudo-pure dry air's for ``Gas.Air``. - """ + """Return optmat's equation of state of the gas, resolved once.""" from optmat.gas.eos import ( # noqa: PLC0415 - optmat is imported lazily air_equation_of_state, equation_of_state, @@ -219,11 +213,8 @@ def _equation(self) -> _optmat.Untyped: def _transport(self) -> tuple[tuple[_optmat.Untyped, float], ...]: """Return optmat's transport model of each constituent, with its fraction. - Returns - ------- - tuple - ``(TransportModel, mole fraction)`` pairs; one, with fraction 1, for a - pure gas. + - Returns: ``(TransportModel, mole fraction)`` pairs; one, with fraction 1, for + a pure gas. """ from optmat.gas.transport import transport_model # noqa: PLC0415 - lazily @@ -263,19 +254,9 @@ def _state_error( ) -> MaterialError: """Return the error for a state optmat's equation of state refuses. - Parameters - ---------- - error : Exception - The ``StateError`` optmat raised. - pressure_bar : float - The pressure asked for, in bar, or NaN when a density was given. - temperature : float - The temperature asked for, in K. - - Returns - ------- - MaterialError - The refusal, naming the saturation pressure below the critical point. + - ``pressure_bar``: the pressure asked for, in bar, or NaN when a density was + given. + - ``temperature``: the temperature asked for, in K. """ equation = self._equation low, high = equation.temperature_range @@ -395,20 +376,7 @@ def temperature_range(self) -> tuple[float, float]: def _checked(self, temperature: float) -> float: """Refuse a temperature outside the transport range; apply the status policy. - Parameters - ---------- - temperature : float - Temperature in K. - - Returns - ------- - float - The temperature as a float. - - Raises - ------ - MaterialError - If the temperature is not finite or lies outside the range. + - ``temperature``: temperature in K. """ low, high = self.temperature_range value = float(temperature) @@ -636,15 +604,8 @@ def __post_init__(self) -> None: def _frequencies(self, omega: ArrayLike | float) -> Float[np.ndarray, "*shape"]: """Check requested frequencies against the declared optical support. - Parameters - ---------- - omega : array_like or float - Angular frequencies in rad/s. - - Returns - ------- - numpy.ndarray - Finite frequencies in rad/s within the table support. + - ``omega``: angular frequencies in rad/s. + - Returns: finite frequencies in rad/s within the table support. """ values = np.asarray(omega, dtype=float) low, high = self.wavelength_range @@ -912,18 +873,7 @@ def fill_species(gas: Gas | str, susceptibility: GasSusceptibility | None) -> Sp @functools.cache def _default_species(gas: Gas) -> Species: - """Build :func:`default_species` once per gas, keyed by the :class:`Gas` member. - - Parameters - ---------- - gas : Gas - The gas. - - Returns - ------- - Species - ``species(gas)``. - """ + """Build :func:`default_species` once per gas, keyed by the :class:`Gas` member.""" return species(gas) @@ -953,16 +903,8 @@ def built_in_population(material: Species) -> bool: def _optmat_ionisation(gas: Gas) -> IonisationData: """Return optmat's curated first ionisation energy of a gas, once per gas. - Parameters - ---------- - gas : Gas - A built-in gas other than air. - - Returns - ------- - IonisationData - The energy in J, cited to optmat's dataset key. optmat curates one for every - built-in gas but air, which its callers refuse first. + - Returns: the energy in J, cited to optmat's dataset key. optmat curates one for + every built-in gas but air, which its callers refuse first. """ from optmat.gas.ionisation import ionisation_parameters # noqa: PLC0415 - lazy diff --git a/src/solprop/physdata/transport.py b/src/solprop/physdata/transport.py index b7ba6edd..72158a5a 100644 --- a/src/solprop/physdata/transport.py +++ b/src/solprop/physdata/transport.py @@ -100,25 +100,7 @@ def temperature_range(gas: GasLike) -> tuple[float, float]: def _member(gas: GasLike, quantity: str) -> OptmatGas: - """Return the data source behind a declaration, or refuse a custom species. - - Parameters - ---------- - gas : Gas, str or Species - The gas. - quantity : str - The property asked for, named in the error. - - Returns - ------- - OptmatGas - The optmat equation of state and transport of a name or a species. - - Raises - ------ - MaterialError - If the gas is unknown, or a custom species without optmat's data. - """ + """Return the data source behind a declaration, or refuse a custom species.""" if isinstance(gas, Species): state = gas.equation_of_state if isinstance(state, OptmatGas): @@ -138,23 +120,8 @@ def _composition( ) -> tuple[tuple[OptmatGas, float], ...]: """Resolve a gas or a mole-fraction mapping to its data sources and fractions. - Parameters - ---------- - gas : Gas, str, Species or Mapping - One gas, or gases to mole fractions summing to one. - quantity : str - The property asked for, named in errors. - - Returns - ------- - tuple - ``(member, fraction)`` pairs with positive fractions, one per distinct gas. - - Raises - ------ - MaterialError - If a fraction is negative or not finite, the fractions do not sum to one, or - a gas carries no optmat data. + - Returns: ``(member, fraction)`` pairs with positive fractions, one per distinct + gas. """ if not isinstance(gas, Mapping): return ((_member(gas, quantity), 1.0),) @@ -175,18 +142,10 @@ def _composition( def _viscosity(parts: tuple[tuple[OptmatGas, float], ...], temperature: float) -> float: """Return a composition's dilute viscosity. - Parameters - ---------- - parts : tuple - ``(OptmatGas, fraction)`` pairs from :func:`_composition`. - temperature : float - Temperature in K. - - Returns - ------- - float - Dynamic viscosity in Pa s: the gas's own, or optmat's Wilke rule over the - constituents (air's resolved into its own). + - ``parts``: ``(OptmatGas, fraction)`` pairs from :func:`_composition`. + - ``temperature``: temperature in K. + - Returns: dynamic viscosity in Pa s: the gas's own, or optmat's Wilke rule over the + constituents (air's resolved into its own). """ from optmat.gas.transport import mixture_viscosity # noqa: PLC0415 - optmat is lazy From a1d62cca84997d56f4507a7d2bcb7b15dea82aa4 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:19:38 +0000 Subject: [PATCH 29/50] Test engineering's None and NaN text, which a dropped doctest covered Co-Authored-By: Claude Opus 5.5 --- tests/test_display.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_display.py b/tests/test_display.py index 4b4f262d..433fc2bd 100644 --- a/tests/test_display.py +++ b/tests/test_display.py @@ -864,6 +864,8 @@ def test_the_formatter_handles_the_awkward_numbers(): assert _display.engineering(1e30, "W") == "1e+12 EW" assert _display.engineering(1e-30, "s") == "1e-12 as" assert _display.engineering(float("inf"), "m") == "inf" + assert _display.engineering(float("nan"), "s") == "nan" + assert _display.engineering(None, "s") == "none" assert _display.engineering(12.3456) == "12.35" assert _display.summarise(np.array([])) == "<0 values>" assert _display.summarise(3.0) == "" From 09964fd72c4d5fc4033f339f2fdc6b8db729beb4 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:20:35 +0000 Subject: [PATCH 30/50] Restore warning policies and conventions in solprop.physdata private docs Co-Authored-By: Claude Opus 5.5 --- src/solprop/physdata/_optmat.py | 6 ++++++ src/solprop/physdata/equation_of_state.py | 3 ++- src/solprop/physdata/glasses.py | 5 +++++ src/solprop/physdata/mixtures.py | 2 ++ src/solprop/physdata/selection.py | 8 ++++++-- src/solprop/physdata/sellmeier.py | 4 ++++ src/solprop/physdata/species.py | 5 ++++- 7 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/solprop/physdata/_optmat.py b/src/solprop/physdata/_optmat.py index 825cbbe3..a5f1d7db 100644 --- a/src/solprop/physdata/_optmat.py +++ b/src/solprop/physdata/_optmat.py @@ -347,6 +347,9 @@ def translated( - ``wavelength``: the vacuum wavelengths in m the body evaluates, for range messages. - ``state``: the state asked for, for error messages: ``"30 bar and 293.15 K"``. + - Warns :class:`MaterialRangeWarning` once per material and evaluation context if + the band leaves the model's qualified intervals or an absorbing model's real part + is asked for. """ with _captured(model, quantity, material=material, state=state) as caught: yield @@ -431,6 +434,9 @@ def evaluate( - ``omega``: the validated angular frequencies in rad/s, the key's second part. - ``wavelength``: the vacuum wavelengths of `omega` in m, for range messages. + - Returns: fresh, writeable copies of the arrays `compute` returns; nothing is + stored if optmat refuses the evaluation. Range warnings are raised as + :func:`translated` raises them, at every use whether stored or not. """ values = np.asarray(omega) key = (quantity, values.shape, values.dtype.str, values.tobytes()) diff --git a/src/solprop/physdata/equation_of_state.py b/src/solprop/physdata/equation_of_state.py index a7f81bbe..2ae58bba 100644 --- a/src/solprop/physdata/equation_of_state.py +++ b/src/solprop/physdata/equation_of_state.py @@ -66,7 +66,8 @@ def _check_temperature(temperature: object) -> None: """Reject a non-physical temperature at the boundary rather than inside CoolProp. - ``temperature``: temperature in K; typed ``object`` because refusing what is not a - number, a temperature path above all, is this check's job. + number, a temperature path above all, is this check's job: one state has one + temperature. """ if isinstance(temperature, bool) or not isinstance(temperature, numbers.Real): raise MaterialError( diff --git a/src/solprop/physdata/glasses.py b/src/solprop/physdata/glasses.py index 9a791347..abe03a0d 100644 --- a/src/solprop/physdata/glasses.py +++ b/src/solprop/physdata/glasses.py @@ -799,6 +799,9 @@ def _record_dropped( ) -> None: """Record the loss a transparent index drops, and warn once beyond the bound. + The warning, when ``4 pi k / lambda`` exceeds the bound, is once per run inside + a run's evaluation context, and once per adapter outside every context. + - ``wavelength``: the vacuum wavelengths in m it was dropped at. """ kappa = np.ravel(np.asarray(extinction, dtype=float)) @@ -982,6 +985,8 @@ def _optmat_solid( ) -> OptmatGlass: """Resolve a built-in solid's optmat model from a name or an object. + - ``model``: ``"recommended"``, an optmat model name, or a model object, taken as it + is. - ``material``: the material's optmat id: ``"SiO2"``, ``"CaF2"``, ``"MgF2"``, ``"BaF2"``, ``"Al2O3"``, ``"N-BK7"``, ``"Ag"`` or ``"Al"``. - ``axis``: ``"o"`` or ``"e"`` for a uniaxial crystal; required with diff --git a/src/solprop/physdata/mixtures.py b/src/solprop/physdata/mixtures.py index 2ff85b94..5e773812 100644 --- a/src/solprop/physdata/mixtures.py +++ b/src/solprop/physdata/mixtures.py @@ -67,6 +67,8 @@ def _resolved(key: Gas | str | Species, selection: MaterialSelection | None) -> Species: """Return a supplied species as it is, or resolve a name under a selection. + A supplied species is never matched to a built-in by its name. + - ``selection``: the selection for names; ``None`` is ``MaterialSelection()``. """ if isinstance(key, Species): diff --git a/src/solprop/physdata/selection.py b/src/solprop/physdata/selection.py index cf301023..fd8ce5cc 100644 --- a/src/solprop/physdata/selection.py +++ b/src/solprop/physdata/selection.py @@ -557,7 +557,7 @@ def _plain(value: object) -> object: def _entries(values: Iterable[Mapping[str, object]]) -> tuple[dict[str, object], ...]: - """Return entries as plain dicts, each once, in canonical order.""" + """Return entries as plain dicts, each once, sorted by their canonical JSON.""" distinct = { _canonical(_plain(entry)): cast("dict[str, object]", _plain(entry)) for entry in values @@ -568,7 +568,11 @@ def _entries(values: Iterable[Mapping[str, object]]) -> tuple[dict[str, object], def _keyed( entries: Sequence[Mapping[str, object]], fields: Sequence[str] ) -> dict[str, object]: - """Key entries by the named fields, so a difference names the entry that moved.""" + """Key entries by the named fields, so a difference names the entry that moved. + + The key is the fields' values joined by ``:``; entries that share a key take a + numbered suffix, ``#1``, ``#2``, in their order. + """ names = [ ":".join(str(entry.get(name) or "-") for name in fields) for entry in entries ] diff --git a/src/solprop/physdata/sellmeier.py b/src/solprop/physdata/sellmeier.py index 80d4cda8..df71a9c0 100644 --- a/src/solprop/physdata/sellmeier.py +++ b/src/solprop/physdata/sellmeier.py @@ -471,6 +471,8 @@ def _optmat_gas_model(gas: Gas, choice: ModelChoice) -> _optmat.Untyped: """Resolve an optmat dispersion model of a gas from a name or an object. - ``gas``: the species the model must describe. + - ``choice``: ``"recommended"``, an optmat model name (for air, a composition + name), or a model object. """ from optmat.exceptions import OptmatError # noqa: PLC0415 - optmat is lazy from optmat.gas.air import AIR_COMPOSITIONS, DEFAULT_AIR, air_model # noqa: PLC0415 @@ -977,6 +979,8 @@ def _one_plus_chi( - ``omega``: angular frequency in rad/s. - ``pressure``: absolute pressure in bar. - ``temperature``: temperature in K. + - Raises :class:`MaterialError` at or below -1 for ``chi1``, which happens only + when a model is extrapolated onto a pole. """ values = np.asarray( 1.0 + chi1(gas, omega, pressure=pressure, temperature=temperature) diff --git a/src/solprop/physdata/species.py b/src/solprop/physdata/species.py index 932e630e..3602c6b4 100644 --- a/src/solprop/physdata/species.py +++ b/src/solprop/physdata/species.py @@ -199,7 +199,10 @@ def key(self) -> Gas: @functools.cached_property def _equation(self) -> _optmat.Untyped: - """Return optmat's equation of state of the gas, resolved once.""" + """Return optmat's equation of state of the gas, resolved once. + + For ``Gas.Air`` it is the pseudo-pure dry air's. + """ from optmat.gas.eos import ( # noqa: PLC0415 - optmat is imported lazily air_equation_of_state, equation_of_state, From 9ec0de32a96913d275c4e1b9190e99d8c35dc62d Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:21:14 +0000 Subject: [PATCH 31/50] Slim private docstrings in solprop_native Mechanical: python tools/docstring_diet.py native/src/solprop_native --write --check --report --rename-closures By hand: NativeSession._check_destination's sections (D200 skip). Co-Authored-By: Claude Opus 5.5 --- native/src/solprop_native/__init__.py | 17 +- native/src/solprop_native/_allocation.py | 8 +- native/src/solprop_native/_budget.py | 52 +-- native/src/solprop_native/_counters.py | 38 +-- native/src/solprop_native/_cpu.py | 162 ++-------- native/src/solprop_native/_execution.py | 41 +-- native/src/solprop_native/_helpers.py | 39 +-- native/src/solprop_native/_host_blas.py | 20 +- native/src/solprop_native/_load.py | 115 +------ native/src/solprop_native/_probe.py | 35 +- native/src/solprop_native/_profile.py | 47 +-- native/src/solprop_native/_statistics.py | 386 +++-------------------- native/src/solprop_native/_store.py | 45 +-- native/src/solprop_native/_topology.py | 10 +- native/src/solprop_native/_transverse.py | 222 ++----------- native/src/solprop_native/_wait.py | 8 +- native/src/solprop_native/_watch.py | 159 ++-------- native/src/solprop_native/_wisdom.py | 66 +--- native/src/solprop_native/backend.py | 258 ++------------- native/src/solprop_native/group.py | 184 +---------- native/src/solprop_native/session.py | 366 +++------------------ 21 files changed, 258 insertions(+), 2020 deletions(-) diff --git a/native/src/solprop_native/__init__.py b/native/src/solprop_native/__init__.py index 5ec1553a..a5da943a 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 90cc5e22..f6456a15 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 7c388db9..0adbbdec 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 17229703..2ef13aa0 100644 --- a/native/src/solprop_native/_counters.py +++ b/native/src/solprop_native/_counters.py @@ -151,19 +151,10 @@ 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``. + - ``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 +182,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 3adb62ab..676c95fc 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,7 @@ 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. + - Returns: the names, or ``None`` when the text has no such line. """ for line in cpuinfo.splitlines(): name, separator, value = line.partition(":") @@ -297,17 +266,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 +277,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 +286,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 +301,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 +348,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 +376,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 +385,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 +406,8 @@ 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``. """ 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 3335000f..056332eb 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 f5a59441..4fb62d85 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 2626207a..879be8eb 100644 --- a/native/src/solprop_native/_host_blas.py +++ b/native/src/solprop_native/_host_blas.py @@ -82,24 +82,8 @@ 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. + - 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 55762085..0dfce585 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,9 @@ 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. """ values = np.ascontiguousarray(field, dtype=np.complex128) if values.shape != problem.shape: @@ -187,22 +161,7 @@ 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. """ schedule = stepper.schedule if schedule is None: @@ -263,13 +222,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 +234,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 +244,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 +260,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 +278,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 +307,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 89d55a43..b82fd4e0 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 d26d7b5b..53714c7a 100644 --- a/native/src/solprop_native/_profile.py +++ b/native/src/solprop_native/_profile.py @@ -227,25 +227,8 @@ 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. """ text = json.dumps( { @@ -276,28 +259,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 d023b4dd..07a38741 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() @@ -514,23 +422,7 @@ 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. - """ + """Return the metric's square root as :func:`solprop.stats._whitened` forms it.""" values, vectors = np.linalg.eigh(np.moveaxis(metric, (0, 1), (-2, -1))) if np.any(values <= 0.0): msg = ( @@ -550,21 +442,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 +475,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 +497,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 +544,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 +573,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 +634,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 +674,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 @@ -893,17 +690,7 @@ def metric_refusal(problem: PropagationProblem, names: Sequence[str]) -> str | N 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``. + - Returns: the refusal, or ``None``. """ if _route(problem) != "general" or not _metric_varies(problem): return None @@ -979,24 +766,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 +799,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 +877,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 +999,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 +1049,17 @@ 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`). + - Returns: the plan and the shapes. """ 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 8cb6a6f2..0a283806 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): @@ -357,13 +336,7 @@ def current(self, fingerprint: str) -> Generation | None: return generation 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. - """ + """Write `generation` completely, then make it current; return its file.""" directory = self.directory(generation.fingerprint) directory.mkdir(parents=True, exist_ok=True) path = directory / f"{generation.name}.json" @@ -487,13 +460,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 9caed64d..955c1b86 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 7b31f700..314d9f36 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 6586708d..7b6a71cc 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 25ec7078..45bddf9a 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,31 +353,14 @@ 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``. + - Returns: the value, or ``None``. """ number = float(cast("float", value)) return number if math.isfinite(number) else None @@ -489,25 +409,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 +424,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 +437,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 +465,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 268ce7f1..80b0d547 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,8 @@ 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. """ with _LOCK: found = _load(path) if path is not None else {} @@ -210,13 +192,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 +223,7 @@ 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. """ new = {key: plan for key, plan in measured.items() if key not in profiled} if path is None or not new: @@ -290,16 +254,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 365b5053..9da3ffe6 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 @@ -563,28 +557,10 @@ 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. """ if self._closed: raise BackendError("this backend is closed; its buffers were released") @@ -611,10 +587,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 +600,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 +644,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 +1146,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 +1210,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 +1256,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 @@ -1674,10 +1601,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 +1622,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 +1845,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 +2367,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 +2466,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 +2481,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 +2546,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 +2599,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 +2627,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 +2663,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 +2699,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 +2786,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 +2877,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 +2904,7 @@ 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. """ assert self._native is not None assert self.rate_slot is not None @@ -3116,17 +2941,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 +3194,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 a7520f58..ffc6b130 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: @@ -290,15 +282,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``. + - Returns: the reason, 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 @@ -319,21 +303,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 +323,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 @@ -402,10 +356,7 @@ def __init__(self) -> 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. + - Returns: the joined history and rows. """ return join_attempts(self.chunks) @@ -613,25 +564,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,45 +679,14 @@ 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) return member 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. - """ + """Run every running member to its window's end, in rounds; report it.""" windows = { index: _Window() for index, outcome in enumerate(self._outcomes) @@ -827,18 +729,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 +775,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 +799,7 @@ 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. """ events: list[tuple[Event, ...]] = [()] * len(self._sessions) for index, window in windows.items(): @@ -970,19 +841,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 +857,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 86390f9f..9b79534e 100644 --- a/native/src/solprop_native/session.py +++ b/native/src/solprop_native/session.py @@ -271,19 +271,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,17 +295,7 @@ def _statistics_refusal(problem: PropagationProblem, spec: StatsSpec) -> str | N 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``. + - Returns: the reason, or ``None``. """ names = tuple(spec.names) if not names: @@ -354,20 +332,6 @@ def _refuse_foreign_monitor( 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,)``. """ n_t = problem.grids.n_t n_modes = problem.shape[0] @@ -588,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) @@ -636,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 @@ -730,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 @@ -762,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 @@ -816,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): @@ -833,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: @@ -874,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 @@ -1058,11 +942,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 @@ -1089,17 +968,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) @@ -1465,11 +1334,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: @@ -1488,10 +1352,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: @@ -1719,17 +1580,9 @@ def __exit__( # -- translation ---------------------------------------------------------------- def _check_destination(self, out: np.ndarray) -> None: - """Refuse a destination that cannot hold an export (as ``apply_recipe`` does). + """Refuse a destination that cannot hold an export. - Parameters - ---------- - out : numpy.ndarray - The proposed destination. - - Raises - ------ - solprop.exceptions.BackendError - Naming the mismatch. + The same check ``apply_recipe`` makes. """ refusal = destination_refusal(out, self._shape) if refusal is not None: @@ -1738,33 +1591,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.""" z = batch.row_z.reshape(-1) step = batch.row_step.reshape(-1).astype(np.int64) if z.size == 0: @@ -1777,13 +1611,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( np.empty(0), np.empty(0, dtype=np.int64), @@ -1795,18 +1623,7 @@ def _no_rows(self) -> StepStats: @staticmethod 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. - """ + """Return a chunk's attempt rows as a history.""" return BackendHistory( z=np.ascontiguousarray(attempts[:, 0]), dz=np.ascontiguousarray(attempts[:, 1]), @@ -1822,19 +1639,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) @@ -1863,13 +1668,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 @@ -1882,15 +1681,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, @@ -1917,24 +1708,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 @@ -2017,16 +1791,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 @@ -2043,16 +1807,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) @@ -2063,15 +1819,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 @@ -2108,18 +1856,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) @@ -2187,12 +1923,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: @@ -2202,16 +1932,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 @@ -2279,26 +2001,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 From 4f79d9c34a8fc2924eaade63a200395503563cb0 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:22:51 +0000 Subject: [PATCH 32/50] Restore flags, layouts and policies in solprop_native private docs Co-Authored-By: Claude Opus 5.5 --- native/src/solprop_native/_counters.py | 1 + native/src/solprop_native/_cpu.py | 2 ++ native/src/solprop_native/_host_blas.py | 3 +++ native/src/solprop_native/_load.py | 3 +++ native/src/solprop_native/_profile.py | 1 + native/src/solprop_native/_statistics.py | 6 +++++- native/src/solprop_native/_store.py | 5 ++++- native/src/solprop_native/_wisdom.py | 3 +++ native/src/solprop_native/backend.py | 3 +++ native/src/solprop_native/group.py | 10 +++++++++- native/src/solprop_native/session.py | 10 +++++++--- 11 files changed, 41 insertions(+), 6 deletions(-) diff --git a/native/src/solprop_native/_counters.py b/native/src/solprop_native/_counters.py index 2ef13aa0..ac4d584f 100644 --- a/native/src/solprop_native/_counters.py +++ b/native/src/solprop_native/_counters.py @@ -152,6 +152,7 @@ def march( """Count one native march call from its returned rows. - ``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. diff --git a/native/src/solprop_native/_cpu.py b/native/src/solprop_native/_cpu.py index 676c95fc..647da1c9 100644 --- a/native/src/solprop_native/_cpu.py +++ b/native/src/solprop_native/_cpu.py @@ -254,6 +254,7 @@ 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. + - ``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(): @@ -408,6 +409,7 @@ def select_variant( - ``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/_host_blas.py b/native/src/solprop_native/_host_blas.py index 879be8eb..88d65b6e 100644 --- a/native/src/solprop_native/_host_blas.py +++ b/native/src/solprop_native/_host_blas.py @@ -82,6 +82,9 @@ def hold( ) -> str: """Take the host BLAS pools for one more backend; return what applies. + - ``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. """ diff --git a/native/src/solprop_native/_load.py b/native/src/solprop_native/_load.py index 0dfce585..5339b228 100644 --- a/native/src/solprop_native/_load.py +++ b/native/src/solprop_native/_load.py @@ -123,6 +123,8 @@ def load( - ``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: @@ -162,6 +164,7 @@ def schedule_index(stepper: StepperSettings, z: float) -> int: """Return how many steps of a replayed schedule a position has consumed. - ``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: diff --git a/native/src/solprop_native/_profile.py b/native/src/solprop_native/_profile.py index 53714c7a..25048a5b 100644 --- a/native/src/solprop_native/_profile.py +++ b/native/src/solprop_native/_profile.py @@ -229,6 +229,7 @@ def descriptor( - ``grid_kind``: ``"field"`` or ``"envelope"``. - ``sizing``: the workspace's ``execution_sizing``; only its keys are read. + - Returns: 24 hexadecimal digits. """ text = json.dumps( { diff --git a/native/src/solprop_native/_statistics.py b/native/src/solprop_native/_statistics.py index 07a38741..23d26b2b 100644 --- a/native/src/solprop_native/_statistics.py +++ b/native/src/solprop_native/_statistics.py @@ -422,7 +422,11 @@ 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.""" + """Return the metric's square root as :func:`solprop.stats._whitened` forms it. + + ``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): msg = ( diff --git a/native/src/solprop_native/_store.py b/native/src/solprop_native/_store.py index 0a283806..d54c091d 100644 --- a/native/src/solprop_native/_store.py +++ b/native/src/solprop_native/_store.py @@ -336,7 +336,10 @@ def current(self, fingerprint: str) -> Generation | None: return generation def publish(self, generation: Generation) -> Path: - """Write `generation` completely, then make it current; return its file.""" + """Write `generation` completely, then make it current; return its file. + + If the store cannot be written, the previous generation stays current. + """ directory = self.directory(generation.fingerprint) directory.mkdir(parents=True, exist_ok=True) path = directory / f"{generation.name}.json" diff --git a/native/src/solprop_native/_wisdom.py b/native/src/solprop_native/_wisdom.py index 80b0d547..8b0be1c1 100644 --- a/native/src/solprop_native/_wisdom.py +++ b/native/src/solprop_native/_wisdom.py @@ -185,6 +185,8 @@ def plans(path: Path | None) -> tuple[dict[str, str], bool]: - ``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 {} @@ -224,6 +226,7 @@ def store( """Merge plans a run measured into the file. - ``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: diff --git a/native/src/solprop_native/backend.py b/native/src/solprop_native/backend.py index 9da3ffe6..3a0b8afe 100644 --- a/native/src/solprop_native/backend.py +++ b/native/src/solprop_native/backend.py @@ -561,6 +561,7 @@ def _load( 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") @@ -2905,6 +2906,8 @@ def rate(self, z: float, *, before: bool) -> RealArray: """Evaluate ``D(z)`` into the rate slot; return its weights over the terms. - ``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 diff --git a/native/src/solprop_native/group.py b/native/src/solprop_native/group.py index ffc6b130..658e9ec9 100644 --- a/native/src/solprop_native/group.py +++ b/native/src/solprop_native/group.py @@ -686,7 +686,13 @@ def _member(self, member: int) -> int: return member def _window(self, *, stepping: bool, final: bool) -> GroupBatch: - """Run every running member to its window's end, in rounds; report it.""" + """Run every running member to its window's end, in rounds; report it. + + - ``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() for index, outcome in enumerate(self._outcomes) @@ -800,6 +806,8 @@ def _ended( """End every running member with a group-wide stop or failure. - ``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(): diff --git a/native/src/solprop_native/session.py b/native/src/solprop_native/session.py index 9b79534e..64290598 100644 --- a/native/src/solprop_native/session.py +++ b/native/src/solprop_native/session.py @@ -330,8 +330,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. + 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] @@ -1623,7 +1624,10 @@ def _no_rows(self) -> StepStats: @staticmethod def _history(attempts: np.ndarray) -> BackendHistory: - """Return a chunk's attempt rows as a history.""" + """Return a chunk's attempt rows as a history. + + - ``attempts``: ``(n, 5)`` rows: end, step, norm, acceptance, wall time. + """ return BackendHistory( z=np.ascontiguousarray(attempts[:, 0]), dz=np.ascontiguousarray(attempts[:, 1]), From 615c1faff2a2747bdf26904375b196042f613f7d Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:23:01 +0000 Subject: [PATCH 33/50] Restore conventions and reasons in solprop.backends private docs The slimming dropped entries the code does not say: the driver's periodic-row hooks (release vs retain, attempt index incl. rejections, drains), the recorded norm of clipped and diverged attempts, replay resume only at schedule edges, the Adams trial's frame and norm scale, the exponential compensation's chord exponent and coupled residuals, the flow-squaring limit shared with the compiled backends, units of the ungated current and plasma current density, and a few whys (mixed precisions, envelope ledger, the probe's private module name). Also completes the response pass's tuple return and recapitalises Cartesian and P. Co-Authored-By: Claude Opus 5.5 --- src/solprop/backends/host/adams.py | 2 ++ src/solprop/backends/host/driver.py | 41 +++++++++++++++++++---- src/solprop/backends/host/general.py | 14 ++++++-- src/solprop/backends/host/group.py | 3 ++ src/solprop/backends/multistep.py | 12 ++++++- src/solprop/backends/node_chunk.py | 3 ++ src/solprop/backends/numpy_ref/backend.py | 24 ++++++++++--- src/solprop/backends/numpy_ref/passes.py | 6 ++-- src/solprop/backends/numpy_ref/tdse.py | 10 +++++- src/solprop/backends/registry.py | 10 ++++-- src/solprop/backends/spatial.py | 2 ++ 11 files changed, 108 insertions(+), 19 deletions(-) diff --git a/src/solprop/backends/host/adams.py b/src/solprop/backends/host/adams.py index 802390e5..45232555 100644 --- a/src/solprop/backends/host/adams.py +++ b/src/solprop/backends/host/adams.py @@ -624,6 +624,8 @@ def _attempt( """Try one step of length `dz` and commit it if accepted. - ``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. diff --git a/src/solprop/backends/host/driver.py b/src/solprop/backends/host/driver.py index c2055e9d..2459594a 100644 --- a/src/solprop/backends/host/driver.py +++ b/src/solprop/backends/host/driver.py @@ -478,7 +478,11 @@ def _finish( ) def _reserve_source_calls(self, count: int) -> None: - """Reserve a complete trial's external source calls when a service is bound.""" + """Reserve a complete trial's external source calls when a service is bound. + + 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: @@ -656,7 +660,12 @@ 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.""" + """Discard periodic rows at a load or at the start of a plane. + + - ``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() else: @@ -670,11 +679,17 @@ def _record_step_stats(self, spec: StatsSpec, z: float, step: int) -> None: the host, using the same evaluator as :meth:`stats`. - ``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.""" + """Return owning host rows at observation and retain reusable capacity. + + A device override copies the packed values once here. Repeated drains return + no rows until more are recorded. + """ return self._rows.drain() @property @@ -1929,7 +1944,10 @@ 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).""" + """Read the transverse edge where its spacing or a saved plane asks (SEP 40). + + - ``plane``: whether a saved plane has just been reached, which is always read. + """ edge = monitor.transverse if edge is None: return @@ -2114,7 +2132,11 @@ def _boundary_energies(self, state: _StateT) -> BoundaryEnergies: def _check_boundaries( self, monitor: BoundaryMonitor, *, plane: bool = False ) -> None: - """Account the committed step at the current position with the monitor.""" + """Account the committed step at the current position with the monitor. + + - ``plane``: true for a saved plane's check of a step the monitor already + skipped. + """ self._check_at( monitor, self._boundary_energies(self._require_state()), @@ -2316,6 +2338,10 @@ def _record(self, z: float, dz: float, norm: float, accepted: bool) -> None: - ``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 @@ -2546,7 +2572,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. - - ``z``: the position the state is loaded at, m. + - ``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: diff --git a/src/solprop/backends/host/general.py b/src/solprop/backends/host/general.py index e61c111c..5e101333 100644 --- a/src/solprop/backends/host/general.py +++ b/src/solprop/backends/host/general.py @@ -65,6 +65,8 @@ def start( # noqa: PLR0917 - called positionally ) -> "_Trial": """Form the predictor of a step from the history, newest derivative first. + - ``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] @@ -88,11 +90,19 @@ 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.""" + """Return the lab-frame field at the end of the step for an 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.""" + """Return the weak norm of a defect, carried to the end of the step. + + The defect is a local estimate in the anchor's frame; a norm of one is the + tolerance. + """ return self.scale(self.propagator * defect) diff --git a/src/solprop/backends/host/group.py b/src/solprop/backends/host/group.py index a155ef6f..134856b0 100644 --- a/src/solprop/backends/host/group.py +++ b/src/solprop/backends/host/group.py @@ -500,6 +500,9 @@ 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. + 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 diff --git a/src/solprop/backends/multistep.py b/src/solprop/backends/multistep.py index e41a87a3..8b7c0610 100644 --- a/src/solprop/backends/multistep.py +++ b/src/solprop/backends/multistep.py @@ -1453,7 +1453,11 @@ class _Compensation: def add_endpoint( self, rate: _State, state: _State, residual: _State | None = None ) -> None: - """Add the new sample's compensation at the predicted state; no transport.""" + """Add the new sample's compensation at the predicted state; no transport. + + - ``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 if residual is not None: @@ -2412,6 +2416,7 @@ def _history_terms( ) -> list[_State]: """Return ``(D(z_j) - B_n) u_j`` of history samples, untransported. + - ``exponent``: ``x = h B_n``. - ``dz``: the step, m. """ assert self._states is not None and self._rates is not None @@ -2429,8 +2434,11 @@ def _compensation_terms( # noqa: PLR0917 - called positionally ) -> "_Compensation": """Return the trial's linear compensation of its history samples. + - ``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 @@ -2561,6 +2569,8 @@ def _trial_propagators( # noqa: PLR0917 - called positionally ) -> tuple[Any, Any]: """Return a trial's moments and flow, shifted by `shift` when it is given. + - ``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). diff --git a/src/solprop/backends/node_chunk.py b/src/solprop/backends/node_chunk.py index 7b8ba31b..daab1c79 100644 --- a/src/solprop/backends/node_chunk.py +++ b/src/solprop/backends/node_chunk.py @@ -338,6 +338,9 @@ def _envelope_chunk( ) -> int: """Admit envelope plasma's current streams and histories into the chunk. + Modal projected arrays are accounted separately by the backends' ledgers, because + they do not scale with the chunk. + - ``budget``: node workspace budget in bytes. """ components = transform.n_pol * n_pol diff --git a/src/solprop/backends/numpy_ref/backend.py b/src/solprop/backends/numpy_ref/backend.py index 7c3e611c..8f51dc1a 100644 --- a/src/solprop/backends/numpy_ref/backend.py +++ b/src/solprop/backends/numpy_ref/backend.py @@ -1855,6 +1855,8 @@ def _column_sources( - ``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( @@ -1881,6 +1883,8 @@ def _column_sources( def _held_streams(self, gated: np.ndarray, edge: np.ndarray) -> np.ndarray: """Transform and combine the current products in polarisation units. + - ``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. """ @@ -1892,6 +1896,7 @@ 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. + - ``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( @@ -2224,9 +2229,9 @@ def _over_nodes( what makes the quadrature alarm free: the two rules share every node the coarse one has. - - Returns ``coarse``: 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 @@ -2751,7 +2756,12 @@ 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.""" + """Return how many squarings an exponent of this row-sum norm needs, or refuse. + + 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: squarings = math.ceil(math.log2(largest / _TAYLOR_SCALE)) @@ -3016,6 +3026,9 @@ def _apply_flow( ) -> Complex[np.ndarray, "n_batch n_modes n_pol n_omega"]: """Apply a per-frequency channel action to the canonical state. + The identity case is the caller's: a problem with no coupled flow, or the zero + stage fraction, never reaches here. + - ``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. @@ -3197,7 +3210,8 @@ 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``). + (``docs/guide/media/nonlinear.md``). ``validate`` refuses a harmonic term without + the carrier, so only a hand-built problem reaches the refusal here. - Returns: the carrier of shape ``(n_to,)``, or ``None`` when no harmonic term is present. diff --git a/src/solprop/backends/numpy_ref/passes.py b/src/solprop/backends/numpy_ref/passes.py index 6ac86718..94483d00 100644 --- a/src/solprop/backends/numpy_ref/passes.py +++ b/src/solprop/backends/numpy_ref/passes.py @@ -609,10 +609,11 @@ def _vector_raman_polarisation( ) -> Num[np.ndarray, "n_nodes_chunk 2 n_to"]: """Convolve trace, difference and coherence drives with compiled kernels. - - ``field_t``: cartesian field in V/m, shape ``(n_nodes_chunk, 2, n_to)``. + - ``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. + - 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)): @@ -1109,6 +1110,7 @@ def _plasma_channels_current( # noqa: PLR0917 - called through a reference - ``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") diff --git a/src/solprop/backends/numpy_ref/tdse.py b/src/solprop/backends/numpy_ref/tdse.py index b951100c..50290d68 100644 --- a/src/solprop/backends/numpy_ref/tdse.py +++ b/src/solprop/backends/numpy_ref/tdse.py @@ -220,8 +220,12 @@ def _roundoff_floor( ) -> float: """Bound operand cancellation through absolute projection and Fourier sums. - - ``factor``: p or J spectral factor including crop scale, shape ``(n_modes, 1, + - ``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 @@ -412,6 +416,8 @@ def _edge_rounding( # noqa: PLR0917 - called positionally |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 @@ -618,6 +624,8 @@ def _edge_diagnostic( # noqa: PLR0917 - called positionally 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 diff --git a/src/solprop/backends/registry.py b/src/solprop/backends/registry.py index 5845fdab..374c5cc7 100644 --- a/src/solprop/backends/registry.py +++ b/src/solprop/backends/registry.py @@ -173,7 +173,10 @@ class BackendEntry: def _load(path: str) -> object: - """Import the object a registry path names.""" + """Import the object a registry path names. + + ``path`` is ``"module:attribute"``, the attribute dotted for one inside a class. + """ module, _, attribute = path.partition(":") found: object = importlib.import_module(module) for part in attribute.split("."): @@ -434,7 +437,10 @@ 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.""" + """Load the native package's processor check from its file, as its own module. + + 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 raise ImportError(f"cannot load the native processor check {source}") diff --git a/src/solprop/backends/spatial.py b/src/solprop/backends/spatial.py index cbd700de..d0463fb3 100644 --- a/src/solprop/backends/spatial.py +++ b/src/solprop/backends/spatial.py @@ -170,6 +170,8 @@ def _blockwise_fluence( - ``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] From 10a8076e486b28c45b8ec6a35e356fdf5de01a44 Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:23:20 +0000 Subject: [PATCH 34/50] Keep the dropped private doctests' checks as unit tests The slimming dropped the Examples of driver.run._history_of, output.schema._inlined, _array_name, _dotted and _flow_text, service.machines._development and service.results._scan_axis; their assertions now live in the mirrored test modules. Co-Authored-By: Claude Opus 5.5 --- tests/driver/test_run.py | 15 ++++++++++++++- tests/output/test_schema.py | 29 +++++++++++++++++++++++++++++ tests/service/test_agent.py | 5 +++++ tests/service/test_results.py | 7 +++++++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/tests/driver/test_run.py b/tests/driver/test_run.py index 5129a573..642adfc5 100644 --- a/tests/driver/test_run.py +++ b/tests/driver/test_run.py @@ -32,7 +32,7 @@ from solprop.backends.registry import select from solprop.backends.session import Start from solprop.driver import Simulation, SimulationSettings, metadata_for, run_problem -from solprop.driver.run import plane_from_observation +from solprop.driver.run import _history_of, plane_from_observation from solprop.exceptions import BackendError, DriverError from solprop.grid import EnvelopeGrid from solprop.log import NullReporter @@ -733,3 +733,16 @@ def finished(self, total): metadata=simulation.metadata(), progress=FourCalls(), # type: ignore[arg-type] - the older protocol on purpose ) + + +def test_history_of_stores_start_positions_and_per_attempt_times(): + 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) + assert stored.z.tolist() == [0.0, 0.5] + assert stored.wall_time.tolist() == [0.25, 0.5] diff --git a/tests/output/test_schema.py b/tests/output/test_schema.py index 4383a396..6439d1c7 100644 --- a/tests/output/test_schema.py +++ b/tests/output/test_schema.py @@ -1042,3 +1042,32 @@ def test_spatial_metadata_names_its_flux_model(envelope_grid, stepper): ) with pytest.raises(OutputError, match="requires a spatial geometry record"): make_metadata(envelope_grid, stepper, simulation_kind="radial") + + +def test_inlined_replaces_every_array_reference_by_its_values(): + inlined = schema._inlined(schema.serialise({"axis": np.zeros(5000)})) + assert isinstance(inlined, dict) + assert sorted(inlined["axis"]) == ["__ndarray__", "dtype", "shape"] + + +def test_array_name_carries_the_dtype_and_the_shape(): + digest = schema.ArrayDigest.of(np.zeros((50, 100))) + assert schema._array_name(digest).split("-")[1:] == ["float64", "50x100"] + + +def test_dotted_writes_keys_with_dots_and_indices_in_brackets(): + assert schema._dotted(("modes", 2, "label")) == "modes[2].label" + + +def test_flow_text_names_a_diagonal_flow_and_summarises_a_coupled_one(): + assert schema._flow_text(None) == "diagonal" + flow = { + "model": "mean impedance coherent wall", + "section_methods": ["constant_schur"], + "transverse_ratio": 9.012e-3, + "inverse_condition_bound": 1.0, + } + assert schema._flow_text(flow) == ( + "mean impedance coherent wall, constant_schur, worst ratio 9.012e-03, " + "conditioning 1.000e+00" + ) diff --git a/tests/service/test_agent.py b/tests/service/test_agent.py index cf7681ad..ca632396 100644 --- a/tests/service/test_agent.py +++ b/tests/service/test_agent.py @@ -734,3 +734,8 @@ def rmtree(path): ) with pytest.raises(PermissionError): agent._remove_tree(tmp_path / "b", tries=3) + + +def test_a_development_version_is_a_dev_release_or_has_a_local_part(): + assert not machines._development("0.2.0") + assert machines._development("0.2.1.dev17+g8077f26c3.d20261107") diff --git a/tests/service/test_results.py b/tests/service/test_results.py index 4f316dc6..5bcf7ca3 100644 --- a/tests/service/test_results.py +++ b/tests/service/test_results.py @@ -348,3 +348,10 @@ def test_figures_can_come_back_as_bytes(run: str, tmp_path: Path): {"path": run, "file": str(tmp_path / "s"), "plot": "plot_stats", "bytes": True}, ) assert all(figure["data"].size for figure in several) + + +def test_scan_axis_reads_quantities_in_their_unit_and_numbers_in_si(): + axis = results._scan_axis("pressure[0]", np.array(["0.8 bar", "1.2 bar"])) + assert axis["values"].tolist() == [0.8, 1.2] + assert axis["unit"] == "bar" + assert results._scan_axis("energy", np.array([1e-4]))["unit"] == "" From 189bdae67b20d1fd4a5703940953254d61bfe53a Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:28:06 +0000 Subject: [PATCH 35/50] Capitalise Stokes and Cartesian in two slimmed docstrings Co-Authored-By: Claude Opus 5.5 --- src/solprop/spatial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solprop/spatial.py b/src/solprop/spatial.py index f4dd14c3..264b0585 100644 --- a/src/solprop/spatial.py +++ b/src/solprop/spatial.py @@ -555,7 +555,7 @@ def _longitudinal_square( ) -> Complex[np.ndarray, "n_modes n_omega"]: """Preserve unsquared Cartesian inputs when evaluating the launch operator. - - ``transverse``: cartesian axes or already squared transverse frequencies in + - ``transverse``: Cartesian axes or already squared transverse frequencies in m^-2. - ``z``: material sampling position in m. - Returns: complex longitudinal squares in m^-2, one row per transverse column. From 67e03327cddc974b04e2ebcefa6bfbebf07671da Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:28:30 +0000 Subject: [PATCH 36/50] Restore Taylor's capital in three solprop.driver bullets Co-Authored-By: Claude Opus 5.5 --- src/solprop/driver/simulation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/solprop/driver/simulation.py b/src/solprop/driver/simulation.py index 4f38226e..79fd0a86 100644 --- a/src/solprop/driver/simulation.py +++ b/src/solprop/driver/simulation.py @@ -6095,7 +6095,7 @@ def _from_hollow( # noqa: PLR0915 - the interface's keyword surface - ``shape``: Transform-limited shape of the input. - ``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 + - ``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, @@ -8216,7 +8216,7 @@ def _gnlse_fibre( """Map one dispersion source and one nonlinear coefficient onto the GNLSE. - ``gamma``: nonlinear coefficient in 1/(W m), absent beside `n2`. - - ``betas``: taylor coefficients about the reference frequency, s^n/m. + - ``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`. @@ -8375,7 +8375,7 @@ def _shaped_pulse( - ``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. + - ``phases``: Taylor coefficients of the optics-convention spectral phase. """ if pulses is not None: given = [ From 7acf8f3f69a7b4042d2696d1c4f21a05884bd33e Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:29:18 +0000 Subject: [PATCH 37/50] Restore capitals the slimming lowered in group C bullets Cartesian, Hermitian, Jones and the fibre frequency V. Co-Authored-By: Claude Opus 5.5 --- src/solprop/coupling/coupler.py | 4 ++-- src/solprop/modes/_stepindex_vector.py | 2 +- src/solprop/modes/stepindex.py | 2 +- src/solprop/nonlinear/raman.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/solprop/coupling/coupler.py b/src/solprop/coupling/coupler.py index 0da4007a..7c815cc6 100644 --- a/src/solprop/coupling/coupler.py +++ b/src/solprop/coupling/coupler.py @@ -612,7 +612,7 @@ def _whitened_extremes( ) -> tuple[float, float]: """Return the extreme generalised eigenvalues of ``form`` against ``metric``. - - ``metric``: hermitian positive-definite metric. + - ``metric``: Hermitian positive-definite metric. - Returns: the smallest and largest eigenvalue of ``metric^{-1/2} form metric^{-1/2}``. """ @@ -907,7 +907,7 @@ def _channels( """Return each channel's Jones vector and mode index, channel order m-major. - ``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. + - 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) diff --git a/src/solprop/modes/_stepindex_vector.py b/src/solprop/modes/_stepindex_vector.py index a4ec654d..9c55ab3a 100644 --- a/src/solprop/modes/_stepindex_vector.py +++ b/src/solprop/modes/_stepindex_vector.py @@ -184,7 +184,7 @@ 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. - - ``points``: cartesian positions in m, shape (n_points, 2). + - ``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). diff --git a/src/solprop/modes/stepindex.py b/src/solprop/modes/stepindex.py index 52918337..40a170ea 100644 --- a/src/solprop/modes/stepindex.py +++ b/src/solprop/modes/stepindex.py @@ -417,7 +417,7 @@ def _recovered( replaced and the alignment check that follows still sees every root's radial order. - - ``scan``: v, the squared index ratio, the azimuthal order n and the family (HE or + - ``scan``: V, the squared index ratio, the azimuthal order n and the family (HE or EH). - Raises :class:`ModeError` if the factor changes sign more than once in an empty cutoff interval: one guided root per interval rules that out, so which candidate diff --git a/src/solprop/nonlinear/raman.py b/src/solprop/nonlinear/raman.py index 87d8e62a..bec76449 100644 --- a/src/solprop/nonlinear/raman.py +++ b/src/solprop/nonlinear/raman.py @@ -1462,7 +1462,7 @@ def _evaluate_tensor( - ``values``: local Cartesian field in V/m, shape ``(2, n_to)``. - ``density``: Non-negative number density in m^-3. - ``dt``: positive temporal spacing in s. - - Returns: cartesian polarisation in C/m^2, shape ``(2, n_to)``. + - Returns: Cartesian polarisation in C/m^2, shape ``(2, n_to)``. """ quadratic = _quadratic_tensor(values, kind=kind, thg=self.thg) trace = quadratic[0, 0] + quadratic[1, 1] From 9f3b6f8d61687869098c95126f3583f6361a6b9a Mon Sep 17 00:00:00 2001 From: John Travers Date: Thu, 12 Nov 2026 00:39:24 +0000 Subject: [PATCH 38/50] Give public properties one-line summaries, no Returns sections Mechanical pass: each public property's summary becomes a noun phrase, its Returns section goes, the description stays as a paragraph, and the def line carries `# numpydoc ignore=RT01`. The AST without docstrings is unchanged. Co-Authored-By: Claude Opus 5.5 --- src/solprop/backends/events.py | 9 +- src/solprop/backends/exponential.py | 9 +- src/solprop/backends/host/driver.py | 84 ++-- src/solprop/backends/host/group.py | 32 +- src/solprop/backends/host/group_session.py | 36 +- src/solprop/backends/host/low_storage.py | 9 +- src/solprop/backends/host/session.py | 45 +- src/solprop/backends/multistep.py | 16 +- src/solprop/backends/node_chunk.py | 9 +- .../backends/numpy_ref/general_modal.py | 38 +- src/solprop/backends/protocol.py | 76 ++- src/solprop/backends/session.py | 36 +- src/solprop/boundaries.py | 9 +- src/solprop/calibration/controller.py | 7 +- src/solprop/coupling/coupler.py | 80 ++-- src/solprop/coupling/elements.py | 9 +- src/solprop/coupling/models.py | 18 +- src/solprop/coupling/plane.py | 27 +- src/solprop/coupling/port.py | 20 +- src/solprop/coupling/transport.py | 13 +- src/solprop/driver/estimate.py | 9 +- src/solprop/driver/executors/pool.py | 18 +- src/solprop/driver/plane_view.py | 18 +- src/solprop/driver/run.py | 9 +- src/solprop/driver/simulation.py | 34 +- src/solprop/driver/tdse.py | 9 +- src/solprop/driver/tdse_consistency.py | 9 +- src/solprop/gasflow.py | 54 +-- src/solprop/grid.py | 247 ++++------ src/solprop/log.py | 102 ++-- src/solprop/modes/capillary.py | 188 +++----- src/solprop/modes/collection.py | 72 ++- src/solprop/modes/connection.py | 20 +- src/solprop/modes/fill.py | 13 +- src/solprop/modes/graded_index.py | 98 ++-- src/solprop/modes/guide.py | 186 +++----- src/solprop/modes/material.py | 45 +- src/solprop/modes/protocol.py | 15 +- src/solprop/modes/quadrature.py | 20 +- src/solprop/modes/rectangular.py | 92 ++-- src/solprop/modes/slab.py | 192 +++----- src/solprop/modes/stepindex.py | 67 +-- src/solprop/modes/tabulated.py | 9 +- src/solprop/monitor.py | 50 +- src/solprop/nonlinear/envelope_plasma.py | 18 +- src/solprop/nonlinear/gnlse.py | 178 +++---- src/solprop/nonlinear/ionisation.py | 9 +- src/solprop/nonlinear/optmat_raman.py | 9 +- src/solprop/nonlinear/optmat_rates.py | 164 +++---- src/solprop/nonlinear/raman.py | 140 ++---- src/solprop/nonlinear/responses.py | 58 +-- src/solprop/nonlinear/tdse.py | 62 +-- src/solprop/optics.py | 9 +- src/solprop/output/callback.py | 36 +- src/solprop/output/hdf5.py | 16 +- src/solprop/output/memory.py | 11 +- src/solprop/output/resumable.py | 9 +- src/solprop/output/schema.py | 252 ++++------ src/solprop/output/snapshots.py | 9 +- src/solprop/output/tdse_archive.py | 81 ++-- src/solprop/physdata/glasses.py | 105 ++-- src/solprop/physdata/mixtures.py | 18 +- src/solprop/physdata/nonlinear_data.py | 54 +-- src/solprop/physdata/raman_types.py | 9 +- src/solprop/physdata/selection.py | 29 +- src/solprop/physdata/sellmeier.py | 45 +- src/solprop/physdata/species.py | 67 +-- src/solprop/processing/scans.py | 65 +-- src/solprop/profiles.py | 448 +++++++----------- src/solprop/pulses.py | 40 +- src/solprop/recipe/core.py | 9 +- src/solprop/recipe/quantity.py | 7 +- src/solprop/service/client.py | 18 +- src/solprop/service/launch.py | 9 +- src/solprop/service/machines.py | 9 +- src/solprop/service/messages.py | 18 +- src/solprop/spatial.py | 84 ++-- src/solprop/spatial_absorber.py | 20 +- src/solprop/stats.py | 54 +-- src/solprop/stepper/controller.py | 29 +- src/solprop/stepper/low_storage.py | 44 +- src/solprop/stepper/multistep.py | 62 +-- src/solprop/stepper/runge_kutta.py | 9 +- src/solprop/stepper/schedule.py | 71 +-- src/solprop/stepper/tableau.py | 24 +- src/solprop/tools.py | 29 +- src/solprop/translate/chebyshev.py | 18 +- src/solprop/translate/coupled.py | 51 +- src/solprop/translate/envelope_plasma.py | 9 +- src/solprop/translate/expansion.py | 51 +- src/solprop/translate/general.py | 31 +- src/solprop/translate/ionisation.py | 38 +- src/solprop/translate/linop.py | 64 +-- src/solprop/translate/masks.py | 9 +- src/solprop/translate/modal.py | 42 +- src/solprop/translate/modal_path.py | 24 +- src/solprop/translate/modal_tables.py | 18 +- src/solprop/translate/problem.py | 27 +- src/solprop/translate/raman.py | 33 +- src/solprop/translate/responses.py | 131 ++--- src/solprop/translate/run_request.py | 28 +- src/solprop/translate/spatial.py | 29 +- src/solprop/translate/stage.py | 18 +- src/solprop/translate/tdse.py | 69 +-- src/solprop/transverse_monitor.py | 47 +- 105 files changed, 1883 insertions(+), 3449 deletions(-) diff --git a/src/solprop/backends/events.py b/src/solprop/backends/events.py index efaeeaf7..7cf5a81e 100644 --- a/src/solprop/backends/events.py +++ b/src/solprop/backends/events.py @@ -385,13 +385,10 @@ 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. + def terminal(self) -> Terminal | None: # numpydoc ignore=RT01 + """The batch's terminal event, if it ended the session. - Returns - ------- - Terminal or None - The last event when it is terminal; ``None`` otherwise. + The last event when it is terminal; ``None`` otherwise. """ if self.events and isinstance(self.events[-1], Terminal): return self.events[-1] diff --git a/src/solprop/backends/exponential.py b/src/solprop/backends/exponential.py index 210f9620..d219652b 100644 --- a/src/solprop/backends/exponential.py +++ b/src/solprop/backends/exponential.py @@ -442,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 diff --git a/src/solprop/backends/host/driver.py b/src/solprop/backends/host/driver.py index 2459594a..cb93fba6 100644 --- a/src/solprop/backends/host/driver.py +++ b/src/solprop/backends/host/driver.py @@ -779,36 +779,27 @@ 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. + def chunk_plan(self) -> NodeChunkPlan: # numpydoc ignore=RT01 + """Node-chunk plan and what chose it. - Returns - ------- - solprop.backends.node_chunk.NodeChunkPlan - The plan made at construction. + The plan made at construction. """ return self._chunk_plan @@ -1414,51 +1405,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 @@ -2539,13 +2518,10 @@ 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. + def closed(self) -> bool: # numpydoc ignore=RT01 + """Whether the backend has released its buffers, as a failure may. - Returns - ------- - bool - ``True`` when it can run no other input. + ``True`` when it can run no other input. """ return self._closed diff --git a/src/solprop/backends/host/group.py b/src/solprop/backends/host/group.py index 134856b0..e2939f99 100644 --- a/src/solprop/backends/host/group.py +++ b/src/solprop/backends/host/group.py @@ -242,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() diff --git a/src/solprop/backends/host/group_session.py b/src/solprop/backends/host/group_session.py index 568fb9bb..3db98638 100644 --- a/src/solprop/backends/host/group_session.py +++ b/src/solprop/backends/host/group_session.py @@ -179,35 +179,26 @@ def __init__( # -- protocol ------------------------------------------------------------------- @property - def n_members(self) -> int: - """Return the number of members. + def n_members(self) -> int: # numpydoc ignore=RT01 + """Number of members. - Returns - ------- - int - The members. + The 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. + def closed(self) -> bool: # numpydoc ignore=RT01 + """Whether the group was closed. - Returns - ------- - bool - True once closed. + True once closed. """ return self._ledger.state is SessionState.CLOSED @@ -678,13 +669,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 72628286..8fab1325 100644 --- a/src/solprop/backends/host/low_storage.py +++ b/src/solprop/backends/host/low_storage.py @@ -73,13 +73,10 @@ 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. + def engine(self) -> HostLowStorage: # numpydoc ignore=RT01 + """Engine that holds the registers. - Returns - ------- - solprop.backends.low_storage.HostLowStorage - The host engine. + The host engine. """ return self._engine diff --git a/src/solprop/backends/host/session.py b/src/solprop/backends/host/session.py index 2f1f35f8..fc3278d5 100644 --- a/src/solprop/backends/host/session.py +++ b/src/solprop/backends/host/session.py @@ -189,35 +189,26 @@ def __init__( # -- protocol ------------------------------------------------------------------- @property - def driver(self) -> HostStepping: - """Return the reference backend, whose facts the service loop records. + def driver(self) -> HostStepping: # numpydoc ignore=RT01 + """Reference backend, whose facts the service loop records. - Returns - ------- - solprop.backends.protocol.HostStepping - The backend, through the host stepping seam. + The backend, through the host stepping seam. """ return self._driver @property - def finished(self) -> bool: - """Return whether the session yielded its terminal event. + def finished(self) -> bool: # numpydoc ignore=RT01 + """Whether the session yielded its terminal event. - Returns - ------- - bool - True once finished. + True once finished. """ return self._ledger.finished @property - def closed(self) -> bool: - """Return whether the session was closed. + def closed(self) -> bool: # numpydoc ignore=RT01 + """Whether the session was closed. - Returns - ------- - bool - True once closed. + True once closed. """ return self._ledger.state is SessionState.CLOSED @@ -501,13 +492,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 @@ -784,13 +772,10 @@ def __init__( self._closed = False @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 backend's registered name. + The backend's registered name. """ return self._driver.name diff --git a/src/solprop/backends/multistep.py b/src/solprop/backends/multistep.py index 8b7c0610..7ef336b4 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 @@ -1887,13 +1884,10 @@ def reset(self) -> None: self._exponential_trial = None @property - def n_slots(self) -> int: + def n_slots(self) -> int: # numpydoc ignore=RT01 """The history store's rows: the method's order and two more. - Returns - ------- - int - ``order + 2``. + ``order + 2``. """ return self._method.order + 2 diff --git a/src/solprop/backends/node_chunk.py b/src/solprop/backends/node_chunk.py index daab1c79..10bd881e 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" diff --git a/src/solprop/backends/numpy_ref/general_modal.py b/src/solprop/backends/numpy_ref/general_modal.py index 54caeef4..3f321971 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/protocol.py b/src/solprop/backends/protocol.py index c026f178..ff6ae190 100644 --- a/src/solprop/backends/protocol.py +++ b/src/solprop/backends/protocol.py @@ -162,13 +162,10 @@ def __post_init__(self) -> None: raise BackendError(msg) @property - def n_steps(self) -> int: - """Return the number of steps recorded, accepted and rejected together. + def n_steps(self) -> int: # numpydoc ignore=RT01 + """Number of steps recorded, accepted and rejected together. - Returns - ------- - int - The length of ``z``. + The length of ``z``. Examples -------- @@ -181,13 +178,10 @@ def n_steps(self) -> int: return int(self.z.size) @property - def n_rejected(self) -> int: - """Return the number of rejected steps. + def n_rejected(self) -> int: # numpydoc ignore=RT01 + """Number of rejected steps. - Returns - ------- - int - How many entries of ``accepted`` are false. + How many entries of ``accepted`` are false. Examples -------- @@ -304,24 +298,18 @@ def empty(cls) -> "StepStats": return cls(np.empty(0), np.empty(0, dtype=np.int64), {}) @property - def n_rows(self) -> int: - """Return how many rows were taken since the previous observation. + def n_rows(self) -> int: # numpydoc ignore=RT01 + """How many rows were taken since the previous observation. - Returns - ------- - int - The row count. + The row count. """ return int(self.z.size) @property - def names(self) -> tuple[str, ...]: - """Return the statistics the channel carries, sorted. + def names(self) -> tuple[str, ...]: # numpydoc ignore=RT01 + """The statistics the channel carries, sorted. - Returns - ------- - tuple of str - The catalogue names. + The catalogue names. """ return tuple(sorted(self.values)) @@ -827,13 +815,10 @@ def close(self) -> None: ... @property - def closed(self) -> bool: - """Return whether the backend has released its buffers, as a failure may. + def closed(self) -> bool: # numpydoc ignore=RT01 + """Whether the backend has released its buffers, as a failure may. - Returns - ------- - bool - ``True`` when it can run no other input. + ``True`` when it can run no other input. """ ... @@ -967,13 +952,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. """ ... @@ -1100,26 +1082,24 @@ 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. + 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. + def member_records( + self, + ) -> tuple[Mapping[str, object] | None, ...]: # numpydoc ignore=RT01 + """Each member's monitor record as it closed, ``None`` if unwatched. - Returns - ------- - tuple - One entry per member, in member order. + One entry per member, in member order. """ ... diff --git a/src/solprop/backends/session.py b/src/solprop/backends/session.py index dbc9c810..b6d41900 100644 --- a/src/solprop/backends/session.py +++ b/src/solprop/backends/session.py @@ -258,24 +258,18 @@ 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``. + def budget(self) -> int | None: # numpydoc ignore=RT01 + """Explicit node budget as an ``int``, or ``None``. - Returns - ------- - int or None - :attr:`node_budget`, which construction already made an ``int``. + :attr:`node_budget`, which construction already made an ``int``. """ 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``. + def chunk(self) -> int | None: # numpydoc ignore=RT01 + """Exact node chunk as an ``int``, or ``None``. - Returns - ------- - int or None - :attr:`node_chunk`, which construction already made an ``int``. + :attr:`node_chunk`, which construction already made an ``int``. """ return None if self.node_chunk is None else operator.index(self.node_chunk) @@ -1253,23 +1247,17 @@ def close(self) -> bool: return True @property - def pending(self) -> Receipt | None: - """Return the pending batch's receipt. + def pending(self) -> Receipt | None: # numpydoc ignore=RT01 + """Pending batch's receipt. - Returns - ------- - solprop.backends.events.Receipt or None - The receipt, or ``None`` when nothing is pending. + The receipt, or ``None`` when nothing is pending. """ return self._pending @property - def finished(self) -> bool: - """Return whether a terminal batch was issued. + def finished(self) -> bool: # numpydoc ignore=RT01 + """Whether a terminal batch was issued. - Returns - ------- - bool - True once the session yielded its terminal event. + True once the session yielded its terminal event. """ return self.outcome is not None diff --git a/src/solprop/boundaries.py b/src/solprop/boundaries.py index b8829bcd..bf4ddccc 100644 --- a/src/solprop/boundaries.py +++ b/src/solprop/boundaries.py @@ -364,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. - Returns - ------- - float - `collar_length`, or `reference_length` when it is ``None``, m. + `collar_length`, or `reference_length` when it is ``None``, m. Examples -------- diff --git a/src/solprop/calibration/controller.py b/src/solprop/calibration/controller.py index 69dc4da7..b254ab21 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: + def name(self) -> str: # numpydoc ignore=RT01 """``"built-in"``, or the wait and width, such as ``"spin/6"``. - Returns - ------- - str - The candidate's name in reports and scores. + The candidate's name in reports and scores. """ if self.threads is None and self.wait is None: return "built-in" diff --git a/src/solprop/coupling/coupler.py b/src/solprop/coupling/coupler.py index 7c815cc6..d405c40f 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,64 @@ 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. + def incident_energy(self) -> float: # numpydoc ignore=RT01 + """Whole incident energy. - Returns - ------- - float - J. + J. """ return float(np.sum(self.incident)) @property - def launched_energy(self) -> float: - """Return the energy the launch carries into the target. + def launched_energy(self) -> float: # numpydoc ignore=RT01 + """Energy the launch carries into the target. - Returns - ------- - float - J. + J. """ return float(np.sum(self.launched)) @property - def captured_energy(self) -> float | None: - """Return the captured energy in the common metric. + def captured_energy(self) -> float | None: # numpydoc ignore=RT01 + """Captured energy in the common metric. - Returns - ------- - float or None - J; None where it is not defined. + J; None where it is not defined. """ 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, not clipped. - Returns - ------- - float or None - J; None where it is not defined. + J; 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. + Dimensionless; 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. + Dimensionless; at most `model_error` for an admitted coupling. """ return float( sum(item.value for item in self.items if item.counted and item.value) diff --git a/src/solprop/coupling/elements.py b/src/solprop/coupling/elements.py index 9b2b8ea5..b133ead6 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]) diff --git a/src/solprop/coupling/models.py b/src/solprop/coupling/models.py index b69b0706..bdb00eb5 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. - Returns - ------- - float - :func:`cone_angle` of `model_error`, rad. + :func:`cone_angle` of `model_error`, rad. Raises ------ @@ -534,13 +531,10 @@ def leaky( return values @property - def n_modes(self) -> int: - """Return the number of channels. + def n_modes(self) -> int: # numpydoc ignore=RT01 + """Number of channels. - Returns - ------- - int - ``len(labels)``. + ``len(labels)``. """ return len(self.labels) diff --git a/src/solprop/coupling/plane.py b/src/solprop/coupling/plane.py index 01def4b4..35f908bd 100644 --- a/src/solprop/coupling/plane.py +++ b/src/solprop/coupling/plane.py @@ -369,13 +369,10 @@ 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``. + def y_axis(self) -> tuple[float, float, float]: # numpydoc ignore=RT01 + """Local y axis, ``normal`` cross ``x_axis``. - Returns - ------- - tuple of float - The unit vector completing the right-handed frame. + The unit vector completing the right-handed frame. """ value = np.cross(np.asarray(self.normal), np.asarray(self.x_axis)) return (float(value[0]), float(value[1]), float(value[2])) @@ -1004,13 +1001,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) @@ -1125,13 +1119,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. - Returns - ------- - numpy.ndarray - Angular frequencies in rad/s, shape ``(n_omega,)``, in storage order. + Angular frequencies in rad/s, shape ``(n_omega,)``, in storage order. """ return self.grid.omega diff --git a/src/solprop/coupling/port.py b/src/solprop/coupling/port.py index 2b70bf5b..22d3b42b 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/transport.py b/src/solprop/coupling/transport.py index 2e167bba..70851fa4 100644 --- a/src/solprop/coupling/transport.py +++ b/src/solprop/coupling/transport.py @@ -109,14 +109,11 @@ 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) diff --git a/src/solprop/driver/estimate.py b/src/solprop/driver/estimate.py index a2f8dee7..9df22ae2 100644 --- a/src/solprop/driver/estimate.py +++ b/src/solprop/driver/estimate.py @@ -90,13 +90,10 @@ class RunEstimate: total_calls: int | None @property - def step(self) -> float: - """Return the evaluations' share of an accepted step's time. + def step(self) -> float: # numpydoc ignore=RT01 + """Evaluations' share of an accepted step's time. - Returns - ------- - float - Seconds per step. + Seconds per step. """ return self.rhs * self.calls_per_step diff --git a/src/solprop/driver/executors/pool.py b/src/solprop/driver/executors/pool.py index d1642f0f..d9c6c975 100644 --- a/src/solprop/driver/executors/pool.py +++ b/src/solprop/driver/executors/pool.py @@ -72,24 +72,18 @@ class ControlledWork(Protocol): """ @property - def stop(self) -> StopFlag | None: - """Return the caller's stop flag, read in the controlling process. + def stop(self) -> StopFlag | None: # numpydoc ignore=RT01 + """The caller's stop flag, read in the controlling process. - Returns - ------- - StopFlag or None - The flag; ``None`` never stops. + The flag; ``None`` never stops. """ ... @property - def watched(self) -> bool: - """Return whether a worker sends its point's progress to the controller. + def watched(self) -> bool: # numpydoc ignore=RT01 + """Whether a worker sends its point's progress to the controller. - Returns - ------- - bool - True when the controller has a reporter for points. + True when the controller has a reporter for points. """ ... diff --git a/src/solprop/driver/plane_view.py b/src/solprop/driver/plane_view.py index cfb50483..b7759995 100644 --- a/src/solprop/driver/plane_view.py +++ b/src/solprop/driver/plane_view.py @@ -94,24 +94,18 @@ 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) diff --git a/src/solprop/driver/run.py b/src/solprop/driver/run.py index 3f621051..df0341d7 100644 --- a/src/solprop/driver/run.py +++ b/src/solprop/driver/run.py @@ -227,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) diff --git a/src/solprop/driver/simulation.py b/src/solprop/driver/simulation.py index 79fd0a86..c08228ac 100644 --- a/src/solprop/driver/simulation.py +++ b/src/solprop/driver/simulation.py @@ -1083,13 +1083,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) @@ -2071,8 +2068,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 @@ -2080,10 +2077,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 @@ -6749,24 +6743,18 @@ def __exit__(self, *details: object) -> None: self.close() @property - def closed(self) -> bool: - """Return whether the runner, and its backend, are closed. + def closed(self) -> bool: # numpydoc ignore=RT01 + """Whether the runner, and its backend, are closed. - Returns - ------- - bool - True once :meth:`close` has run. + True once :meth:`close` has run. """ return self._closed @property - def backend(self) -> Backend: - """Return the backend every run is stepped on. + def backend(self) -> Backend: # numpydoc ignore=RT01 + """Backend every run is stepped on. - Returns - ------- - solprop.backends.protocol.Backend - The one backend the runner constructed. + The one backend the runner constructed. """ return self._backend diff --git a/src/solprop/driver/tdse.py b/src/solprop/driver/tdse.py index e61d2417..b175f3c9 100644 --- a/src/solprop/driver/tdse.py +++ b/src/solprop/driver/tdse.py @@ -2504,13 +2504,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 1549fc77..cc4433b1 100644 --- a/src/solprop/driver/tdse_consistency.py +++ b/src/solprop/driver/tdse_consistency.py @@ -590,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`. """ ... diff --git a/src/solprop/gasflow.py b/src/solprop/gasflow.py index ddb09283..30caee1e 100644 --- a/src/solprop/gasflow.py +++ b/src/solprop/gasflow.py @@ -958,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) @@ -1040,35 +1031,26 @@ class FlowReport(Displayed): end_is_choked: bool @property - def peak_reynolds(self) -> float: - """Return the largest Reynolds number of the fill. + def peak_reynolds(self) -> float: # numpydoc ignore=RT01 + """Largest Reynolds number of the fill. - Returns - ------- - float - The maximum over the stages. + The maximum over the stages. """ return max(stage.peak_reynolds for stage in self.stages) @property - def peak_reynolds_position(self) -> float: - """Return where the largest Reynolds number is. + def peak_reynolds_position(self) -> float: # numpydoc ignore=RT01 + """Where the largest Reynolds number is. - Returns - ------- - float - The position in m. + The position in 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. + def peak_knudsen(self) -> float: # numpydoc ignore=RT01 + """Largest Knudsen number of the fill. - Returns - ------- - float - The maximum over the stages. + The maximum over the stages. """ return max(stage.peak_knudsen for stage in self.stages) diff --git a/src/solprop/grid.py b/src/solprop/grid.py index cadf7abe..21065bc1 100644 --- a/src/solprop/grid.py +++ b/src/solprop/grid.py @@ -430,68 +430,50 @@ 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. - Returns - ------- - float - ``radius * (1 + collar_fraction)``, m. + ``radius * (1 + collar_fraction)``, m. """ 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. - Returns - ------- - numpy.ndarray - Radii in m, shape ``(n_r,)``; zero and the outer boundary are excluded. + Radii in m, 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. + def k_r(self) -> Float[np.ndarray, " n_r"]: # numpydoc ignore=RT01 + """Transverse spectral samples. - Returns - ------- - numpy.ndarray - Positive angular frequencies in rad/m, shape ``(n_r,)``. + Positive angular frequencies in 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. - Returns - ------- - numpy.ndarray - Weights of ``2*pi*r*dr``, in m^2, shape ``(n_r,)``. + Weights of ``2*pi*r*dr``, in m^2, 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. - 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``, in m^-2, shape ``(n_r,)``. """ return self.hankel.spectral_weights @property - def n_columns(self) -> int: - """Return the number of transverse spectral columns. + def n_columns(self) -> int: # numpydoc ignore=RT01 + """Number of transverse spectral columns. - Returns - ------- - int - The radial sample count. + The radial sample count. """ return self.n_r @@ -727,13 +709,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 @@ -921,14 +900,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" @@ -995,24 +971,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 @@ -1318,70 +1288,52 @@ def __post_init__(self) -> None: # --- sizes --------------------------------------------------------------------- @property - def n_t(self) -> int: - """Return the number of coarse time samples. + def n_t(self) -> int: # numpydoc ignore=RT01 + """Number of coarse time samples. - Returns - ------- - int - The length of ``t``. + The length of ``t``. """ return self.t.size @property - def n_to(self) -> int: - """Return the number of fine time samples. + def n_to(self) -> int: # numpydoc ignore=RT01 + """Number of fine time samples. - Returns - ------- - int - The length of ``t_fine``. + The length of ``t_fine``. """ return self.t_fine.size @property - def n_omega(self) -> int: - """Return the number of coarse spectral bins. + def n_omega(self) -> int: # numpydoc ignore=RT01 + """Number of coarse spectral bins. - Returns - ------- - int - The length of ``omega``. + The length of ``omega``. """ return self.omega.size @property - def n_omega_fine(self) -> int: - """Return the number of fine spectral bins. + def n_omega_fine(self) -> int: # numpydoc ignore=RT01 + """Number of fine spectral bins. - Returns - ------- - int - The length of ``omega_fine``. + The length of ``omega_fine``. """ 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 @@ -1389,137 +1341,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) diff --git a/src/solprop/log.py b/src/solprop/log.py index 809a31bf..d9bf05e9 100644 --- a/src/solprop/log.py +++ b/src/solprop/log.py @@ -298,46 +298,34 @@ class PlaneSummary(Protocol): """ @property - def n_steps(self) -> int: - """Return the steps taken to reach the plane, accepted and rejected together. + def n_steps(self) -> int: # numpydoc ignore=RT01 + """The steps taken to reach the plane, accepted and rejected together. - Returns - ------- - int - The attempted steps. + The attempted steps. """ ... @property - def n_rejected(self) -> int: - """Return the rejected steps among them. + def n_rejected(self) -> int: # numpydoc ignore=RT01 + """Rejected steps among them. - Returns - ------- - int - The rejected steps. + The rejected steps. """ ... @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. + def wall_time(self) -> float: # numpydoc ignore=RT01 + """The seconds the plane took. - Returns - ------- - float - Wall time, s. + Wall time, s. """ ... @@ -868,66 +856,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) @@ -1385,13 +1358,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 + """Tqdm instance while the bar 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 diff --git a/src/solprop/modes/capillary.py b/src/solprop/modes/capillary.py index 0201c0e5..08407c05 100644 --- a/src/solprop/modes/capillary.py +++ b/src/solprop/modes/capillary.py @@ -474,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 -------- @@ -639,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 -------- @@ -669,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: