momwire 0.2.3__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.
Files changed (32) hide show
  1. momwire-0.2.3/LICENSE +21 -0
  2. momwire-0.2.3/MANIFEST.in +4 -0
  3. momwire-0.2.3/PKG-INFO +86 -0
  4. momwire-0.2.3/README.md +61 -0
  5. momwire-0.2.3/pyproject.toml +133 -0
  6. momwire-0.2.3/setup.cfg +4 -0
  7. momwire-0.2.3/setup.py +150 -0
  8. momwire-0.2.3/src/momwire/__init__.py +17 -0
  9. momwire-0.2.3/src/momwire/_aca.py +295 -0
  10. momwire-0.2.3/src/momwire/_accel.py +85 -0
  11. momwire-0.2.3/src/momwire/_accelerators.cpp +2361 -0
  12. momwire-0.2.3/src/momwire/_bspline_kernels.py +292 -0
  13. momwire-0.2.3/src/momwire/_bspline_static_moments.py +807 -0
  14. momwire-0.2.3/src/momwire/_bspline_static_moments_inline.h +41 -0
  15. momwire-0.2.3/src/momwire/_quadrature.py +30 -0
  16. momwire-0.2.3/src/momwire/_triangular_kernels.py +268 -0
  17. momwire-0.2.3/src/momwire/array_block.py +750 -0
  18. momwire-0.2.3/src/momwire/bspline.py +1732 -0
  19. momwire-0.2.3/src/momwire/hmatrix.py +1033 -0
  20. momwire-0.2.3/src/momwire/sinusoidal.py +1195 -0
  21. momwire-0.2.3/src/momwire/triangular.py +1074 -0
  22. momwire-0.2.3/src/momwire.egg-info/PKG-INFO +86 -0
  23. momwire-0.2.3/src/momwire.egg-info/SOURCES.txt +30 -0
  24. momwire-0.2.3/src/momwire.egg-info/dependency_links.txt +1 -0
  25. momwire-0.2.3/src/momwire.egg-info/requires.txt +10 -0
  26. momwire-0.2.3/src/momwire.egg-info/top_level.txt +1 -0
  27. momwire-0.2.3/tests/test_accel_fallback.py +83 -0
  28. momwire-0.2.3/tests/test_array_block.py +589 -0
  29. momwire-0.2.3/tests/test_hmatrix.py +470 -0
  30. momwire-0.2.3/tests/test_momwire.py +2682 -0
  31. momwire-0.2.3/tests/test_momwire_backend.py +167 -0
  32. momwire-0.2.3/tests/test_pynec_backend.py +315 -0
momwire-0.2.3/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Steven Burns
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,4 @@
1
+ # setuptools auto-includes the C++ ext source (_accelerators.cpp) in the sdist,
2
+ # but not the header it pulls in via #include "_bspline_static_moments_inline.h".
3
+ # Without this, a source build from the sdist fails to compile the extension.
4
+ include src/momwire/_bspline_static_moments_inline.h
momwire-0.2.3/PKG-INFO ADDED
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: momwire
3
+ Version: 0.2.3
4
+ Summary: A pure-Python method-of-moments antenna simulator with optional C++ accelerators
5
+ Author-email: "Steven M. Burns" <smburns47@gmail.com>
6
+ Project-URL: Homepage, https://github.com/stevenmburns/momwire
7
+ Project-URL: Issues, https://github.com/stevenmburns/momwire/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: POSIX :: Linux
11
+ Classifier: Operating System :: Microsoft :: Windows
12
+ Classifier: Operating System :: MacOS
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: numpy>=1.21
17
+ Requires-Dist: scipy>=1.7
18
+ Provides-Extra: test
19
+ Requires-Dist: pytest; extra == "test"
20
+ Requires-Dist: matplotlib; extra == "test"
21
+ Requires-Dist: scikit-rf; extra == "test"
22
+ Provides-Extra: validation
23
+ Requires-Dist: pymininec>=1.2; extra == "validation"
24
+ Dynamic: license-file
25
+
26
+ # momwire
27
+
28
+ A pure-Python method-of-moments antenna simulator with optional C++ accelerators (pybind11).
29
+
30
+ Extracted from [antenna_designer](https://github.com/stevenmburns/antenna_designer).
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ python3 -m venv .venv
36
+ source .venv/bin/activate
37
+ pip install --upgrade pip
38
+ pip install -e .
39
+ ```
40
+
41
+ **macOS:** the C++ accelerator uses Homebrew's OpenMP runtime, so `brew install
42
+ libomp` is required — both to compile from source and to run the prebuilt
43
+ Apple-Silicon wheel. The wheel deliberately does **not** bundle `libomp` (it links
44
+ Homebrew's by absolute path) so that it shares a single OpenMP runtime with
45
+ pynec-accel; two private copies in one process abort with `OMP: Error #15` (or
46
+ deadlock). Without `libomp` installed, the accelerator can't load and momwire
47
+ warns and falls back to the slower pure-Python path. On Linux the system
48
+ `libgomp` covers this, so no extra step is needed.
49
+
50
+ ## Test
51
+
52
+ ```bash
53
+ pip install -e ".[test]" # core + test deps (pytest, matplotlib, scikit-rf)
54
+ pytest tests/
55
+ ```
56
+
57
+ The cross-validation against NEC2 (`tests/test_pynec_backend.py`) additionally
58
+ needs PyNEC — a test-only dependency installed separately from a wheel (see
59
+ below). Those tests skip cleanly when it isn't present; everything else runs
60
+ without it.
61
+
62
+ ## Optional: PyNEC backend (test-only)
63
+
64
+ momwire can be cross-validated against NEC2 via [PyNEC](https://github.com/tmolteno/python-necpp) (the `tests/test_pynec_backend.py` suite); NEC2 also delivers ~5–10× faster single-frequency solves. PyNEC is a **test-flow-only** dependency — momwire's own solver never imports it.
65
+
66
+ ### Install the PyNEC wheel
67
+
68
+ Install PyNEC from the [python-necpp fork](https://github.com/stevenmburns/python-necpp)'s release. The wheels are self-contained — OpenBLAS is vendored (via scipy-openblas32), so no system BLAS, SWIG, or build toolchain is needed — and cover Linux + Windows on CPython 3.10–3.14:
69
+
70
+ ```bash
71
+ pip install PyNEC --no-index \
72
+ --find-links https://github.com/stevenmburns/python-necpp/releases/expanded_assets/v1.7.4-accel.1
73
+ ```
74
+
75
+ `--no-index` ensures pip takes the fork's wheel rather than upstream PyNEC on PyPI (same version, but its builds are broken on current Python and it lacks the OpenBLAS/OpenMP work). After install, `from PyNEC import nec_context` works and the cross-validation tests run; without it they're skipped (momwire itself needs no PyNEC).
76
+
77
+ ### Runtime thread pinning
78
+
79
+ The wheel links OpenBLAS and parallelises the NEC2 matrix fill with OpenMP. Pick thread counts up front:
80
+
81
+ ```bash
82
+ export OMP_NUM_THREADS=$(nproc --all) # PyNEC matrix fill
83
+ export OPENBLAS_NUM_THREADS=1 # muzzle numpy/scipy's idle pool
84
+ ```
85
+
86
+ Pinning `OPENBLAS_NUM_THREADS=1` stops numpy/scipy from spinning up their own OpenBLAS thread pool that contends with PyNEC's threads on the same cores. On a 100-director Yagi (2142 segs) this is worth ~8% wall time at NP=4.
@@ -0,0 +1,61 @@
1
+ # momwire
2
+
3
+ A pure-Python method-of-moments antenna simulator with optional C++ accelerators (pybind11).
4
+
5
+ Extracted from [antenna_designer](https://github.com/stevenmburns/antenna_designer).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ python3 -m venv .venv
11
+ source .venv/bin/activate
12
+ pip install --upgrade pip
13
+ pip install -e .
14
+ ```
15
+
16
+ **macOS:** the C++ accelerator uses Homebrew's OpenMP runtime, so `brew install
17
+ libomp` is required — both to compile from source and to run the prebuilt
18
+ Apple-Silicon wheel. The wheel deliberately does **not** bundle `libomp` (it links
19
+ Homebrew's by absolute path) so that it shares a single OpenMP runtime with
20
+ pynec-accel; two private copies in one process abort with `OMP: Error #15` (or
21
+ deadlock). Without `libomp` installed, the accelerator can't load and momwire
22
+ warns and falls back to the slower pure-Python path. On Linux the system
23
+ `libgomp` covers this, so no extra step is needed.
24
+
25
+ ## Test
26
+
27
+ ```bash
28
+ pip install -e ".[test]" # core + test deps (pytest, matplotlib, scikit-rf)
29
+ pytest tests/
30
+ ```
31
+
32
+ The cross-validation against NEC2 (`tests/test_pynec_backend.py`) additionally
33
+ needs PyNEC — a test-only dependency installed separately from a wheel (see
34
+ below). Those tests skip cleanly when it isn't present; everything else runs
35
+ without it.
36
+
37
+ ## Optional: PyNEC backend (test-only)
38
+
39
+ momwire can be cross-validated against NEC2 via [PyNEC](https://github.com/tmolteno/python-necpp) (the `tests/test_pynec_backend.py` suite); NEC2 also delivers ~5–10× faster single-frequency solves. PyNEC is a **test-flow-only** dependency — momwire's own solver never imports it.
40
+
41
+ ### Install the PyNEC wheel
42
+
43
+ Install PyNEC from the [python-necpp fork](https://github.com/stevenmburns/python-necpp)'s release. The wheels are self-contained — OpenBLAS is vendored (via scipy-openblas32), so no system BLAS, SWIG, or build toolchain is needed — and cover Linux + Windows on CPython 3.10–3.14:
44
+
45
+ ```bash
46
+ pip install PyNEC --no-index \
47
+ --find-links https://github.com/stevenmburns/python-necpp/releases/expanded_assets/v1.7.4-accel.1
48
+ ```
49
+
50
+ `--no-index` ensures pip takes the fork's wheel rather than upstream PyNEC on PyPI (same version, but its builds are broken on current Python and it lacks the OpenBLAS/OpenMP work). After install, `from PyNEC import nec_context` works and the cross-validation tests run; without it they're skipped (momwire itself needs no PyNEC).
51
+
52
+ ### Runtime thread pinning
53
+
54
+ The wheel links OpenBLAS and parallelises the NEC2 matrix fill with OpenMP. Pick thread counts up front:
55
+
56
+ ```bash
57
+ export OMP_NUM_THREADS=$(nproc --all) # PyNEC matrix fill
58
+ export OPENBLAS_NUM_THREADS=1 # muzzle numpy/scipy's idle pool
59
+ ```
60
+
61
+ Pinning `OPENBLAS_NUM_THREADS=1` stops numpy/scipy from spinning up their own OpenBLAS thread pool that contends with PyNEC's threads on the same cores. On a 100-director Yagi (2142 segs) this is worth ~8% wall time at NP=4.
@@ -0,0 +1,133 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel", "pybind11"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "momwire"
7
+ version = "0.2.3"
8
+ authors = [
9
+ { name="Steven M. Burns", email="smburns47@gmail.com" },
10
+ ]
11
+ description = "A pure-Python method-of-moments antenna simulator with optional C++ accelerators"
12
+ readme = "README.md"
13
+ requires-python = ">=3.8"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ # Accelerated wheels are built for these three; other platforms fall back to
18
+ # the pure-Python solver (the C++ extension is an optional build). Not "OS
19
+ # Independent" — that connotes a pure-Python package, which this isn't.
20
+ "Operating System :: POSIX :: Linux",
21
+ "Operating System :: Microsoft :: Windows",
22
+ "Operating System :: MacOS",
23
+ ]
24
+ # Core runtime deps: the solver and its helpers import these unconditionally
25
+ # (numpy/scipy throughout src/momwire). The C++ _accelerators extension is an
26
+ # optional speedup, not required for import.
27
+ dependencies = [
28
+ "numpy>=1.21",
29
+ "scipy>=1.7",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ # Test/dev flow: `pip install -e .[test]` reproduces the suite. numpy/scipy are
34
+ # core deps; these are the extra imports the tests use (matplotlib for the
35
+ # plot-marked tests, scikit-rf for the network tests) plus the runner. The
36
+ # PyNEC cross-check (tests/test_pynec_backend.py) additionally needs PyNEC,
37
+ # installed separately from the python-necpp fork's wheel — see README — and
38
+ # those tests skip cleanly without it, so it stays out of this list (it's a
39
+ # platform-specific --find-links wheel, not a PyPI package a name could pin).
40
+ test = [
41
+ "pytest",
42
+ "matplotlib",
43
+ "scikit-rf",
44
+ ]
45
+ # Optional third-party MoM solvers used by the comparator scripts in
46
+ # scripts/. (PyNEC is installed separately from the python-necpp fork's
47
+ # release wheel — see README — not from PyPI.)
48
+ validation = [
49
+ "pymininec>=1.2",
50
+ ]
51
+
52
+ [project.urls]
53
+ Homepage = "https://github.com/stevenmburns/momwire"
54
+ Issues = "https://github.com/stevenmburns/momwire/issues"
55
+
56
+ [tool.pytest.ini_options]
57
+ # Only collect the project's own tests.
58
+ testpaths = ["tests"]
59
+ markers = [
60
+ "slow: tests that take more than a couple seconds (deselect with -m 'not slow')",
61
+ "plot: tests that produce plots for visual inspection (deselect with -m 'not plot')",
62
+ ]
63
+ # Put the project root on sys.path so the `validation` harness package (which
64
+ # lives next to `tests/`, not under src/) is importable from the backend tests.
65
+ pythonpath = ["."]
66
+
67
+ [tool.cibuildwheel]
68
+ # Modern CPython only. requires-python is >=3.8, but 3.8/3.9 predate the
69
+ # AVX2-baseline target; 3.10+ matches the "modern Linux/Windows" goal.
70
+ build = "cp310-* cp311-* cp312-* cp313-* cp314-*"
71
+ # 64-bit glibc Linux + 64-bit Windows only. Skip musllinux (Alpine/musl has no
72
+ # libmvec, and the -lmvec link in setup.py would fail), 32-bit, and PyPy.
73
+ skip = "*-musllinux_* *_i686 *-win32 pp*"
74
+ test-requires = "pytest"
75
+ # First assert the compiled C++ accelerator actually loads in the wheel: a
76
+ # failed import would otherwise be swallowed by triangular.py's pure-Python
77
+ # fallback, so the tests below would pass on a silently-degraded wheel. The
78
+ # direct import + _HAVE_* flags make that a hard failure instead.
79
+ #
80
+ # Then run only the three core solver modules — they exercise the accelerator
81
+ # without the PyNEC cross-check backend (a separately-built wheel, test-only).
82
+ # -m skips slow/plot. cibuildwheel runs this from a temp dir against the
83
+ # installed wheel, so `import momwire` resolves to the wheel, not src/.
84
+ test-command = 'python -c "import momwire._accelerators; from momwire import triangular as t; assert t._HAVE_ASSEMBLE_Z and t._HAVE_ASSEMBLE_Z_GENERAL" && pytest {project}/tests/test_momwire.py {project}/tests/test_hmatrix.py {project}/tests/test_array_block.py -m "not slow and not plot"'
85
+
86
+ [tool.cibuildwheel.linux]
87
+ # glibc >= 2.28 ships libmvec; manylinux2014 (glibc 2.17) does NOT, so the
88
+ # -lmvec link in setup.py would fail. _2_28 is the right "modern Linux" floor.
89
+ # (It is also cibuildwheel 3.x+'s default, but pin it explicitly so a future
90
+ # default change can't silently drop libmvec.)
91
+ manylinux-x86_64-image = "manylinux_2_28"
92
+ # Do NOT vendor libgomp (`--exclude libgomp.so.1`): auditwheel would otherwise
93
+ # bundle a private copy (momwire.libs/libgomp-<hash>.so) that uses initial-exec
94
+ # TLS. When another OpenMP extension (e.g. pynec-accel) loads the system libgomp
95
+ # first, momwire's vendored copy then fails to dlopen with "cannot allocate
96
+ # memory in static TLS block" and the accelerator silently drops to the slow
97
+ # pure-Python path. Excluding it makes momwire link the SYSTEM libgomp, so both
98
+ # engines share one runtime — the Linux mirror of the macOS `--exclude libomp`,
99
+ # and matching what pynec-accel already does. Needs a system libgomp at runtime
100
+ # (the GCC OpenMP runtime, universal on glibc Linux; `apt install libgomp1` if
101
+ # absent — manylinux images already have it for the build).
102
+ repair-wheel-command = "auditwheel repair --exclude libgomp.so.1 -w {dest_dir} {wheel}"
103
+
104
+ [tool.cibuildwheel.macos]
105
+ # Native Apple Silicon only for this first cut (the macos-14 runner is arm64).
106
+ # Intel macOS (x86_64) is intentionally out of scope.
107
+ archs = ["arm64"]
108
+ # libomp is provided by `brew install libomp` — at build AND at runtime (the
109
+ # wheel no longer bundles it, see --exclude below). Point setup.py's darwin
110
+ # branch at it and pin the deployment target to the runner's floor.
111
+ environment = { LIBOMP_PREFIX = "/opt/homebrew/opt/libomp", MACOSX_DEPLOYMENT_TARGET = "14.0" }
112
+ # Do NOT vendor libomp (`--exclude libomp`): leave the extension referencing
113
+ # Homebrew's libomp by its absolute install-name
114
+ # (/opt/homebrew/opt/libomp/lib/libomp.dylib). pynec-accel's macOS wheel excludes
115
+ # it too, so when both load in one process they share ONE libomp image instead of
116
+ # each loading a private copy. Two private copies is what triggered the OMP
117
+ # "Error #15" duplicate-runtime abort + the join-barrier deadlock, forcing the
118
+ # KMP_DUPLICATE_LIB_OK / OMP_NUM_THREADS=1 stopgap; one shared runtime keeps both
119
+ # backends fully multithreaded with no env vars. This mirrors the Linux wheels,
120
+ # where neither side vendors libgomp (glibc's is shared) — macOS has no system
121
+ # libomp, so the shared copy is Homebrew's, hence `brew install libomp` is a
122
+ # runtime requirement of the macOS wheel. DYLD_LIBRARY_PATH lets delocate resolve
123
+ # the (now-external) libomp during repair.
124
+ repair-wheel-command = "DYLD_LIBRARY_PATH=$(brew --prefix libomp)/lib delocate-wheel --exclude libomp --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
125
+
126
+ [tool.ruff.lint]
127
+ ignore = ["E741"]
128
+ # Keep "imported but unused" as a warning instead of auto-stripping.
129
+ # Without this, multi-pass edits (add imports first, then add code that
130
+ # uses them) can have the import deleted between edits — by the editor's
131
+ # format-on-save, the project's auto-fix hook, or a pre-commit run —
132
+ # producing a broken next edit. Manual cleanup of unused imports is fine.
133
+ unfixable = ["F401"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
momwire-0.2.3/setup.py ADDED
@@ -0,0 +1,150 @@
1
+ import os
2
+ import sys
3
+ import warnings
4
+
5
+ from setuptools import setup
6
+ from setuptools.command.build_ext import build_ext
7
+ from pybind11.setup_helpers import Pybind11Extension
8
+
9
+ # Build-time error classes. setuptools.errors is the modern home (distutils is
10
+ # removed in Python 3.12+); fall back to distutils for very old setuptools.
11
+ try:
12
+ from setuptools.errors import CCompilerError, ExecError, PlatformError, FileError
13
+ except ImportError: # pragma: no cover - ancient setuptools
14
+ from distutils.errors import ( # type: ignore[no-redef]
15
+ CCompilerError,
16
+ DistutilsExecError as ExecError,
17
+ DistutilsPlatformError as PlatformError,
18
+ DistutilsFileError as FileError,
19
+ )
20
+
21
+ # momwire._accelerators is an *optional* C++ speedup: every module that imports
22
+ # it (triangular, _triangular_kernels, _bspline_kernels, bspline, hmatrix,
23
+ # sinusoidal) guards the import with `try/except ImportError` and falls back to
24
+ # a pure-Python/numpy path. So a platform with no working compiler / libmvec /
25
+ # libomp (musllinux, glibc < 2.28, an arch outside the wheel matrix, or no
26
+ # toolchain at all) should still get a usable install rather than a hard
27
+ # `pip install` failure. This cmdclass makes a failed extension build a warning
28
+ # instead of an error, leaving the package importable in pure-Python mode.
29
+ #
30
+ # This does NOT let a silently-degraded *wheel* ship: the cibuildwheel
31
+ # test-command asserts `import momwire._accelerators` succeeds, so any CI wheel
32
+ # that fails to compile the extension fails its tests. The graceful path is only
33
+ # for source (sdist) installs on unsupported platforms.
34
+ _OPTIONAL_BUILD_ERRORS = (
35
+ CCompilerError,
36
+ ExecError,
37
+ PlatformError,
38
+ FileError, # inplace copy of an extension that never got built
39
+ FileNotFoundError, # compiler binary absent
40
+ )
41
+
42
+
43
+ class OptionalBuildExt(build_ext):
44
+ def run(self):
45
+ try:
46
+ super().run()
47
+ except _OPTIONAL_BUILD_ERRORS as exc:
48
+ self._warn(exc)
49
+
50
+ def build_extension(self, ext):
51
+ try:
52
+ super().build_extension(ext)
53
+ except _OPTIONAL_BUILD_ERRORS as exc:
54
+ self._warn(exc)
55
+
56
+ @staticmethod
57
+ def _warn(exc):
58
+ warnings.warn(
59
+ f"momwire._accelerators C++ extension failed to build ({exc!r}); "
60
+ "installing in pure-Python mode. The solver will work but run "
61
+ "slower. Install a C++ toolchain (and on Linux, glibc>=2.28 with "
62
+ "libmvec) for the accelerated path.",
63
+ stacklevel=2,
64
+ )
65
+
66
+ # The accelerator is built on all three platforms; the vectorization strategy
67
+ # differs per platform. Linux/GCC binds the inner sincos to glibc's libmvec
68
+ # (-lmvec) via the `omp declare simd` block in _accelerators.cpp; Windows/MSVC
69
+ # has no libmvec, so it relies on /arch:AVX2 autovectorization plus OpenMP
70
+ # parallelism; macOS Apple Silicon (arm64) has neither libmvec nor AVX2, so it
71
+ # relies on Homebrew libomp for OpenMP parallelism and lets clang autovectorize
72
+ # the inner loops for NEON. The .cpp guards the libmvec-specific declarations to
73
+ # non-MSVC, non-Apple compilers. If the extension fails to build/import,
74
+ # triangular.py falls back to pure Python.
75
+ if sys.platform == "win32":
76
+ # OpenMP on MSVC is a minefield for this code: /openmp:experimental rejects
77
+ # unsigned loop indices (the kernels use size_t) and silently drops the
78
+ # `reduction` clause from `omp simd` (a correctness hazard), while
79
+ # /openmp:llvm rejects the `omp simd` directive outright. We use
80
+ # /openmp:llvm — it supports the OpenMP 3.0 `collapse` clause and unsigned
81
+ # loop indices, so the parallel-for loops need no changes — and the .cpp
82
+ # neutralizes the `omp simd` directives under _MSC_VER, leaving /arch:AVX2
83
+ # autovectorization to handle the inner loops. /arch:AVX2 matches the Linux
84
+ # AVX2 baseline.
85
+ extra_compile_args = ["/O2", "/arch:AVX2", "/openmp:llvm", "/fp:fast"]
86
+ extra_link_args = []
87
+ elif sys.platform == "darwin":
88
+ # Apple clang ships no OpenMP runtime and macOS has no libmvec, so this
89
+ # branch is deliberately the "simple pragmas" port: Homebrew's libomp gives
90
+ # us the OpenMP parallel-for + omp-simd directives (passed through Apple
91
+ # clang via -Xpreprocessor -fopenmp), and clang autovectorizes the inner
92
+ # sincos for NEON on its own. No -mavx2/-mfma (arm64 has no AVX2) and no
93
+ # -lmvec (no vectorized libm on macOS); the libmvec `declare simd` block in
94
+ # _accelerators.cpp is #ifdef'd off under __APPLE__. delocate vendors the
95
+ # libomp dylib into the wheel (the -rpath below points the extension at it).
96
+ _libomp = os.environ.get("LIBOMP_PREFIX", "/opt/homebrew/opt/libomp")
97
+ extra_compile_args = [
98
+ "-O3",
99
+ "-Xpreprocessor",
100
+ "-fopenmp",
101
+ # Same errno rationale as the Linux branch: let the vectorizer run.
102
+ "-fno-math-errno",
103
+ "-std=gnu++11",
104
+ f"-I{os.path.join(_libomp, 'include')}",
105
+ ]
106
+ extra_link_args = [
107
+ f"-L{os.path.join(_libomp, 'lib')}",
108
+ "-lomp",
109
+ f"-Wl,-rpath,{os.path.join(_libomp, 'lib')}",
110
+ ]
111
+ else:
112
+ extra_compile_args = [
113
+ # Force -O3 -- Debian's Python CFLAGS inject -O2 before our flags
114
+ # and pybind11's default -O3 doesn't override that. Our -O3 here
115
+ # comes after both and wins (gcc takes the last -O).
116
+ "-O3",
117
+ "-fopenmp",
118
+ "-fopenmp-simd",
119
+ # AVX2 + FMA: required for the SIMD inner-loop sincos in
120
+ # _accelerators.cpp to use libmvec (vectorized libm). KBL/HSW
121
+ # and newer Intel; matches what pybind11 release wheels can't
122
+ # assume but a local pip install -e . can.
123
+ "-mavx2",
124
+ "-mfma",
125
+ # `std::cos` / `std::sin` set errno on domain errors by default,
126
+ # which is a global side effect that blocks auto-vectorization.
127
+ # We don't care about errno from a deterministic-domain real input,
128
+ # so disable the side effect to let the vectorizer kick in.
129
+ "-fno-math-errno",
130
+ "-g",
131
+ "-fno-omit-frame-pointer",
132
+ "-std=gnu++11",
133
+ ]
134
+ extra_link_args = ["-fopenmp", "-lpthread", "-lmvec"]
135
+
136
+ ext_modules = [
137
+ Pybind11Extension(
138
+ "momwire._accelerators",
139
+ ["src/momwire/_accelerators.cpp"],
140
+ extra_compile_args=extra_compile_args,
141
+ extra_link_args=extra_link_args,
142
+ ),
143
+ ]
144
+
145
+ setup(
146
+ ext_modules=ext_modules,
147
+ cmdclass={"build_ext": OptionalBuildExt},
148
+ packages=["momwire"],
149
+ package_dir={"": "src/"},
150
+ )
@@ -0,0 +1,17 @@
1
+ from ._accel import LOADED as accelerated
2
+ from .bspline import BSplineSolver
3
+ from .hmatrix import HMatrixSolver
4
+ from .array_block import ArrayBlockSolver
5
+ from .sinusoidal import SinusoidalSolver
6
+ from .triangular import TriangularSolver
7
+
8
+ # `accelerated` is True iff the optional C++ accelerator loaded; consumers can
9
+ # assert it to guard against a silent fall-back to the slow pure-Python path.
10
+ __all__ = [
11
+ "TriangularSolver",
12
+ "SinusoidalSolver",
13
+ "BSplineSolver",
14
+ "HMatrixSolver",
15
+ "ArrayBlockSolver",
16
+ "accelerated",
17
+ ]