solprop 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- solprop/__init__.py +121 -0
- solprop/_display.py +361 -0
- solprop/_helpers.py +223 -0
- solprop/_host.py +264 -0
- solprop/_interrupts.py +210 -0
- solprop/_numba_cache.py +557 -0
- solprop/_process.py +574 -0
- solprop/adiabatic.py +820 -0
- solprop/backends/__init__.py +35 -0
- solprop/backends/adams_core.py +557 -0
- solprop/backends/batch.py +596 -0
- solprop/backends/bodies.py +2123 -0
- solprop/backends/driver.py +2555 -0
- solprop/backends/exponential.py +714 -0
- solprop/backends/low_storage.py +727 -0
- solprop/backends/memory.py +2255 -0
- solprop/backends/mlx_gpu/__init__.py +21 -0
- solprop/backends/mlx_gpu/arithmetic.py +710 -0
- solprop/backends/mlx_gpu/backend.py +1598 -0
- solprop/backends/mlx_gpu/batch.py +78 -0
- solprop/backends/mlx_gpu/envelope.py +374 -0
- solprop/backends/mlx_gpu/passes.py +720 -0
- solprop/backends/multistep.py +4082 -0
- solprop/backends/multistep_general.py +455 -0
- solprop/backends/numba_cpu/__init__.py +20 -0
- solprop/backends/numba_cpu/backend.py +5238 -0
- solprop/backends/numba_cpu/batch.py +185 -0
- solprop/backends/numba_cpu/coupled_flow.py +1086 -0
- solprop/backends/numba_cpu/dispersive.py +1644 -0
- solprop/backends/numba_cpu/dispersive_general.py +451 -0
- solprop/backends/numba_cpu/dispersive_spatial.py +539 -0
- solprop/backends/numba_cpu/envelope_plasma.py +471 -0
- solprop/backends/numba_cpu/fft.py +1465 -0
- solprop/backends/numba_cpu/field_plasma.py +375 -0
- solprop/backends/numba_cpu/general_modal.py +981 -0
- solprop/backends/numba_cpu/kernels.py +2036 -0
- solprop/backends/numba_cpu/low_storage.py +1305 -0
- solprop/backends/numba_cpu/multistep.py +1937 -0
- solprop/backends/numba_cpu/openmp.py +349 -0
- solprop/backends/numba_cpu/pass_timers.py +162 -0
- solprop/backends/numba_cpu/planar.py +420 -0
- solprop/backends/numba_cpu/spatial.py +1202 -0
- solprop/backends/numba_cpu/tables.py +108 -0
- solprop/backends/numpy_ref/__init__.py +65 -0
- solprop/backends/numpy_ref/admission.py +717 -0
- solprop/backends/numpy_ref/backend.py +3825 -0
- solprop/backends/numpy_ref/batch.py +213 -0
- solprop/backends/numpy_ref/envelope_plasma.py +180 -0
- solprop/backends/numpy_ref/general_modal.py +1151 -0
- solprop/backends/numpy_ref/passes.py +2230 -0
- solprop/backends/numpy_ref/single.py +1288 -0
- solprop/backends/numpy_ref/tdse.py +1026 -0
- solprop/backends/numpy_ref/transforms.py +208 -0
- solprop/backends/populations.py +295 -0
- solprop/backends/protocol.py +787 -0
- solprop/backends/registry.py +653 -0
- solprop/backends/spatial.py +1152 -0
- solprop/boundaries.py +1204 -0
- solprop/constants.py +124 -0
- solprop/coupling/__init__.py +135 -0
- solprop/coupling/coupler.py +2906 -0
- solprop/coupling/elements.py +3244 -0
- solprop/coupling/envelopes.py +273 -0
- solprop/coupling/models.py +941 -0
- solprop/coupling/outcouple.py +226 -0
- solprop/coupling/plane.py +1847 -0
- solprop/coupling/port.py +412 -0
- solprop/coupling/remap.py +555 -0
- solprop/coupling/transport.py +517 -0
- solprop/driver/__init__.py +60 -0
- solprop/driver/_scan_batch.py +449 -0
- solprop/driver/_scan_context.py +24 -0
- solprop/driver/_scan_model.py +327 -0
- solprop/driver/_scan_run.py +899 -0
- solprop/driver/batch.py +423 -0
- solprop/driver/executors/__init__.py +38 -0
- solprop/driver/executors/_queue_state.py +208 -0
- solprop/driver/executors/_request.py +36 -0
- solprop/driver/executors/batch.py +73 -0
- solprop/driver/executors/local.py +33 -0
- solprop/driver/executors/pool.py +604 -0
- solprop/driver/executors/queue.py +706 -0
- solprop/driver/executors/slurm.py +365 -0
- solprop/driver/interface.py +1797 -0
- solprop/driver/material_output.py +986 -0
- solprop/driver/memory.py +822 -0
- solprop/driver/mixtures.py +382 -0
- solprop/driver/run.py +1489 -0
- solprop/driver/scaffold.py +259 -0
- solprop/driver/scan.py +91 -0
- solprop/driver/simulation.py +11064 -0
- solprop/driver/summary.py +847 -0
- solprop/driver/tdse.py +3172 -0
- solprop/driver/tdse_consistency.py +1219 -0
- solprop/exceptions.py +349 -0
- solprop/fourier.py +1025 -0
- solprop/gasflow.py +2087 -0
- solprop/grid.py +2341 -0
- solprop/hankel.py +198 -0
- solprop/linear.py +612 -0
- solprop/log.py +1541 -0
- solprop/modes/__init__.py +195 -0
- solprop/modes/_stepindex_vector.py +394 -0
- solprop/modes/antiresonant.py +1144 -0
- solprop/modes/base.py +475 -0
- solprop/modes/capillary.py +3806 -0
- solprop/modes/collection.py +1048 -0
- solprop/modes/connection.py +989 -0
- solprop/modes/fill.py +733 -0
- solprop/modes/graded_index.py +1272 -0
- solprop/modes/guide.py +2795 -0
- solprop/modes/material.py +806 -0
- solprop/modes/protocol.py +568 -0
- solprop/modes/quadrature.py +1732 -0
- solprop/modes/rectangular.py +887 -0
- solprop/modes/slab.py +1306 -0
- solprop/modes/stepindex.py +1630 -0
- solprop/modes/tabulated.py +1032 -0
- solprop/modes/vector.py +431 -0
- solprop/monitor.py +1018 -0
- solprop/noise.py +515 -0
- solprop/nonlinear/__init__.py +114 -0
- solprop/nonlinear/envelope_plasma.py +814 -0
- solprop/nonlinear/gnlse.py +2699 -0
- solprop/nonlinear/ionisation.py +795 -0
- solprop/nonlinear/mixtures.py +730 -0
- solprop/nonlinear/normalisation.py +462 -0
- solprop/nonlinear/optmat_raman.py +1686 -0
- solprop/nonlinear/optmat_rates.py +1312 -0
- solprop/nonlinear/raman.py +2705 -0
- solprop/nonlinear/responses.py +1611 -0
- solprop/nonlinear/tdse.py +890 -0
- solprop/optics.py +2078 -0
- solprop/output/__init__.py +110 -0
- solprop/output/_file_lock.py +97 -0
- solprop/output/_scan_lock.py +122 -0
- solprop/output/_writer_lock.py +79 -0
- solprop/output/capability.py +335 -0
- solprop/output/hdf5.py +2134 -0
- solprop/output/memory.py +668 -0
- solprop/output/scan.py +867 -0
- solprop/output/scan_artifacts.py +136 -0
- solprop/output/schema.py +4465 -0
- solprop/output/snapshots.py +968 -0
- solprop/output/tdse_archive.py +2307 -0
- solprop/physdata/__init__.py +190 -0
- solprop/physdata/_optmat.py +833 -0
- solprop/physdata/data/mirrors/HD120.csv +2408 -0
- solprop/physdata/data/mirrors/HD59_GDD.dat +2485 -0
- solprop/physdata/data/mirrors/NOTICE +26 -0
- solprop/physdata/data/mirrors/PC147.txt +2409 -0
- solprop/physdata/data/mirrors/PC1611.txt +2409 -0
- solprop/physdata/data/mirrors/PC1821.txt +2408 -0
- solprop/physdata/data/mirrors/PC70_GDD.csv +2408 -0
- solprop/physdata/data/mirrors/PC70_R.csv +2795 -0
- solprop/physdata/data/mirrors/UCxx-15FS_GD.csv +233 -0
- solprop/physdata/data/mirrors/UCxx-15FS_R.csv +1380 -0
- solprop/physdata/equation_of_state.py +255 -0
- solprop/physdata/gases.py +118 -0
- solprop/physdata/glasses.py +1771 -0
- solprop/physdata/metals.py +70 -0
- solprop/physdata/mixtures.py +1535 -0
- solprop/physdata/nonlinear_data.py +1209 -0
- solprop/physdata/raman_types.py +664 -0
- solprop/physdata/selection.py +1106 -0
- solprop/physdata/sellmeier.py +1279 -0
- solprop/physdata/species.py +1082 -0
- solprop/physdata/transport.py +395 -0
- solprop/plotting/__init__.py +75 -0
- solprop/plotting/propagation.py +724 -0
- solprop/plotting/spectrograms.py +229 -0
- solprop/plotting/statistics.py +247 -0
- solprop/plotting/style.py +625 -0
- solprop/polarisation.py +351 -0
- solprop/processing/__init__.py +172 -0
- solprop/processing/_examples.py +467 -0
- solprop/processing/axes.py +658 -0
- solprop/processing/coherence.py +275 -0
- solprop/processing/compression.py +1737 -0
- solprop/processing/fields.py +769 -0
- solprop/processing/mixtures.py +423 -0
- solprop/processing/modal.py +1248 -0
- solprop/processing/planes.py +570 -0
- solprop/processing/scans.py +962 -0
- solprop/processing/spectra.py +373 -0
- solprop/processing/time.py +934 -0
- solprop/profiles.py +3988 -0
- solprop/pulses.py +2445 -0
- solprop/py.typed +0 -0
- solprop/scan.py +189 -0
- solprop/sol.py +527 -0
- solprop/spatial.py +3096 -0
- solprop/stats.py +2966 -0
- solprop/stepper/__init__.py +74 -0
- solprop/stepper/controller.py +780 -0
- solprop/stepper/low_storage.py +155 -0
- solprop/stepper/multistep.py +963 -0
- solprop/stepper/runge_kutta.py +70 -0
- solprop/stepper/schedule.py +1070 -0
- solprop/stepper/tableau.py +797 -0
- solprop/tools.py +2860 -0
- solprop/translate/__init__.py +152 -0
- solprop/translate/chebyshev.py +2108 -0
- solprop/translate/coupled.py +3249 -0
- solprop/translate/coupling.py +384 -0
- solprop/translate/envelope_plasma.py +713 -0
- solprop/translate/expansion.py +1213 -0
- solprop/translate/flux.py +408 -0
- solprop/translate/free.py +269 -0
- solprop/translate/general.py +2302 -0
- solprop/translate/ionisation.py +729 -0
- solprop/translate/linop.py +3124 -0
- solprop/translate/masks.py +484 -0
- solprop/translate/modal.py +881 -0
- solprop/translate/modal_path.py +1149 -0
- solprop/translate/modal_tables.py +2365 -0
- solprop/translate/planar.py +442 -0
- solprop/translate/plasma_integration.py +369 -0
- solprop/translate/plasma_source.py +128 -0
- solprop/translate/precision.py +733 -0
- solprop/translate/problem.py +6728 -0
- solprop/translate/radial.py +717 -0
- solprop/translate/raman.py +749 -0
- solprop/translate/responses.py +3803 -0
- solprop/translate/spatial.py +1052 -0
- solprop/translate/stage.py +955 -0
- solprop/translate/tdse.py +1570 -0
- solprop/translate/tdse_source.py +313 -0
- solprop-0.1.0.dist-info/METADATA +201 -0
- solprop-0.1.0.dist-info/RECORD +233 -0
- solprop-0.1.0.dist-info/WHEEL +4 -0
- solprop-0.1.0.dist-info/licenses/LICENSE +674 -0
- solprop-0.1.0.dist-info/licenses/src/solprop/physdata/data/mirrors/NOTICE +26 -0
solprop/__init__.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 John C. Travers and contributors
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
"""Ultrafast nonlinear pulse propagation.
|
|
6
|
+
|
|
7
|
+
solprop models ultrafast pulse propagation with the unidirectional pulse propagation
|
|
8
|
+
equation (UPPE) and the generalised nonlinear Schrödinger equation (GNLSE), for the full
|
|
9
|
+
electric field or a complex envelope, in one mode, in many modes of a waveguide and
|
|
10
|
+
in radial or Cartesian free space. The design is described under ``docs/dev/`` and the
|
|
11
|
+
manual under ``docs/``.
|
|
12
|
+
|
|
13
|
+
The package installs a :class:`logging.NullHandler`; call
|
|
14
|
+
:func:`solprop.log.configure_logging` from a script to see progress and diagnostics.
|
|
15
|
+
|
|
16
|
+
Every submodule is an attribute of the package -- ``solprop.grid``, ``solprop.driver``,
|
|
17
|
+
``solprop.plotting`` -- loaded on first access (:pep:`562`), so ``dir(solprop)`` and tab
|
|
18
|
+
completion list them while ``import solprop`` stays within its import-time budget: the
|
|
19
|
+
modules that pull in h5py, matplotlib or a compiled backend are not imported until they
|
|
20
|
+
are used.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import importlib
|
|
24
|
+
import logging
|
|
25
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
26
|
+
from types import ModuleType
|
|
27
|
+
from typing import Final
|
|
28
|
+
|
|
29
|
+
# The scheduler's CPU grant, read before any OpenMP runtime binds the main thread.
|
|
30
|
+
import solprop._process # noqa: F401 # pyright: ignore[reportUnusedImport]
|
|
31
|
+
from solprop.exceptions import SolpropError
|
|
32
|
+
from solprop.log import configure_logging
|
|
33
|
+
|
|
34
|
+
__all__ = ["SolpropError", "__version__", "configure_logging"]
|
|
35
|
+
|
|
36
|
+
_SUBMODULES: Final = (
|
|
37
|
+
"adiabatic",
|
|
38
|
+
"backends",
|
|
39
|
+
"boundaries",
|
|
40
|
+
"constants",
|
|
41
|
+
"coupling",
|
|
42
|
+
"driver",
|
|
43
|
+
"exceptions",
|
|
44
|
+
"fourier",
|
|
45
|
+
"gasflow",
|
|
46
|
+
"grid",
|
|
47
|
+
"hankel",
|
|
48
|
+
"linear",
|
|
49
|
+
"log",
|
|
50
|
+
"modes",
|
|
51
|
+
"monitor",
|
|
52
|
+
"noise",
|
|
53
|
+
"nonlinear",
|
|
54
|
+
"optics",
|
|
55
|
+
"output",
|
|
56
|
+
"physdata",
|
|
57
|
+
"plotting",
|
|
58
|
+
"polarisation",
|
|
59
|
+
"processing",
|
|
60
|
+
"profiles",
|
|
61
|
+
"pulses",
|
|
62
|
+
"scan",
|
|
63
|
+
"sol",
|
|
64
|
+
"spatial",
|
|
65
|
+
"stats",
|
|
66
|
+
"stepper",
|
|
67
|
+
"tools",
|
|
68
|
+
"translate",
|
|
69
|
+
)
|
|
70
|
+
"""The submodules reachable as attributes; a test keeps it equal to what is on disk."""
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def __getattr__(name: str) -> ModuleType:
|
|
74
|
+
"""Import a submodule the first time it is asked for as an attribute.
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
name : str
|
|
79
|
+
The attribute looked up on the package.
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
types.ModuleType
|
|
84
|
+
The submodule, imported now and cached by the import system.
|
|
85
|
+
|
|
86
|
+
Raises
|
|
87
|
+
------
|
|
88
|
+
AttributeError
|
|
89
|
+
If `name` is not a submodule of the package.
|
|
90
|
+
"""
|
|
91
|
+
if name in _SUBMODULES:
|
|
92
|
+
return importlib.import_module(f"{__name__}.{name}")
|
|
93
|
+
msg = f"module {__name__!r} has no attribute {name!r}"
|
|
94
|
+
raise AttributeError(msg)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def __dir__() -> list[str]:
|
|
98
|
+
"""List the package's public surface, the submodules included whether or not loaded.
|
|
99
|
+
|
|
100
|
+
The names in ``__all__``, the submodules and the dunders; not the imports this
|
|
101
|
+
module happens to make, which are implementation and not part of the package.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
list of str
|
|
106
|
+
The sorted names ``dir`` and tab completion show.
|
|
107
|
+
"""
|
|
108
|
+
names = {*globals(), *_SUBMODULES}
|
|
109
|
+
return sorted(
|
|
110
|
+
name
|
|
111
|
+
for name in names
|
|
112
|
+
if name.startswith("__") or name in __all__ or name in _SUBMODULES
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
__version__ = version("solprop")
|
|
118
|
+
except PackageNotFoundError: # pragma: no cover - only when run from a source checkout
|
|
119
|
+
__version__ = "0.0.0"
|
|
120
|
+
|
|
121
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
solprop/_display.py
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 John C. Travers and contributors
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
"""Format objects for a person reading them at a prompt.
|
|
6
|
+
|
|
7
|
+
The package's printing rule, in one place: ``repr(x)`` is one line and never holds an
|
|
8
|
+
array, so that it is safe in a log record or an error message; ``str(x)`` is a short
|
|
9
|
+
aligned block in the units people quote (nm, fs, uJ, um, bar); and IPython and Jupyter
|
|
10
|
+
show that block when the object is the last expression of a cell, through
|
|
11
|
+
:meth:`Displayed._repr_pretty_`.
|
|
12
|
+
|
|
13
|
+
Printing never evaluates physics, never warns and never touches a file it does not
|
|
14
|
+
already own: a block is built from numbers the object already holds.
|
|
15
|
+
|
|
16
|
+
This module imports nothing from the rest of the package, and NumPy only where an array
|
|
17
|
+
is actually summarised, so that any module may use it.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import math
|
|
21
|
+
import textwrap
|
|
22
|
+
from collections.abc import Iterable, Sequence
|
|
23
|
+
from typing import Protocol, runtime_checkable
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"Displayed",
|
|
27
|
+
"block",
|
|
28
|
+
"engineering",
|
|
29
|
+
"line",
|
|
30
|
+
"phase_text",
|
|
31
|
+
"summarise",
|
|
32
|
+
"tail",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
_PREFIXES = {
|
|
36
|
+
-18: "a",
|
|
37
|
+
-15: "f",
|
|
38
|
+
-12: "p",
|
|
39
|
+
-9: "n",
|
|
40
|
+
-6: "u",
|
|
41
|
+
-3: "m",
|
|
42
|
+
0: "",
|
|
43
|
+
3: "k",
|
|
44
|
+
6: "M",
|
|
45
|
+
9: "G",
|
|
46
|
+
12: "T",
|
|
47
|
+
15: "P",
|
|
48
|
+
18: "E",
|
|
49
|
+
}
|
|
50
|
+
"""SI prefixes by decimal exponent. ``u`` stands for micro: the package's text is ASCII
|
|
51
|
+
wherever a terminal, a log file or an HDF5 attribute may have to carry it."""
|
|
52
|
+
|
|
53
|
+
_WIDTH = 88
|
|
54
|
+
"""Longest line of a block, the package's line length."""
|
|
55
|
+
|
|
56
|
+
_WRAPPED = 4
|
|
57
|
+
"""Most lines a wrapped value takes in a block; a longer one ends with an ellipsis."""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@runtime_checkable
|
|
61
|
+
class _Printer(Protocol):
|
|
62
|
+
"""The part of IPython's pretty printer that :class:`Displayed` uses."""
|
|
63
|
+
|
|
64
|
+
def text(self, value: str) -> None:
|
|
65
|
+
"""Write text.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
value : str
|
|
70
|
+
The text.
|
|
71
|
+
"""
|
|
72
|
+
...
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def engineering(value: float | None, unit: str = "", digits: int = 4) -> str:
|
|
76
|
+
"""Return a number with an SI prefix, as a person would write it.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
value : float or None
|
|
81
|
+
The number, in the SI unit named by `unit`.
|
|
82
|
+
unit : str, optional
|
|
83
|
+
The unit's symbol, without a prefix: ``"m"``, ``"s"``, ``"J"``, ``"W"``.
|
|
84
|
+
With an empty unit no prefix is used and the number is written plainly.
|
|
85
|
+
digits : int, optional
|
|
86
|
+
Significant figures.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
str
|
|
91
|
+
For example ``"800 nm"``, ``"30 fs"``, ``"1.5 m"``; ``"none"`` for ``None``
|
|
92
|
+
and ``"nan"``, ``"inf"`` or ``"-inf"`` for a value that is not finite.
|
|
93
|
+
|
|
94
|
+
Examples
|
|
95
|
+
--------
|
|
96
|
+
>>> from solprop._display import engineering
|
|
97
|
+
>>> engineering(800e-9, "m"), engineering(1.5, "m"), engineering(275e-6, "J")
|
|
98
|
+
('800 nm', '1.5 m', '275 uJ')
|
|
99
|
+
>>> engineering(float("nan"), "s"), engineering(None, "s"), engineering(0.0, "W")
|
|
100
|
+
('nan', 'none', '0 W')
|
|
101
|
+
"""
|
|
102
|
+
if value is None:
|
|
103
|
+
return "none"
|
|
104
|
+
number = float(value)
|
|
105
|
+
if not math.isfinite(number):
|
|
106
|
+
return str(number)
|
|
107
|
+
if not unit:
|
|
108
|
+
return f"{number:.{digits}g}"
|
|
109
|
+
if number == 0.0:
|
|
110
|
+
return f"0 {unit}"
|
|
111
|
+
exponent = 3 * math.floor(math.log10(abs(number)) / 3.0)
|
|
112
|
+
exponent = min(max(exponent, min(_PREFIXES)), max(_PREFIXES))
|
|
113
|
+
scaled = number / 10.0**exponent
|
|
114
|
+
# Rounding to the requested figures can carry 999.96 over to 1000.
|
|
115
|
+
if abs(float(f"{scaled:.{digits}g}")) >= 1000.0 and exponent < max(_PREFIXES):
|
|
116
|
+
exponent += 3
|
|
117
|
+
scaled = number / 10.0**exponent
|
|
118
|
+
return f"{scaled:.{digits}g} {_PREFIXES[exponent]}{unit}"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def summarise(values: object, unit: str = "") -> str:
|
|
122
|
+
"""Return a one-phrase summary of an array, never its contents.
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
values : array_like
|
|
127
|
+
The array.
|
|
128
|
+
unit : str, optional
|
|
129
|
+
SI unit of the values, for the range; empty to give the shape alone.
|
|
130
|
+
|
|
131
|
+
Returns
|
|
132
|
+
-------
|
|
133
|
+
str
|
|
134
|
+
For example ``"<201 values, 0 m to 1.5 m>"`` or ``"<2x1x8192 complex>"``.
|
|
135
|
+
|
|
136
|
+
Examples
|
|
137
|
+
--------
|
|
138
|
+
>>> import numpy as np
|
|
139
|
+
>>> from solprop._display import summarise
|
|
140
|
+
>>> summarise(np.linspace(0.0, 1.5, 201), "m")
|
|
141
|
+
'<201 values, 0 m to 1.5 m>'
|
|
142
|
+
>>> summarise(np.zeros((2, 1, 8), dtype=complex))
|
|
143
|
+
'<2x1x8 complex>'
|
|
144
|
+
"""
|
|
145
|
+
import numpy as np # noqa: PLC0415 - kept out of the import of modules that only format scalars
|
|
146
|
+
|
|
147
|
+
array = np.asarray(values)
|
|
148
|
+
if array.ndim != 1 or not unit or array.size == 0 or np.iscomplexobj(array):
|
|
149
|
+
shape = "x".join(str(size) for size in array.shape) or "scalar"
|
|
150
|
+
kind = "complex" if np.iscomplexobj(array) else "values"
|
|
151
|
+
return f"<{shape} {kind}>"
|
|
152
|
+
low, high = float(np.min(array)), float(np.max(array))
|
|
153
|
+
return (
|
|
154
|
+
f"<{array.size} values, {engineering(low, unit)} to {engineering(high, unit)}>"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
_PHASE_NAMES = ("CEP", "delay", "GDD", "TOD", "FOD")
|
|
159
|
+
"""What the first Taylor coefficients of a spectral phase are called."""
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def phase_text(coefficients: Iterable[float]) -> str:
|
|
163
|
+
"""Return Taylor spectral-phase coefficients as people quote them.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
coefficients : iterable of float
|
|
168
|
+
``c_0, c_1, ...`` in ``s**n``; a tuple or an array.
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
str
|
|
173
|
+
For example ``"CEP 0.3 rad, GDD 250 fs^2"``: zero coefficients are left out,
|
|
174
|
+
order ``n`` is in ``fs^n``, and ``"none"`` stands for no phase at all.
|
|
175
|
+
|
|
176
|
+
Examples
|
|
177
|
+
--------
|
|
178
|
+
>>> from solprop._display import phase_text
|
|
179
|
+
>>> phase_text((0.3, 0.0, 250e-30, -4000e-45))
|
|
180
|
+
'CEP 0.3 rad, GDD 250 fs^2, TOD -4000 fs^3'
|
|
181
|
+
>>> phase_text(())
|
|
182
|
+
'none'
|
|
183
|
+
"""
|
|
184
|
+
parts = []
|
|
185
|
+
for order, value in enumerate(coefficients):
|
|
186
|
+
if float(value) == 0.0:
|
|
187
|
+
continue
|
|
188
|
+
name = _PHASE_NAMES[order] if order < len(_PHASE_NAMES) else f"order {order}"
|
|
189
|
+
unit = "rad" if order == 0 else ("fs" if order == 1 else f"fs^{order}")
|
|
190
|
+
parts.append(f"{name} {float(value) * 1e15**order:.4g} {unit}")
|
|
191
|
+
return ", ".join(parts) or "none"
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def tail(text: str, width: int = 68) -> str:
|
|
195
|
+
"""Return the end of a long text, which for a path is the part that matters.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
text : str
|
|
200
|
+
The text, typically a file path.
|
|
201
|
+
width : int, optional
|
|
202
|
+
Longest result, in characters.
|
|
203
|
+
|
|
204
|
+
Returns
|
|
205
|
+
-------
|
|
206
|
+
str
|
|
207
|
+
The text itself, or an ellipsis and its last ``width - 3`` characters.
|
|
208
|
+
|
|
209
|
+
Examples
|
|
210
|
+
--------
|
|
211
|
+
>>> from solprop._display import tail
|
|
212
|
+
>>> tail("/a/very/long/path/to/run.h5", 12)
|
|
213
|
+
'...to/run.h5'
|
|
214
|
+
"""
|
|
215
|
+
return text if len(text) <= width else "..." + text[-(width - 3) :]
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def line(name: str, fields: Iterable[tuple[str, object]]) -> str:
|
|
219
|
+
"""Return a one-line ``Name(key=value, ...)`` description.
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
name : str
|
|
224
|
+
The class name.
|
|
225
|
+
fields : iterable of tuple
|
|
226
|
+
``(key, value)`` pairs; a value is written with ``str``, and a pair whose key
|
|
227
|
+
is empty is written as the value alone.
|
|
228
|
+
|
|
229
|
+
Returns
|
|
230
|
+
-------
|
|
231
|
+
str
|
|
232
|
+
The line.
|
|
233
|
+
|
|
234
|
+
Examples
|
|
235
|
+
--------
|
|
236
|
+
>>> from solprop._display import line
|
|
237
|
+
>>> line("Grid", [("n_t", 8192), ("", "field")])
|
|
238
|
+
'Grid(n_t=8192, field)'
|
|
239
|
+
"""
|
|
240
|
+
parts = [f"{key}={value}" if key else str(value) for key, value in fields]
|
|
241
|
+
return f"{name}({', '.join(parts)})"
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def block(title: str, rows: Sequence[tuple[str, object]]) -> str:
|
|
245
|
+
"""Return an aligned multi-line description.
|
|
246
|
+
|
|
247
|
+
Parameters
|
|
248
|
+
----------
|
|
249
|
+
title : str
|
|
250
|
+
The first line: the class and its headline.
|
|
251
|
+
rows : sequence of tuple
|
|
252
|
+
``(key, value)`` pairs, one line each. A value holding newlines is indented
|
|
253
|
+
under its key; a line longer than the package's 88 columns is wrapped at a
|
|
254
|
+
space, its continuation indented under its value, over at most four lines,
|
|
255
|
+
and what is left over, or a single word too long for a line, is cut with an
|
|
256
|
+
ellipsis, so that a block is always safe to paste into a page.
|
|
257
|
+
|
|
258
|
+
Returns
|
|
259
|
+
-------
|
|
260
|
+
str
|
|
261
|
+
The block, without a trailing newline.
|
|
262
|
+
|
|
263
|
+
Examples
|
|
264
|
+
--------
|
|
265
|
+
>>> from solprop._display import block
|
|
266
|
+
>>> print(block("Pulse", [("energy", "275 uJ"), ("duration", "7.5 fs")]))
|
|
267
|
+
Pulse
|
|
268
|
+
energy: 275 uJ
|
|
269
|
+
duration: 7.5 fs
|
|
270
|
+
"""
|
|
271
|
+
width = max((len(key) for key, _ in rows), default=0) + 1
|
|
272
|
+
under = " " * (width + 3)
|
|
273
|
+
# A long title continues deeper than the rows, so it cannot be read as one.
|
|
274
|
+
lines = _fitted("", title, " ")
|
|
275
|
+
for key, value in rows:
|
|
276
|
+
first, *rest = str(value).split("\n")
|
|
277
|
+
lines.extend(_fitted(f" {key + ':':<{width}} ", first, under))
|
|
278
|
+
for extra in rest:
|
|
279
|
+
deeper = extra[: len(extra) - len(extra.lstrip())]
|
|
280
|
+
lines.extend(_fitted(under + deeper, extra.lstrip(), under + deeper))
|
|
281
|
+
return "\n".join(lines)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _fitted(lead: str, value: str, indent: str) -> list[str]:
|
|
285
|
+
"""Return one line of a block within the package's width.
|
|
286
|
+
|
|
287
|
+
Parameters
|
|
288
|
+
----------
|
|
289
|
+
lead : str
|
|
290
|
+
What precedes the value on its first line: the key's column, or the
|
|
291
|
+
indentation of a continuation.
|
|
292
|
+
value : str
|
|
293
|
+
The text, wrapped at its spaces when the line is too long.
|
|
294
|
+
indent : str
|
|
295
|
+
The indentation of each wrapped continuation, under the value.
|
|
296
|
+
|
|
297
|
+
Returns
|
|
298
|
+
-------
|
|
299
|
+
list of str
|
|
300
|
+
The line or its wrapped pieces, at most :data:`_WRAPPED` of them; the last
|
|
301
|
+
kept piece of a longer value, and a piece holding a single word too long for
|
|
302
|
+
the width, end with an ellipsis.
|
|
303
|
+
"""
|
|
304
|
+
if len(lead) + len(value) <= _WIDTH:
|
|
305
|
+
return [lead + value]
|
|
306
|
+
pieces = textwrap.wrap(
|
|
307
|
+
value,
|
|
308
|
+
_WIDTH,
|
|
309
|
+
initial_indent=lead,
|
|
310
|
+
subsequent_indent=indent,
|
|
311
|
+
break_long_words=False,
|
|
312
|
+
break_on_hyphens=False,
|
|
313
|
+
) or [lead]
|
|
314
|
+
if len(pieces) > _WRAPPED:
|
|
315
|
+
pieces = [*pieces[: _WRAPPED - 1], pieces[_WRAPPED - 1][: _WIDTH - 4] + " ..."]
|
|
316
|
+
return [
|
|
317
|
+
piece if len(piece) <= _WIDTH else piece[: _WIDTH - 3] + "..."
|
|
318
|
+
for piece in pieces
|
|
319
|
+
]
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
class Displayed:
|
|
323
|
+
"""Give a class the package's printing: a block for ``str`` and for IPython.
|
|
324
|
+
|
|
325
|
+
A class provides ``_describe()`` returning its title and its rows, and its own
|
|
326
|
+
one-line ``__repr__``; this mixin turns the first into ``__str__`` and into the
|
|
327
|
+
text IPython and Jupyter show for the last expression of a cell. The plain Python
|
|
328
|
+
prompt, log records and error messages keep the one-line ``repr``.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
def _describe(self) -> tuple[str, Sequence[tuple[str, object]]]:
|
|
332
|
+
"""Return the title and the rows of the block.
|
|
333
|
+
|
|
334
|
+
Returns
|
|
335
|
+
-------
|
|
336
|
+
tuple
|
|
337
|
+
The first line, and ``(key, value)`` pairs for the rest.
|
|
338
|
+
"""
|
|
339
|
+
return repr(self), ()
|
|
340
|
+
|
|
341
|
+
def __str__(self) -> str:
|
|
342
|
+
"""Return the aligned block.
|
|
343
|
+
|
|
344
|
+
Returns
|
|
345
|
+
-------
|
|
346
|
+
str
|
|
347
|
+
The title and one line per row.
|
|
348
|
+
"""
|
|
349
|
+
return block(*self._describe())
|
|
350
|
+
|
|
351
|
+
def _repr_pretty_(self, printer: _Printer, cycle: bool) -> None:
|
|
352
|
+
"""Write the block for IPython's pretty printer.
|
|
353
|
+
|
|
354
|
+
Parameters
|
|
355
|
+
----------
|
|
356
|
+
printer : object
|
|
357
|
+
IPython's printer.
|
|
358
|
+
cycle : bool
|
|
359
|
+
Whether the object was met again while printing itself.
|
|
360
|
+
"""
|
|
361
|
+
printer.text(repr(self) if cycle else str(self))
|
solprop/_helpers.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 John C. Travers and contributors
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
"""The evaluations a backend supplies to solprop's shared per-stage and per-step code.
|
|
6
|
+
|
|
7
|
+
Two small scalar computations run on every backend's path: a scalar table's Chebyshev
|
|
8
|
+
rows at a step's positions (behind every stage bundle and anchor, and an expansion's
|
|
9
|
+
rate rows; :meth:`solprop.translate.chebyshev.PiecewiseChebyshev.at_positions`) and
|
|
10
|
+
the Lagrange basis of an Adams formula on its actual nodes
|
|
11
|
+
(:func:`solprop.stepper.multistep.integrated_weights` and
|
|
12
|
+
:func:`~solprop.stepper.multistep.lagrange_coefficients`). The code around them is
|
|
13
|
+
shared, but **which implementation evaluates them belongs to the backend that runs the
|
|
14
|
+
stage** (SEP 29 section 6.6, R16), never to a process-wide switch or to what happens
|
|
15
|
+
to be installed: the NumPy reference must not run compiled code it exists to check,
|
|
16
|
+
and one simulation must not change another's arithmetic.
|
|
17
|
+
|
|
18
|
+
A backend names its record in ``StepDriver.helpers`` and the shared driver, march and
|
|
19
|
+
engines pass it down: ``numpy_ref`` takes :data:`PLAIN`, the uncompiled Python forms;
|
|
20
|
+
``numba_cpu`` takes :data:`NUMBA`, the same functions compiled; the native backend
|
|
21
|
+
takes its own record of C++ bindings, defined in its package. All three perform the
|
|
22
|
+
same IEEE operations in the same order, strict (no contraction, no reassociation;
|
|
23
|
+
SEP 25 section 4.2), so they agree to the last bit wherever their arithmetic is IEEE.
|
|
24
|
+
A record may also form a whole exponential Adams trial's coefficient tables in one
|
|
25
|
+
call (:attr:`Helpers.tables`), the composition of
|
|
26
|
+
:func:`solprop.backends.multistep.trial_tables` over its own basis, which its
|
|
27
|
+
resident engine reads every trial; the others compose them from the basis.
|
|
28
|
+
|
|
29
|
+
Translation is not a backend's: it runs before any backend exists and builds the
|
|
30
|
+
problem every backend receives, so it keeps the shared reference compiled by Numba
|
|
31
|
+
(:func:`solprop.translate.chebyshev.clenshaw`, and a table called without a record),
|
|
32
|
+
and the shared functions' defaults are :data:`NUMBA` for callers outside a backend. A
|
|
33
|
+
z table's state-sized evaluation is not a stage scalar and stays on that shared pass.
|
|
34
|
+
|
|
35
|
+
Like ``_display`` and ``_numba_cache``, this module imports nothing from the rest of
|
|
36
|
+
the package at import time, so any layer may use it; each record imports its
|
|
37
|
+
functions from where they live at the first evaluation.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
import functools
|
|
41
|
+
from collections.abc import Callable
|
|
42
|
+
from dataclasses import dataclass, field
|
|
43
|
+
|
|
44
|
+
import numpy as np
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"NUMBA",
|
|
48
|
+
"PLAIN",
|
|
49
|
+
"BasisFunction",
|
|
50
|
+
"Helpers",
|
|
51
|
+
"PointsFunction",
|
|
52
|
+
"TablesFunction",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
PointsFunction = Callable[
|
|
56
|
+
[np.ndarray, np.ndarray, float, float, np.ndarray, np.ndarray], int
|
|
57
|
+
]
|
|
58
|
+
"""``points(z, edges, lower, upper, c, out) -> int``: a table's rows at positions.
|
|
59
|
+
|
|
60
|
+
The contract of :func:`solprop.translate.chebyshev._clenshaw_points`: ``z`` float64
|
|
61
|
+
``(n_points,)``, ``edges`` float64 ``(n_intervals + 1,)``, ``c`` float64 or complex128
|
|
62
|
+
``(n_intervals, n_deg, n_values)`` and ``out`` of ``c``'s dtype ``(n_points,
|
|
63
|
+
n_values)``, all C-contiguous; the result is the first position outside ``[lower,
|
|
64
|
+
upper]`` or ``-1``.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
BasisFunction = Callable[[np.ndarray, float, bool], tuple[np.ndarray, np.ndarray]]
|
|
68
|
+
"""``basis(points, upper, integrate) -> (rows, weights)``: an Adams formula's basis.
|
|
69
|
+
|
|
70
|
+
The contract of :func:`solprop.stepper.multistep._basis_rows`: ``points`` float64
|
|
71
|
+
``(n,)`` and C-contiguous; ``rows`` ``(n, n)`` (zero when integrating) and ``weights``
|
|
72
|
+
``(n,)`` (unset when not).
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
TablesFunction = Callable[
|
|
76
|
+
[np.ndarray, int, list[int], bool],
|
|
77
|
+
tuple[
|
|
78
|
+
np.ndarray,
|
|
79
|
+
np.ndarray,
|
|
80
|
+
np.ndarray,
|
|
81
|
+
np.ndarray,
|
|
82
|
+
np.ndarray,
|
|
83
|
+
np.ndarray,
|
|
84
|
+
np.ndarray,
|
|
85
|
+
],
|
|
86
|
+
]
|
|
87
|
+
"""``tables(nodes, order, orders, compensated) -> tables``: a trial's tables at once.
|
|
88
|
+
|
|
89
|
+
The contract of :func:`solprop.backends.multistep.trial_tables`, whose composition over
|
|
90
|
+
the record's own basis it performs in one call, with the same operations in the same
|
|
91
|
+
order: ``nodes`` float64 ``(n,)`` and C-contiguous, newest first; the result is that
|
|
92
|
+
function's seven arrays.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass(frozen=True)
|
|
97
|
+
class Helpers:
|
|
98
|
+
"""The per-stage and per-step evaluations of one backend.
|
|
99
|
+
|
|
100
|
+
Each operation is loaded at its first use and kept: a record names its functions
|
|
101
|
+
without importing them, so constructing or importing one loads nothing (the Numba
|
|
102
|
+
record compiles at its first evaluation, exactly as the cached dispatchers it
|
|
103
|
+
holds always have).
|
|
104
|
+
|
|
105
|
+
Attributes
|
|
106
|
+
----------
|
|
107
|
+
name : str
|
|
108
|
+
What the record is, for messages and tests.
|
|
109
|
+
load_points : collections.abc.Callable
|
|
110
|
+
Returns the :data:`PointsFunction`.
|
|
111
|
+
load_basis : collections.abc.Callable
|
|
112
|
+
Returns the :data:`BasisFunction`.
|
|
113
|
+
load_tables : collections.abc.Callable or None, optional
|
|
114
|
+
Returns the :data:`TablesFunction`, for a record that forms an exponential
|
|
115
|
+
Adams trial's tables in one call; ``None``, the default, composes them from
|
|
116
|
+
the basis.
|
|
117
|
+
|
|
118
|
+
Examples
|
|
119
|
+
--------
|
|
120
|
+
>>> import numpy as np
|
|
121
|
+
>>> from solprop._helpers import PLAIN
|
|
122
|
+
>>> rows, weights = PLAIN.basis(np.array([1.0, 0.0]), 1.0, True)
|
|
123
|
+
>>> weights.tolist()
|
|
124
|
+
[0.5, 0.5]
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
name: str
|
|
128
|
+
load_points: Callable[[], PointsFunction] = field(repr=False)
|
|
129
|
+
load_basis: Callable[[], BasisFunction] = field(repr=False)
|
|
130
|
+
load_tables: Callable[[], TablesFunction] | None = field(default=None, repr=False)
|
|
131
|
+
|
|
132
|
+
@functools.cached_property
|
|
133
|
+
def points(self) -> PointsFunction:
|
|
134
|
+
"""Return the Chebyshev rows at several positions.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
collections.abc.Callable
|
|
139
|
+
The :data:`PointsFunction`.
|
|
140
|
+
"""
|
|
141
|
+
return self.load_points()
|
|
142
|
+
|
|
143
|
+
@functools.cached_property
|
|
144
|
+
def basis(self) -> BasisFunction:
|
|
145
|
+
"""Return the Lagrange basis on an Adams formula's nodes.
|
|
146
|
+
|
|
147
|
+
Returns
|
|
148
|
+
-------
|
|
149
|
+
collections.abc.Callable
|
|
150
|
+
The :data:`BasisFunction`.
|
|
151
|
+
"""
|
|
152
|
+
return self.load_basis()
|
|
153
|
+
|
|
154
|
+
@functools.cached_property
|
|
155
|
+
def tables(self) -> TablesFunction | None:
|
|
156
|
+
"""Return an exponential Adams trial's tables in one call, if the record has it.
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
collections.abc.Callable or None
|
|
161
|
+
The :data:`TablesFunction`, or ``None``: the tables are then composed from
|
|
162
|
+
:attr:`basis`.
|
|
163
|
+
"""
|
|
164
|
+
return None if self.load_tables is None else self.load_tables()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _plain_points() -> PointsFunction:
|
|
168
|
+
"""Return the Chebyshev rows' Python form.
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
collections.abc.Callable
|
|
173
|
+
:func:`solprop.translate.chebyshev._clenshaw_points`.
|
|
174
|
+
"""
|
|
175
|
+
from solprop.translate import chebyshev # noqa: PLC0415 - where it lives
|
|
176
|
+
|
|
177
|
+
return chebyshev._clenshaw_points
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _plain_basis() -> BasisFunction:
|
|
181
|
+
"""Return the Adams basis' Python form.
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
collections.abc.Callable
|
|
186
|
+
:func:`solprop.stepper.multistep._basis_rows`.
|
|
187
|
+
"""
|
|
188
|
+
from solprop.stepper import multistep # noqa: PLC0415 - where it lives
|
|
189
|
+
|
|
190
|
+
return multistep._basis_rows
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _numba_points() -> PointsFunction:
|
|
194
|
+
"""Return the Chebyshev rows compiled by Numba.
|
|
195
|
+
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
collections.abc.Callable
|
|
199
|
+
The cached dispatcher of :func:`solprop.translate.chebyshev._clenshaw_batch`.
|
|
200
|
+
"""
|
|
201
|
+
from solprop.translate import chebyshev # noqa: PLC0415 - numba on first use
|
|
202
|
+
|
|
203
|
+
return chebyshev._clenshaw_batch()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _numba_basis() -> BasisFunction:
|
|
207
|
+
"""Return the Adams basis compiled by Numba.
|
|
208
|
+
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
211
|
+
collections.abc.Callable
|
|
212
|
+
The cached dispatcher of :func:`solprop.stepper.multistep._basis`.
|
|
213
|
+
"""
|
|
214
|
+
from solprop.stepper import multistep # noqa: PLC0415 - numba on first use
|
|
215
|
+
|
|
216
|
+
return multistep._basis()
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
PLAIN = Helpers("plain", _plain_points, _plain_basis)
|
|
220
|
+
"""The uncompiled Python forms: the NumPy reference backend's."""
|
|
221
|
+
|
|
222
|
+
NUMBA = Helpers("numba", _numba_points, _numba_basis)
|
|
223
|
+
"""The same forms compiled by Numba: ``numba_cpu``'s, and the shared default."""
|