Skip to content

Commit 4a88da4

Browse files
authored
Merge pull request #29 from Arcascope/4.3.0-macos
updates to get working on macOS (again)
2 parents b29ac51 + 5880ed3 commit 4a88da4

7 files changed

Lines changed: 85 additions & 15 deletions

File tree

‎CMakeLists.txt‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,20 @@ endif()
3737
# Flags must be semicolon-separated CMake lists, not space-separated strings,
3838
# so that each flag is passed as a separate argument to the compiler.
3939
if(APPLE)
40-
# Only use Homebrew-specific hints if Homebrew is available.
41-
find_program(HOMEBREW_EXECUTABLE brew)
42-
if(HOMEBREW_EXECUTABLE)
40+
if(DEFINED ENV{LIBOMP_PREFIX})
41+
set(LIBOMP_PREFIX "$ENV{LIBOMP_PREFIX}")
42+
else()
43+
# Only use Homebrew-specific hints if Homebrew is available.
44+
find_program(HOMEBREW_EXECUTABLE brew)
45+
endif()
46+
if(NOT LIBOMP_PREFIX AND HOMEBREW_EXECUTABLE)
4347
execute_process(
4448
COMMAND "${HOMEBREW_EXECUTABLE}" --prefix libomp
4549
OUTPUT_VARIABLE LIBOMP_PREFIX
4650
OUTPUT_STRIP_TRAILING_WHITESPACE
4751
)
52+
endif()
53+
if(LIBOMP_PREFIX)
4854
set(OpenMP_C_FLAGS "-Xpreprocessor;-fopenmp;-I${LIBOMP_PREFIX}/include")
4955
set(OpenMP_CXX_FLAGS "-Xpreprocessor;-fopenmp;-I${LIBOMP_PREFIX}/include")
5056
set(OpenMP_C_LIB_NAMES "omp")

‎README.md‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ python -m pip install https://github.com/Arcascope/sensor_preprocessing_cpp/rele
2424

2525
Installing from source, `pip install git+https://github.com/Arcascope/sensor_preprocessing_cpp.git@4.0.1`,
2626
compiles the native extension locally and requires a C++17 toolchain, CMake, and pybind11.
27+
On macOS it also requires Homebrew's OpenMP runtime (`brew install libomp`). senpy discovers
28+
that prefix itself; custom installations can set `LIBOMP_PREFIX`.
2729

2830
Release wheels are built by `.github/workflows/release-wheel.yml` (attached to a published
2931
release, or run manually to backfill a tag) and published to PyPI via OpenID Connect trusted
@@ -186,6 +188,12 @@ one per recording) for the full grid and its sample counts. Batch sizes remain
186188
a hardware-specific throughput setting: measure with `block_until_ready()` and
187189
a CUDA profiler before claiming GPU saturation.
188190

191+
On macOS, the standard JAX wheel uses the CPU backend. Pass `enable_x64=True`
192+
to `compute_nustft_many` when double-precision parity with the native CPU
193+
transform is more important than float32 throughput. This enables JAX's
194+
process-wide x64 setting before senpy allocates its buffers. Leave it off for
195+
Metal, whose kernels do not support float64.
196+
189197
## Streaming NUSTFT
190198

191199
`StreamingNUSTFT` computes the **same coefficients** from a live stream:
@@ -257,4 +265,3 @@ band at 30 s windows costs about 150 000 multiply-accumulates per second of stre
257265
* **Timestamp precision.** Absolute unix seconds in float64 resolve to about half a microsecond,
258266
which is a ~1e-5 relative phase error at the top of a 5 Hz band. Pass times relative to a recent
259267
origin when sub-microsecond timing matters.
260-

‎senpy/CMakeLists.txt‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ target_include_directories(_core PRIVATE ${CPP_DIR})
4545
target_include_directories(_core PRIVATE ${FINUFFT_INCLUDE_DIR})
4646

4747
# Link against required libraries
48-
target_link_libraries(_core PRIVATE ${FINUFFT_LIB} ${FFTW3_LIB} ${FFTW3F_LIB} m gomp)
48+
find_package(OpenMP REQUIRED)
49+
target_link_libraries(_core PRIVATE ${FINUFFT_LIB} ${FFTW3_LIB} ${FFTW3F_LIB} OpenMP::OpenMP_CXX)
50+
if(UNIX AND NOT APPLE)
51+
target_link_libraries(_core PRIVATE m)
52+
endif()
4953

5054
# Set RPATH for the module
5155
set_target_properties(_core PROPERTIES
@@ -56,4 +60,4 @@ set_target_properties(_core PROPERTIES
5660
install(TARGETS _core
5761
LIBRARY DESTINATION senpy
5862
RUNTIME DESTINATION senpy
59-
)
63+
)

‎senpy/_version.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "4.2.0"
1+
__version__ = "4.3.0"

‎senpy/jax_backend.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,7 @@ def compute_nustft_many(
992992
rows_per_call: int = DEFAULT_ROWS_PER_CALL,
993993
max_in_flight: int = 3,
994994
build_threads: int = 4,
995+
enable_x64: bool = False,
995996
) -> List[List[Any]]:
996997
"""Transform many multi-channel recordings at device throughput.
997998
@@ -1013,6 +1014,12 @@ def compute_nustft_many(
10131014
is enabled. Local sample coordinates are computed on the host in float64
10141015
before that cast, so long recordings keep their phase precision.
10151016
1017+
``enable_x64=True`` enables JAX's process-wide 64-bit mode before buffers
1018+
are allocated. This is useful on macOS's CPU backend when close numerical
1019+
agreement with the double-precision native transform matters. It is opt-in
1020+
because 64-bit mode affects all JAX work in the process and is unavailable
1021+
on some accelerators, including Metal.
1022+
10161023
Like the other transforms here, a recording with nothing to report gets
10171024
zero rows rather than an error.
10181025
"""
@@ -1041,6 +1048,8 @@ def compute_nustft_many(
10411048
_timestamp_scale(ts_unit)
10421049

10431050
jax, jnp = _dependencies()
1051+
if enable_x64:
1052+
jax.config.update("jax_enable_x64", True)
10441053
host_dtype = np.float64 if jnp.asarray(0.0).dtype == np.float64 else np.float32
10451054
complex_dtype = np.complex128 if host_dtype == np.float64 else np.complex64
10461055

‎senpy/setup.py‎

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,41 @@ def __str__(self):
5757
fftw3_lib = os.path.join(BUILD_DIR, "_deps/fftw3-build/libfftw3.a")
5858
fftw3f_lib = os.path.join(BUILD_DIR, "_deps/fftw3f-build/libfftw3f.a")
5959

60+
61+
def find_libomp_prefix():
62+
"""Return the macOS libomp prefix without relying on shell flags."""
63+
if platform.system() != "Darwin":
64+
return None
65+
configured = os.environ.get("LIBOMP_PREFIX")
66+
if configured:
67+
prefix = configured
68+
else:
69+
brew = shutil.which("brew")
70+
if brew is None:
71+
raise RuntimeError(
72+
"Building senpy on macOS requires libomp. Install it with "
73+
"'brew install libomp', or set LIBOMP_PREFIX to its install prefix."
74+
)
75+
try:
76+
prefix = subprocess.check_output(
77+
[brew, "--prefix", "libomp"], text=True
78+
).strip()
79+
except subprocess.CalledProcessError as exc:
80+
raise RuntimeError(
81+
"Homebrew could not locate libomp. Install it with "
82+
"'brew install libomp', or set LIBOMP_PREFIX to its install prefix."
83+
) from exc
84+
library = os.path.join(prefix, "lib", "libomp.dylib")
85+
if not os.path.exists(library):
86+
raise RuntimeError(
87+
f"libomp was not found at {library}. Install it with "
88+
"'brew install libomp', or correct LIBOMP_PREFIX."
89+
)
90+
return prefix
91+
92+
93+
LIBOMP_PREFIX = find_libomp_prefix()
94+
6095
def required_artifacts():
6196
return [finufft_include_dir, finufft_lib_so, fftw3_lib, fftw3f_lib]
6297

@@ -111,20 +146,29 @@ def _copy_runtime_libraries(self):
111146

112147
include_dirs.append(finufft_include_dir)
113148

149+
extra_link_args = [
150+
f'-L{finufft_lib_dir}',
151+
'-lfinufft',
152+
fftw3_lib, fftw3f_lib,
153+
'-Wl,-rpath,@loader_path' if platform.system() == 'Darwin' else '-Wl,-rpath,$ORIGIN',
154+
]
155+
if LIBOMP_PREFIX is not None:
156+
extra_link_args.extend([
157+
f'-L{os.path.join(LIBOMP_PREFIX, "lib")}',
158+
f'-Wl,-rpath,{os.path.join(LIBOMP_PREFIX, "lib")}',
159+
'-lomp',
160+
])
161+
else:
162+
extra_link_args.append('-lgomp')
163+
114164
ext_modules = [
115165
Extension(
116166
'senpy._core', # Full module path - creates senpy/_core.so
117167
sources=sources,
118168
include_dirs=include_dirs,
119169
language='c++',
120170
extra_compile_args=['-std=c++17', '-O3', '-march=x86-64-v3' if os.environ.get('CI') == 'true' else '-march=native', '-DPYTHON'],
121-
extra_link_args=[
122-
f'-L{finufft_lib_dir}',
123-
'-lfinufft',
124-
fftw3_lib, fftw3f_lib,
125-
'-Wl,-rpath,@loader_path' if platform.system() == 'Darwin' else '-Wl,-rpath,$ORIGIN',
126-
'-lomp' if platform.system() == 'Darwin' else '-lgomp',
127-
],
171+
extra_link_args=extra_link_args,
128172
),
129173
]
130174

‎senpy/tests/test_spectral_api.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,4 +276,4 @@ def test_removed_nufft_alias_and_uniform_compatibility_alias_warns():
276276

277277

278278
def test_version_is_consolidated():
279-
assert senpy.__version__ == sp.__version__ == "4.2.0"
279+
assert senpy.__version__ == sp.__version__ == "4.3.0"

0 commit comments

Comments
 (0)