mxlpy-native 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- mxlpy_native-0.3.0/LICENSE +21 -0
- mxlpy_native-0.3.0/PKG-INFO +58 -0
- mxlpy_native-0.3.0/README.md +36 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/c/mxn_solvers.c +5 -0
- mxlpy_native-0.3.0/pyproject.toml +58 -0
- mxlpy_native-0.3.0/setup.py +113 -0
- mxlpy_native-0.3.0/src/mxlpy_native/__init__.py +76 -0
- mxlpy_native-0.3.0/src/mxlpy_native/__main__.py +35 -0
- mxlpy_native-0.3.0/src/mxlpy_native.egg-info/PKG-INFO +58 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/src/mxlpy_native.egg-info/SOURCES.txt +7 -1
- mxlpy_native-0.3.0/src/mxlpy_native.egg-info/requires.txt +1 -0
- mxlpy_native-0.3.0/tests/test_build_errors.py +48 -0
- mxlpy_native-0.3.0/tests/test_solvers.py +60 -0
- mxlpy_native-0.3.0/tests/test_vendor.py +32 -0
- mxlpy_native-0.2.0/PKG-INFO +0 -6
- mxlpy_native-0.2.0/README.md +0 -5
- mxlpy_native-0.2.0/pyproject.toml +0 -18
- mxlpy_native-0.2.0/setup.py +0 -26
- mxlpy_native-0.2.0/src/mxlpy_native/__init__.py +0 -18
- mxlpy_native-0.2.0/src/mxlpy_native.egg-info/PKG-INFO +0 -6
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/MANIFEST.in +0 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/c/vendor/HAIRER-LICENCE.txt +0 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/c/vendor/SCIPY-LICENCE.txt +0 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/c/vendor/VENDORED +0 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/c/vendor/blaslapack_declarations.h +0 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/c/vendor/dc_decsol.c +0 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/c/vendor/decsol.c +0 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/c/vendor/f2c.h +0 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/c/vendor/libf2c_stubs.c +0 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/c/vendor/lsoda.c +0 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/c/vendor/lsoda.h +0 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/c/vendor/mxs_lapack.c +0 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/c/vendor/radau5.c +0 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/setup.cfg +0 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/src/mxlpy_native.egg-info/dependency_links.txt +0 -0
- {mxlpy_native-0.2.0 → mxlpy_native-0.3.0}/src/mxlpy_native.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2025] [Marvin van Aalst, Anna Matuszyńska]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mxlpy-native
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Compiled ODE solvers for MxlPy's Native integrator
|
|
5
|
+
Author-email: Marvin van Aalst <marvin.vanaalst@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Computational-Biology-Aachen/mxlpy-native
|
|
8
|
+
Project-URL: Issues, https://github.com/Computational-Biology-Aachen/mxlpy-native/issues
|
|
9
|
+
Classifier: Operating System :: MacOS
|
|
10
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Programming Language :: C
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
License-File: c/vendor/HAIRER-LICENCE.txt
|
|
19
|
+
License-File: c/vendor/SCIPY-LICENCE.txt
|
|
20
|
+
Requires-Dist: llvmlite>=0.45
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# mxlpy-native
|
|
24
|
+
|
|
25
|
+
The compiled Radau and LSODA solvers behind [MxlPy](https://github.com/Computational-Biology-Aachen/MxlPy)'s `Native` integrator, plus `llvmlite`, which compiles models to machine code for them. Install it through MxlPy rather than on its own:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install "mxlpy[native]"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Platforms
|
|
32
|
+
|
|
33
|
+
Wheels are published for Linux (x86_64, aarch64; glibc 2.28+), macOS (arm64, 12+) and Windows (amd64, arm64), the platforms `llvmlite` has wheels for. On anything else, `mxlpy[native]` installs the WASM backend (`wasmtime`) instead, which runs the same solvers without a compiler, a little slower.
|
|
34
|
+
|
|
35
|
+
If pip has to build from the sdist anyway, it needs a C11 compiler (GCC 8+, Clang 7+ or MSVC 2019 16.8+), and nothing else: no Fortran, BLAS/LAPACK or Python headers. The build says what to install if the compiler is missing or fails.
|
|
36
|
+
|
|
37
|
+
## Troubleshooting
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
python -m mxlpy_native
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
prints the installed version, the solver ABI, and whether the compiled solvers and `llvmlite` load, with a fix for each failure.
|
|
44
|
+
|
|
45
|
+
## Development
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv sync # builds the extension in place
|
|
49
|
+
uv run pytest
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`c/vendor/` is copied from [mxl-solvers](https://github.com/Computational-Biology-Aachen/mxl-solvers) by its `scripts/vendor.sh`. `vendor/VENDORED` records the commit and file hashes, which `tests/test_vendor.py` checks. Fix solvers there, not here. `c/mxn_solvers.c` is the glue MxlPy calls, compiled unchanged into the wheel and into MxlPy's `solvers.wasm` (`scripts/build_native_wasm.sh` in MxlPy).
|
|
53
|
+
|
|
54
|
+
When an exported function's signature or meaning changes, bump `MXN_ABI_VERSION` in `c/mxn_solvers.c`, `ABI_VERSION` in `src/mxlpy_native/__init__.py` and the ABI MxlPy expects, then release both. MxlPy refuses a mismatched pair with an upgrade hint instead of crashing.
|
|
55
|
+
|
|
56
|
+
## Releasing
|
|
57
|
+
|
|
58
|
+
Bump `version` in `pyproject.toml` and publish a GitHub release `v<version>`. The release workflow builds the wheels with cibuildwheel plus an sdist and uploads them to PyPI via trusted publishing.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# mxlpy-native
|
|
2
|
+
|
|
3
|
+
The compiled Radau and LSODA solvers behind [MxlPy](https://github.com/Computational-Biology-Aachen/MxlPy)'s `Native` integrator, plus `llvmlite`, which compiles models to machine code for them. Install it through MxlPy rather than on its own:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install "mxlpy[native]"
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Platforms
|
|
10
|
+
|
|
11
|
+
Wheels are published for Linux (x86_64, aarch64; glibc 2.28+), macOS (arm64, 12+) and Windows (amd64, arm64), the platforms `llvmlite` has wheels for. On anything else, `mxlpy[native]` installs the WASM backend (`wasmtime`) instead, which runs the same solvers without a compiler, a little slower.
|
|
12
|
+
|
|
13
|
+
If pip has to build from the sdist anyway, it needs a C11 compiler (GCC 8+, Clang 7+ or MSVC 2019 16.8+), and nothing else: no Fortran, BLAS/LAPACK or Python headers. The build says what to install if the compiler is missing or fails.
|
|
14
|
+
|
|
15
|
+
## Troubleshooting
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
python -m mxlpy_native
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
prints the installed version, the solver ABI, and whether the compiled solvers and `llvmlite` load, with a fix for each failure.
|
|
22
|
+
|
|
23
|
+
## Development
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv sync # builds the extension in place
|
|
27
|
+
uv run pytest
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`c/vendor/` is copied from [mxl-solvers](https://github.com/Computational-Biology-Aachen/mxl-solvers) by its `scripts/vendor.sh`. `vendor/VENDORED` records the commit and file hashes, which `tests/test_vendor.py` checks. Fix solvers there, not here. `c/mxn_solvers.c` is the glue MxlPy calls, compiled unchanged into the wheel and into MxlPy's `solvers.wasm` (`scripts/build_native_wasm.sh` in MxlPy).
|
|
31
|
+
|
|
32
|
+
When an exported function's signature or meaning changes, bump `MXN_ABI_VERSION` in `c/mxn_solvers.c`, `ABI_VERSION` in `src/mxlpy_native/__init__.py` and the ABI MxlPy expects, then release both. MxlPy refuses a mismatched pair with an upgrade hint instead of crashing.
|
|
33
|
+
|
|
34
|
+
## Releasing
|
|
35
|
+
|
|
36
|
+
Bump `version` in `pyproject.toml` and publish a GitHub release `v<version>`. The release workflow builds the wheels with cibuildwheel plus an sdist and uploads them to PyPI via trusted publishing.
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
#define MXN_API
|
|
16
16
|
#endif
|
|
17
17
|
|
|
18
|
+
/* Bump whenever an exported signature or its meaning changes, together with ABI_VERSION
|
|
19
|
+
* in mxlpy_native/__init__.py and the version MxlPy's Native integrator expects. */
|
|
20
|
+
#define MXN_ABI_VERSION 1
|
|
21
|
+
MXN_API int mxn_abi_version(void) { return MXN_ABI_VERSION; }
|
|
22
|
+
|
|
18
23
|
extern int radau5_(integer *n, U_fp fcn, doublereal *x, doublereal *y, doublereal *xend, doublereal *h,
|
|
19
24
|
doublereal *rtol, doublereal *atol, integer *itol, U_fp jac, integer *ijac, integer *mljac,
|
|
20
25
|
integer *mujac, U_fp mas, integer *imas, integer *mlmas, integer *mumas, U_fp solout,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "setuptools.build_meta"
|
|
3
|
+
requires = ["setuptools>=77", "wheel"]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
authors = [{ email = "marvin.vanaalst@gmail.com", name = "Marvin van Aalst" }]
|
|
7
|
+
classifiers = [
|
|
8
|
+
"Operating System :: MacOS",
|
|
9
|
+
"Operating System :: Microsoft :: Windows",
|
|
10
|
+
"Operating System :: POSIX :: Linux",
|
|
11
|
+
"Programming Language :: C",
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Topic :: Scientific/Engineering",
|
|
14
|
+
]
|
|
15
|
+
# llvmlite JIT-compiles models for these solvers. It only has wheels for linux
|
|
16
|
+
# x86_64/aarch64, macOS arm64 and windows amd64/arm64; MxlPy's `native` extra
|
|
17
|
+
# installs the WASM backend everywhere else, so this is never pulled in there.
|
|
18
|
+
dependencies = ["llvmlite>=0.45"]
|
|
19
|
+
description = "Compiled ODE solvers for MxlPy's Native integrator"
|
|
20
|
+
license = "MIT"
|
|
21
|
+
license-files = ["LICENSE", "c/vendor/*LICENCE*.txt"]
|
|
22
|
+
name = "mxlpy-native"
|
|
23
|
+
readme = "README.md"
|
|
24
|
+
requires-python = ">=3.12"
|
|
25
|
+
version = "0.3.0"
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/Computational-Biology-Aachen/mxlpy-native"
|
|
29
|
+
Issues = "https://github.com/Computational-Biology-Aachen/mxlpy-native/issues"
|
|
30
|
+
|
|
31
|
+
[dependency-groups]
|
|
32
|
+
dev = ["pytest>=8", "ruff>=0.9", "setuptools>=77"]
|
|
33
|
+
|
|
34
|
+
[tool.cibuildwheel]
|
|
35
|
+
# One abi3 wheel per platform covers Python 3.12+.
|
|
36
|
+
build = "cp312-*"
|
|
37
|
+
# Only platforms llvmlite publishes wheels for.
|
|
38
|
+
skip = ["*-musllinux_*", "*-win32", "*-manylinux_i686", "*-macosx_x86_64"]
|
|
39
|
+
test-command = "python -m mxlpy_native"
|
|
40
|
+
|
|
41
|
+
[tool.cibuildwheel.macos]
|
|
42
|
+
environment = { MACOSX_DEPLOYMENT_TARGET = "12.0" }
|
|
43
|
+
|
|
44
|
+
[tool.ruff]
|
|
45
|
+
line-length = 100
|
|
46
|
+
|
|
47
|
+
[tool.ruff.lint]
|
|
48
|
+
select = ["ALL"]
|
|
49
|
+
ignore = ["COM812", "D203", "D213", "S101", "T201", "PLR2004"]
|
|
50
|
+
|
|
51
|
+
[tool.ruff.lint.per-file-ignores]
|
|
52
|
+
"tests/*" = ["D", "INP001", "ANN"]
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.packages.find]
|
|
55
|
+
where = ["src"]
|
|
56
|
+
|
|
57
|
+
[tool.uv]
|
|
58
|
+
exclude-newer = "3 days"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Copyright 2025 Marvin van Aalst, Anna Matuszyńska
|
|
2
|
+
|
|
3
|
+
"""Build the solver as one stable-ABI extension, so a wheel covers all Python versions.
|
|
4
|
+
|
|
5
|
+
Wheels are published for the common platforms, so this only runs when pip has to build
|
|
6
|
+
from the sdist. That needs a C11 compiler and nothing else: no Fortran, BLAS or Python
|
|
7
|
+
headers. When it fails, the error says what to install instead of showing a raw
|
|
8
|
+
compiler traceback.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import platform
|
|
14
|
+
import shutil
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
from setuptools import Extension, setup
|
|
18
|
+
from setuptools.command.build_ext import build_ext
|
|
19
|
+
from setuptools.errors import CCompilerError, ExecError, PlatformError
|
|
20
|
+
|
|
21
|
+
_NO_COMPILER_HINTS = {
|
|
22
|
+
"linux": (
|
|
23
|
+
" Debian/Ubuntu: sudo apt install build-essential\n"
|
|
24
|
+
" Fedora/RHEL: sudo dnf install gcc\n"
|
|
25
|
+
" Alpine: apk add build-base\n"
|
|
26
|
+
" conda/pixi: conda install -c conda-forge c-compiler"
|
|
27
|
+
),
|
|
28
|
+
"darwin": " macOS: xcode-select --install",
|
|
29
|
+
"win32": (
|
|
30
|
+
' Windows: install "Build Tools for Visual Studio" (2019 16.8 or newer)\n'
|
|
31
|
+
' with the "Desktop development with C++" workload'
|
|
32
|
+
),
|
|
33
|
+
}
|
|
34
|
+
_ALTERNATIVE = (
|
|
35
|
+
"If you can't install a compiler, use the WASM backend instead, which needs none:\n"
|
|
36
|
+
' pip install "mxlpy[native-wasm]"'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _platform() -> str:
|
|
41
|
+
return f"{sys.platform} {platform.machine()}, Python {platform.python_version()}"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _no_compiler(detail: str) -> str:
|
|
45
|
+
hints = _NO_COMPILER_HINTS.get(sys.platform, " Install a C11 compiler such as gcc or clang.")
|
|
46
|
+
return (
|
|
47
|
+
f"mxlpy-native: no C compiler found ({detail}).\n\n"
|
|
48
|
+
f"There is no prebuilt mxlpy-native wheel for {_platform()}, so pip is building it\n"
|
|
49
|
+
"from source, which needs a C11 compiler (no Fortran, BLAS or Python headers):\n"
|
|
50
|
+
f"{hints}\n\n{_ALTERNATIVE}"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _compile_failed(detail: str) -> str:
|
|
55
|
+
return (
|
|
56
|
+
f"mxlpy-native: compiling the solvers failed ({detail}).\n\n"
|
|
57
|
+
f"There is no prebuilt mxlpy-native wheel for {_platform()}, so pip is building it\n"
|
|
58
|
+
"from source. The solvers need C11 support: GCC 8+, Clang 7+ or MSVC 2019 16.8+.\n"
|
|
59
|
+
"Check that your compiler is recent enough and that CC points to it. If it is, please\n"
|
|
60
|
+
"report this at https://github.com/Computational-Biology-Aachen/mxlpy-native/issues\n"
|
|
61
|
+
f"with the compiler output above.\n\n{_ALTERNATIVE}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class _BuildError(PlatformError):
|
|
66
|
+
"""A build error that already explains itself."""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class BuildExt(build_ext):
|
|
70
|
+
"""build_ext that explains missing or unsuitable compilers."""
|
|
71
|
+
|
|
72
|
+
def run(self) -> None:
|
|
73
|
+
"""Explain a missing MSVC."""
|
|
74
|
+
try:
|
|
75
|
+
super().run()
|
|
76
|
+
except _BuildError:
|
|
77
|
+
raise
|
|
78
|
+
except PlatformError as e: # MSVC isn't installed
|
|
79
|
+
raise _BuildError(_no_compiler(str(e))) from e
|
|
80
|
+
|
|
81
|
+
def build_extension(self, ext: Extension) -> None:
|
|
82
|
+
"""Explain a missing or failing compiler."""
|
|
83
|
+
compiler = getattr(self.compiler, "compiler_so", None)
|
|
84
|
+
if compiler and shutil.which(compiler[0]) is None:
|
|
85
|
+
raise _BuildError(_no_compiler(f"{compiler[0]!r} is not on PATH"))
|
|
86
|
+
try:
|
|
87
|
+
super().build_extension(ext)
|
|
88
|
+
except (ExecError, FileNotFoundError) as e: # the compiler couldn't be started
|
|
89
|
+
raise _BuildError(_no_compiler(str(e))) from e
|
|
90
|
+
except CCompilerError as e: # it ran, but compiling or linking failed
|
|
91
|
+
msg = _compile_failed("the compiler returned an error, see its output above")
|
|
92
|
+
raise _BuildError(msg) from e
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
vendored = ["radau5.c", "dc_decsol.c", "decsol.c", "libf2c_stubs.c", "lsoda.c", "mxs_lapack.c"]
|
|
96
|
+
setup(
|
|
97
|
+
ext_modules=[
|
|
98
|
+
Extension(
|
|
99
|
+
"mxlpy_native._solvers",
|
|
100
|
+
sources=["c/mxn_solvers.c", *(f"c/vendor/{s}" for s in vendored)],
|
|
101
|
+
include_dirs=["c/vendor"],
|
|
102
|
+
py_limited_api=True,
|
|
103
|
+
define_macros=[("Py_LIMITED_API", "0x030C0000")],
|
|
104
|
+
# lsoda.c needs C11 (restrict, compound literals), which MSVC only enables on request.
|
|
105
|
+
extra_compile_args=["/std:c11"]
|
|
106
|
+
if sys.platform == "win32"
|
|
107
|
+
else ["-std=gnu17", "-w", "-O2"],
|
|
108
|
+
libraries=[] if sys.platform == "win32" else ["m"],
|
|
109
|
+
)
|
|
110
|
+
],
|
|
111
|
+
cmdclass={"build_ext": BuildExt},
|
|
112
|
+
options={"bdist_wheel": {"py_limited_api": "cp312"}},
|
|
113
|
+
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Copyright 2025 Marvin van Aalst, Anna Matuszyńska
|
|
2
|
+
|
|
3
|
+
"""Compiled solvers for MxlPy's Native integrator.
|
|
4
|
+
|
|
5
|
+
`load()` returns the solver library for ctypes. Everything that can go wrong with an
|
|
6
|
+
installation raises `NativeInstallError` with a message saying how to fix it.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import ctypes
|
|
12
|
+
from functools import lru_cache
|
|
13
|
+
from importlib.machinery import EXTENSION_SUFFIXES
|
|
14
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
__all__ = ["ABI_VERSION", "NativeInstallError", "__version__", "library_path", "load"]
|
|
18
|
+
|
|
19
|
+
# Must match MXN_ABI_VERSION in c/mxn_solvers.c. MxlPy checks it before calling into
|
|
20
|
+
# the library, so a mismatched pair fails with an upgrade hint instead of crashing.
|
|
21
|
+
ABI_VERSION = 1
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
__version__ = version("mxlpy-native")
|
|
25
|
+
except PackageNotFoundError: # running from a source tree without installing
|
|
26
|
+
__version__ = "0+unknown"
|
|
27
|
+
|
|
28
|
+
_REINSTALL = 'pip install --force-reinstall --no-cache-dir "mxlpy-native"'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class NativeInstallError(ImportError):
|
|
32
|
+
"""mxlpy-native is installed, but its compiled solvers can't be used."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def library_path() -> Path:
|
|
36
|
+
"""Return the path of the compiled solvers, for loading with ctypes."""
|
|
37
|
+
here = Path(__file__).parent
|
|
38
|
+
for suffix in EXTENSION_SUFFIXES:
|
|
39
|
+
if (path := here / f"_solvers{suffix}").exists():
|
|
40
|
+
return path
|
|
41
|
+
found = sorted(p.name for p in here.glob("_solvers*"))
|
|
42
|
+
detail = f"found only {found}, none built for this Python" if found else "none found"
|
|
43
|
+
msg = (
|
|
44
|
+
f"mxlpy-native {__version__} is installed without its compiled solvers in {here} "
|
|
45
|
+
f"({detail}).\nThis usually means a source checkout was installed without building "
|
|
46
|
+
f"it, or the wheel belongs to a different platform. Reinstall it with:\n {_REINSTALL}"
|
|
47
|
+
)
|
|
48
|
+
raise NativeInstallError(msg)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@lru_cache(maxsize=1)
|
|
52
|
+
def load() -> ctypes.CDLL:
|
|
53
|
+
"""Load the compiled solvers and check that they match this package."""
|
|
54
|
+
path = library_path()
|
|
55
|
+
try:
|
|
56
|
+
lib = ctypes.CDLL(str(path))
|
|
57
|
+
except OSError as e:
|
|
58
|
+
msg = (
|
|
59
|
+
f"mxlpy-native {__version__} can't load its compiled solvers {path}: {e}\n"
|
|
60
|
+
"The file may be corrupt or built for another platform or architecture. "
|
|
61
|
+
f"Reinstall it with:\n {_REINSTALL}"
|
|
62
|
+
)
|
|
63
|
+
raise NativeInstallError(msg) from e
|
|
64
|
+
try:
|
|
65
|
+
lib.mxn_abi_version.restype = ctypes.c_int
|
|
66
|
+
abi = lib.mxn_abi_version()
|
|
67
|
+
except AttributeError:
|
|
68
|
+
abi = 0 # built before the ABI was versioned
|
|
69
|
+
if abi != ABI_VERSION:
|
|
70
|
+
msg = (
|
|
71
|
+
f"mxlpy-native {__version__}'s compiled solvers {path} have ABI {abi}, but its "
|
|
72
|
+
f"Python code expects {ABI_VERSION}. The build is stale; rebuild it with:\n"
|
|
73
|
+
f" {_REINSTALL}"
|
|
74
|
+
)
|
|
75
|
+
raise NativeInstallError(msg)
|
|
76
|
+
return lib
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Copyright 2025 Marvin van Aalst, Anna Matuszyńska
|
|
2
|
+
|
|
3
|
+
"""Diagnose an installation: python -m mxlpy_native."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import platform
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
import mxlpy_native
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main() -> int:
|
|
14
|
+
"""Print what's installed and whether the compiled solvers load."""
|
|
15
|
+
print(f"mxlpy-native {mxlpy_native.__version__} (ABI {mxlpy_native.ABI_VERSION})")
|
|
16
|
+
print(f"platform {sys.platform} {platform.machine()}, Python {platform.python_version()}")
|
|
17
|
+
ok = True
|
|
18
|
+
try:
|
|
19
|
+
print(f"solvers {mxlpy_native.load()._name}: ok") # noqa: SLF001
|
|
20
|
+
except mxlpy_native.NativeInstallError as e:
|
|
21
|
+
print(f"solvers FAILED\n{e}")
|
|
22
|
+
ok = False
|
|
23
|
+
try:
|
|
24
|
+
import llvmlite.binding as llvm # noqa: PLC0415
|
|
25
|
+
|
|
26
|
+
llvm.initialize_native_target()
|
|
27
|
+
print(f"llvmlite {llvm.llvm_version_info} targeting {llvm.get_default_triple()}: ok")
|
|
28
|
+
except Exception as e: # noqa: BLE001
|
|
29
|
+
print(f"llvmlite FAILED: {e}")
|
|
30
|
+
ok = False
|
|
31
|
+
return 0 if ok else 1
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if __name__ == "__main__":
|
|
35
|
+
sys.exit(main())
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mxlpy-native
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Compiled ODE solvers for MxlPy's Native integrator
|
|
5
|
+
Author-email: Marvin van Aalst <marvin.vanaalst@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Computational-Biology-Aachen/mxlpy-native
|
|
8
|
+
Project-URL: Issues, https://github.com/Computational-Biology-Aachen/mxlpy-native/issues
|
|
9
|
+
Classifier: Operating System :: MacOS
|
|
10
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Programming Language :: C
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
License-File: c/vendor/HAIRER-LICENCE.txt
|
|
19
|
+
License-File: c/vendor/SCIPY-LICENCE.txt
|
|
20
|
+
Requires-Dist: llvmlite>=0.45
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# mxlpy-native
|
|
24
|
+
|
|
25
|
+
The compiled Radau and LSODA solvers behind [MxlPy](https://github.com/Computational-Biology-Aachen/MxlPy)'s `Native` integrator, plus `llvmlite`, which compiles models to machine code for them. Install it through MxlPy rather than on its own:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install "mxlpy[native]"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Platforms
|
|
32
|
+
|
|
33
|
+
Wheels are published for Linux (x86_64, aarch64; glibc 2.28+), macOS (arm64, 12+) and Windows (amd64, arm64), the platforms `llvmlite` has wheels for. On anything else, `mxlpy[native]` installs the WASM backend (`wasmtime`) instead, which runs the same solvers without a compiler, a little slower.
|
|
34
|
+
|
|
35
|
+
If pip has to build from the sdist anyway, it needs a C11 compiler (GCC 8+, Clang 7+ or MSVC 2019 16.8+), and nothing else: no Fortran, BLAS/LAPACK or Python headers. The build says what to install if the compiler is missing or fails.
|
|
36
|
+
|
|
37
|
+
## Troubleshooting
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
python -m mxlpy_native
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
prints the installed version, the solver ABI, and whether the compiled solvers and `llvmlite` load, with a fix for each failure.
|
|
44
|
+
|
|
45
|
+
## Development
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv sync # builds the extension in place
|
|
49
|
+
uv run pytest
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`c/vendor/` is copied from [mxl-solvers](https://github.com/Computational-Biology-Aachen/mxl-solvers) by its `scripts/vendor.sh`. `vendor/VENDORED` records the commit and file hashes, which `tests/test_vendor.py` checks. Fix solvers there, not here. `c/mxn_solvers.c` is the glue MxlPy calls, compiled unchanged into the wheel and into MxlPy's `solvers.wasm` (`scripts/build_native_wasm.sh` in MxlPy).
|
|
53
|
+
|
|
54
|
+
When an exported function's signature or meaning changes, bump `MXN_ABI_VERSION` in `c/mxn_solvers.c`, `ABI_VERSION` in `src/mxlpy_native/__init__.py` and the ABI MxlPy expects, then release both. MxlPy refuses a mismatched pair with an upgrade hint instead of crashing.
|
|
55
|
+
|
|
56
|
+
## Releasing
|
|
57
|
+
|
|
58
|
+
Bump `version` in `pyproject.toml` and publish a GitHub release `v<version>`. The release workflow builds the wheels with cibuildwheel plus an sdist and uploads them to PyPI via trusted publishing.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
LICENSE
|
|
1
2
|
MANIFEST.in
|
|
2
3
|
README.md
|
|
3
4
|
pyproject.toml
|
|
@@ -16,7 +17,12 @@ c/vendor/lsoda.h
|
|
|
16
17
|
c/vendor/mxs_lapack.c
|
|
17
18
|
c/vendor/radau5.c
|
|
18
19
|
src/mxlpy_native/__init__.py
|
|
20
|
+
src/mxlpy_native/__main__.py
|
|
19
21
|
src/mxlpy_native.egg-info/PKG-INFO
|
|
20
22
|
src/mxlpy_native.egg-info/SOURCES.txt
|
|
21
23
|
src/mxlpy_native.egg-info/dependency_links.txt
|
|
22
|
-
src/mxlpy_native.egg-info/
|
|
24
|
+
src/mxlpy_native.egg-info/requires.txt
|
|
25
|
+
src/mxlpy_native.egg-info/top_level.txt
|
|
26
|
+
tests/test_build_errors.py
|
|
27
|
+
tests/test_solvers.py
|
|
28
|
+
tests/test_vendor.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
llvmlite>=0.45
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Copyright 2025 Marvin van Aalst, Anna Matuszyńska
|
|
2
|
+
|
|
3
|
+
"""Building from source without a working compiler explains what to install."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
ROOT = Path(__file__).parents[1]
|
|
16
|
+
|
|
17
|
+
pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="MSVC ignores CC")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _build(tmp_path: Path, cc: str) -> str:
|
|
21
|
+
for name in ("setup.py", "pyproject.toml", "c", "src"):
|
|
22
|
+
src = ROOT / name
|
|
23
|
+
if src.is_dir():
|
|
24
|
+
shutil.copytree(src, tmp_path / name, ignore=shutil.ignore_patterns("*.so", "*.pyd"))
|
|
25
|
+
else:
|
|
26
|
+
shutil.copy(src, tmp_path / name)
|
|
27
|
+
proc = subprocess.run(
|
|
28
|
+
[sys.executable, "setup.py", "-q", "build_ext", "--inplace"],
|
|
29
|
+
cwd=tmp_path,
|
|
30
|
+
env={**os.environ, "CC": cc},
|
|
31
|
+
capture_output=True,
|
|
32
|
+
text=True,
|
|
33
|
+
check=False,
|
|
34
|
+
)
|
|
35
|
+
assert proc.returncode != 0
|
|
36
|
+
return proc.stdout + proc.stderr
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_missing_compiler(tmp_path: Path) -> None:
|
|
40
|
+
out = _build(tmp_path, "/nonexistent/cc")
|
|
41
|
+
assert "no C compiler found" in out
|
|
42
|
+
assert "mxlpy[native-wasm]" in out
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_failing_compiler(tmp_path: Path) -> None:
|
|
46
|
+
out = _build(tmp_path, "false")
|
|
47
|
+
assert "compiling the solvers failed" in out
|
|
48
|
+
assert "C11" in out
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Copyright 2025 Marvin van Aalst, Anna Matuszyńska
|
|
2
|
+
|
|
3
|
+
"""The compiled solvers load, match the Python package and solve dy/dt = -k y."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import ctypes as ct
|
|
8
|
+
import math
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
import mxlpy_native
|
|
13
|
+
|
|
14
|
+
_DP = ct.POINTER(ct.c_double)
|
|
15
|
+
_MODEL_FN = ct.CFUNCTYPE(None, ct.c_int, ct.c_double, _DP, _DP, _DP)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@_MODEL_FN
|
|
19
|
+
def _decay(n: int, t: float, y: _DP, dydt: _DP, pars: _DP) -> None: # noqa: ARG001
|
|
20
|
+
for i in range(n):
|
|
21
|
+
dydt[i] = -pars[0] * y[i]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _arr(*values: float) -> ct.Array:
|
|
25
|
+
return (ct.c_double * len(values))(*values)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_abi_matches() -> None:
|
|
29
|
+
assert mxlpy_native.load().mxn_abi_version() == mxlpy_native.ABI_VERSION
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.mark.parametrize("method", ["radau", "lsoda"])
|
|
33
|
+
def test_solve_exponential_decay(method: str) -> None:
|
|
34
|
+
lib = mxlpy_native.load()
|
|
35
|
+
t_out = [0.0, 0.5, 1.0, 2.0]
|
|
36
|
+
y, pars, t, out = _arr(1.0, 2.0), _arr(0.7), _arr(*t_out), _arr(*[0.0] * 8)
|
|
37
|
+
lib.mxn_set_model.argtypes = [ct.c_void_p]
|
|
38
|
+
lib.mxn_set_model(ct.cast(_decay, ct.c_void_p))
|
|
39
|
+
head = (2, y, pars, ct.c_double(1e-10), ct.c_double(1e-10), 100_000)
|
|
40
|
+
if method == "radau":
|
|
41
|
+
rc = lib.mxn_radau(*head, 0, len(t_out), t, out)
|
|
42
|
+
assert rc == 1
|
|
43
|
+
else:
|
|
44
|
+
rc = lib.mxn_lsoda(*head, len(t_out), t, out)
|
|
45
|
+
assert rc == 2
|
|
46
|
+
for i, ti in enumerate(t_out):
|
|
47
|
+
assert out[2 * i] == pytest.approx(math.exp(-0.7 * ti), rel=1e-7)
|
|
48
|
+
assert out[2 * i + 1] == pytest.approx(2 * math.exp(-0.7 * ti), rel=1e-7)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_missing_library_explains_reinstall(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
52
|
+
monkeypatch.setattr(mxlpy_native, "EXTENSION_SUFFIXES", [".nope"])
|
|
53
|
+
with pytest.raises(mxlpy_native.NativeInstallError, match="force-reinstall"):
|
|
54
|
+
mxlpy_native.library_path()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_diagnostics_pass() -> None:
|
|
58
|
+
from mxlpy_native.__main__ import main # noqa: PLC0415
|
|
59
|
+
|
|
60
|
+
assert main() == 0
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Copyright 2025 Marvin van Aalst, Anna Matuszyńska
|
|
2
|
+
|
|
3
|
+
"""The solvers in c/vendor must be exactly what mxl-solvers' vendor.sh copied.
|
|
4
|
+
|
|
5
|
+
Fix solvers in https://github.com/Computational-Biology-Aachen/mxl-solvers and re-run
|
|
6
|
+
its scripts/vendor.sh, rather than editing the copies here.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
VENDOR = Path(__file__).parents[1] / "c" / "vendor"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _manifest() -> dict[str, str]:
|
|
18
|
+
lines = (VENDOR / "VENDORED").read_text().splitlines()
|
|
19
|
+
return {
|
|
20
|
+
name: digest
|
|
21
|
+
for digest, name in (line.split() for line in lines if line and not line.startswith("#"))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_vendored_solvers_are_unmodified() -> None:
|
|
26
|
+
manifest = _manifest()
|
|
27
|
+
actual = {
|
|
28
|
+
p.name: hashlib.sha256(p.read_bytes()).hexdigest()
|
|
29
|
+
for p in VENDOR.iterdir()
|
|
30
|
+
if p.name != "VENDORED"
|
|
31
|
+
}
|
|
32
|
+
assert actual == manifest
|
mxlpy_native-0.2.0/PKG-INFO
DELETED
mxlpy_native-0.2.0/README.md
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
# mxlpy-native
|
|
2
|
-
|
|
3
|
-
The compiled Radau and LSODA solvers behind MxlPy's `Native` integrator. Install it with `pip install "mxlpy[native]"`, not on its own.
|
|
4
|
-
|
|
5
|
-
`c/vendor/` is copied from [mxl-solvers](https://github.com/Computational-Biology-Aachen/mxl-solvers) by its `scripts/vendor.sh`. `vendor/VENDORED` records the commit and file hashes, which `tests/integrators/test_native_vendor.py` checks. Fix solvers there, not here. `c/mxn_solvers.c` is MxlPy's own glue, compiled unchanged into both the wheel and `solvers.wasm`.
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
build-backend = "setuptools.build_meta"
|
|
3
|
-
requires = ["setuptools>=77", "wheel"]
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
description = "Compiled ODE solvers for MxlPy's Native integrator"
|
|
7
|
-
license = "MIT"
|
|
8
|
-
name = "mxlpy-native"
|
|
9
|
-
requires-python = ">=3.12"
|
|
10
|
-
version = "0.2.0"
|
|
11
|
-
|
|
12
|
-
[tool.cibuildwheel]
|
|
13
|
-
build = "cp312-*"
|
|
14
|
-
skip = ["*-musllinux_*", "*-win32", "*-manylinux_i686"]
|
|
15
|
-
test-command = "python -c \"import mxlpy_native; assert mxlpy_native.library_path().exists()\""
|
|
16
|
-
|
|
17
|
-
[tool.setuptools.packages.find]
|
|
18
|
-
where = ["src"]
|
mxlpy_native-0.2.0/setup.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Copyright 2025 Marvin van Aalst, Anna Matuszyńska # noqa: INP001
|
|
2
|
-
|
|
3
|
-
"""Build the solver as one stable-ABI extension, so a wheel covers all Python versions."""
|
|
4
|
-
|
|
5
|
-
import sys
|
|
6
|
-
|
|
7
|
-
from setuptools import Extension, setup
|
|
8
|
-
|
|
9
|
-
vendored = ["radau5.c", "dc_decsol.c", "decsol.c", "libf2c_stubs.c", "lsoda.c", "mxs_lapack.c"]
|
|
10
|
-
setup(
|
|
11
|
-
ext_modules=[
|
|
12
|
-
Extension(
|
|
13
|
-
"mxlpy_native._solvers",
|
|
14
|
-
sources=["c/mxn_solvers.c", *(f"c/vendor/{s}" for s in vendored)],
|
|
15
|
-
include_dirs=["c/vendor"],
|
|
16
|
-
py_limited_api=True,
|
|
17
|
-
define_macros=[("Py_LIMITED_API", "0x030C0000")],
|
|
18
|
-
# lsoda.c needs C11 (restrict, compound literals), which MSVC only enables on request.
|
|
19
|
-
extra_compile_args=["/std:c11"]
|
|
20
|
-
if sys.platform == "win32"
|
|
21
|
-
else ["-std=gnu17", "-w", "-O2"],
|
|
22
|
-
libraries=[] if sys.platform == "win32" else ["m"],
|
|
23
|
-
)
|
|
24
|
-
],
|
|
25
|
-
options={"bdist_wheel": {"py_limited_api": "cp312"}},
|
|
26
|
-
)
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# Copyright 2025 Marvin van Aalst, Anna Matuszyńska
|
|
2
|
-
|
|
3
|
-
"""Compiled solvers for MxlPy's Native integrator."""
|
|
4
|
-
|
|
5
|
-
from importlib.machinery import EXTENSION_SUFFIXES
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
__all__ = ["library_path"]
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def library_path() -> Path:
|
|
12
|
-
"""Return the path of the compiled solvers, for loading with ctypes."""
|
|
13
|
-
here = Path(__file__).parent
|
|
14
|
-
for suffix in EXTENSION_SUFFIXES:
|
|
15
|
-
if (path := here / f"_solvers{suffix}").exists():
|
|
16
|
-
return path
|
|
17
|
-
msg = f"compiled solvers not found in {here}"
|
|
18
|
-
raise FileNotFoundError(msg)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|