From dc466fbfec6a740c716e29abb4902b0f9ff183c6 Mon Sep 17 00:00:00 2001 From: RoxGamba Date: Thu, 19 Mar 2026 17:06:13 -0700 Subject: [PATCH 1/3] Fixes: change requirements to include sxs, romspline and pycbc; choose versions compatible with numpy<2 (for pycbc match) --- PyART/__init__.py | 3 --- PyART/analysis/__init__.py | 2 -- PyART/analytic/__init__.py | 2 -- PyART/catalogs/__init__.py | 2 -- PyART/models/__init__.py | 2 -- PyART/numerical/__init__.py | 2 -- PyART/utils/__init__.py | 2 -- PyART/utils/convert_sxs_to_lvc.py | 2 +- examples/match_sxs.py | 11 ++++++++++- requirements.txt | 11 +++++++++-- setup.cfg | 3 ++- tests/test_gra.py | 1 + 12 files changed, 23 insertions(+), 20 deletions(-) diff --git a/PyART/__init__.py b/PyART/__init__.py index 462c39e..f296254 100644 --- a/PyART/__init__.py +++ b/PyART/__init__.py @@ -1,7 +1,4 @@ #!/usr/bin/env python -from __future__ import absolute_import - -__import__("pkg_resources").declare_namespace(__name__) from . import analysis from . import analytic diff --git a/PyART/analysis/__init__.py b/PyART/analysis/__init__.py index f5ba105..8b13789 100644 --- a/PyART/analysis/__init__.py +++ b/PyART/analysis/__init__.py @@ -1,3 +1 @@ -from __future__ import absolute_import -__import__("pkg_resources").declare_namespace(__name__) diff --git a/PyART/analytic/__init__.py b/PyART/analytic/__init__.py index f5ba105..8b13789 100644 --- a/PyART/analytic/__init__.py +++ b/PyART/analytic/__init__.py @@ -1,3 +1 @@ -from __future__ import absolute_import -__import__("pkg_resources").declare_namespace(__name__) diff --git a/PyART/catalogs/__init__.py b/PyART/catalogs/__init__.py index f5ba105..8b13789 100644 --- a/PyART/catalogs/__init__.py +++ b/PyART/catalogs/__init__.py @@ -1,3 +1 @@ -from __future__ import absolute_import -__import__("pkg_resources").declare_namespace(__name__) diff --git a/PyART/models/__init__.py b/PyART/models/__init__.py index f5ba105..8b13789 100644 --- a/PyART/models/__init__.py +++ b/PyART/models/__init__.py @@ -1,3 +1 @@ -from __future__ import absolute_import -__import__("pkg_resources").declare_namespace(__name__) diff --git a/PyART/numerical/__init__.py b/PyART/numerical/__init__.py index f5ba105..8b13789 100644 --- a/PyART/numerical/__init__.py +++ b/PyART/numerical/__init__.py @@ -1,3 +1 @@ -from __future__ import absolute_import -__import__("pkg_resources").declare_namespace(__name__) diff --git a/PyART/utils/__init__.py b/PyART/utils/__init__.py index f5ba105..8b13789 100644 --- a/PyART/utils/__init__.py +++ b/PyART/utils/__init__.py @@ -1,3 +1 @@ -from __future__ import absolute_import -__import__("pkg_resources").declare_namespace(__name__) diff --git a/PyART/utils/convert_sxs_to_lvc.py b/PyART/utils/convert_sxs_to_lvc.py index f5abad7..d696f28 100644 --- a/PyART/utils/convert_sxs_to_lvc.py +++ b/PyART/utils/convert_sxs_to_lvc.py @@ -731,7 +731,7 @@ def convert_simulation( The option resolution is an integer labeling the resolution of the converted waveform. Modes is an array of the format [[l1, m1], [l2, m2], ...] listing the l,m modes to convert. This function - outputs a file in LVC format named SXS_BBH_\#\#\#\#_Res\#.h5 + outputs a file in LVC format named SXS_BBH_####_Res#.h5 in out_path.""" horizons = h5py.File(sxs_data_path + "/Horizons.h5", "r") rhOverM = h5py.File(sxs_data_path + "/rhOverM_Asymptotic_GeometricUnits_CoM.h5") diff --git a/examples/match_sxs.py b/examples/match_sxs.py index 826628d..da5afc1 100644 --- a/examples/match_sxs.py +++ b/examples/match_sxs.py @@ -5,6 +5,9 @@ from PyART.models import teob from PyART.analysis.match import Matcher from PyART.utils import utils as ut +from PyART.logging_config import setup_logging + +setup_logging(level="INFO") matplotlib.rc("text", usetex=True) @@ -39,7 +42,13 @@ # load (or download) SXS data sxs_id = f"{args.sxs_id:04}" # e.g.0180 nr = sxs.Waveform_SXS( - path=sxs_path, download=True, ID=sxs_id, order="Extrapolated_N3.dir", ellmax=7 + path=sxs_path, + download=True, + ID=sxs_id, + ellmax=7, + ignore_deprecation=True, + downloads=["hlm", "metadata", "horizons"], + load=["hlm", "metadata", "horizons"], ) nr.cut(300) # nr.compute_hphc() diff --git a/requirements.txt b/requirements.txt index 827a637..e67385e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,6 @@ # Requirements for most basic library +# Note: numpy<2.0 required — pycbc>=2.4.0 uses np.array(..., copy=False) which +# changed semantics in numpy 2.x and raises ValueError at runtime. numpy>=1.16.0,<2.0,!=1.19.0 scipy>=1.13.0 rich>=13.7.0 @@ -8,11 +10,16 @@ astropy>=5.0.0 setuptools # GW package dependencies -# pycbc>=2.4.0 +pycbc>=2.4.0 # teobresums>=4.4.0 # gw-eccentricity>=1.0.4 # mayawaves>=2024.4.25 -# lalsuite>=7.22 +lalsuite>=7.22 + +# SXS catalog support (optional; needed for downloading SXS simulations) +# sxs>=2025.0 requires numpy>=2.0 which is incompatible with pycbc; pin <2025 +sxs>=2024.0.0,<2025.0 +romspline # Documentation sphinx>=4.2.0 diff --git a/setup.cfg b/setup.cfg index 82d4c24..1e05e46 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,6 +16,7 @@ classifiers = Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 Topic :: Scientific/Engineering :: Astronomy Topic :: Scientific/Engineering :: Physics Natural Language :: English @@ -24,4 +25,4 @@ classifiers = [options] packages = find: install_requires = file: requirements.txt -python_requires = >=3.9 +python_requires = >=3.9,<3.13 diff --git a/tests/test_gra.py b/tests/test_gra.py index 60363aa..fd3d649 100644 --- a/tests/test_gra.py +++ b/tests/test_gra.py @@ -19,6 +19,7 @@ def test_gra(): res="128", downloads=["hlm", "metadata"], ext="CCE", + r_ext="50.00", ) # check attributes assert wf.ID == "0001" From cba6f1171600a9e2317a49c824d71a678ae3cf7f Mon Sep 17 00:00:00 2001 From: RoxGamba Date: Thu, 19 Mar 2026 18:39:33 -0700 Subject: [PATCH 2/3] Fix: apply patch to pycbc after installation for compatibility with np > 2 --- .github/workflows/tests.yml | 8 ++- README.md | 8 +++ requirements.txt | 10 ++-- scripts/patch_pycbc_numpy2.py | 96 +++++++++++++++++++++++++++++++++++ setup.py | 32 +++++++++++- 5 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 scripts/patch_pycbc_numpy2.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8efdd57..acaf7b5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,8 +26,12 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - pip install flake8 pytest 'sxs==2025.0.9' romspline pycbc requests bs4 + pip install -r requirements.txt + pip install flake8 pytest + pip install -e . + - name: Apply pycbc numpy 2.x compatibility patch + run: | + python scripts/patch_pycbc_numpy2.py #- name: Lint with flake8 # run: | # stop the build if there are Python syntax errors or undefined names diff --git a/README.md b/README.md index 9c01ea8..0843591 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,14 @@ Install with pip install . ``` +> **Note:** PyART depends on `pycbc`, which currently has two incompatibilities with numpy 2.x. +> After installing, apply the one-time patch: +> ``` +> python scripts/patch_pycbc_numpy2.py +> ``` +> This patches `pycbc`'s `events/threshold_cpu.py` and `filter/matchedfilter.py` +> in-place. It is idempotent and safe to re-run after pycbc upgrades. + ## For developers If you are a developer: diff --git a/requirements.txt b/requirements.txt index e67385e..c372a1a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,8 @@ # Requirements for most basic library -# Note: numpy<2.0 required — pycbc>=2.4.0 uses np.array(..., copy=False) which -# changed semantics in numpy 2.x and raises ValueError at runtime. -numpy>=1.16.0,<2.0,!=1.19.0 +# Note: pycbc>=2.4.0 uses np.array(..., copy=False) which changed semantics in +# numpy 2.x. After installing, apply the one-time patch: +# python scripts/patch_pycbc_numpy2.py +numpy>=2.0 scipy>=1.13.0 rich>=13.7.0 matplotlib>=3.7.3 @@ -17,8 +18,7 @@ pycbc>=2.4.0 lalsuite>=7.22 # SXS catalog support (optional; needed for downloading SXS simulations) -# sxs>=2025.0 requires numpy>=2.0 which is incompatible with pycbc; pin <2025 -sxs>=2024.0.0,<2025.0 +sxs>=2025.0 romspline # Documentation diff --git a/scripts/patch_pycbc_numpy2.py b/scripts/patch_pycbc_numpy2.py new file mode 100644 index 0000000..a200d75 --- /dev/null +++ b/scripts/patch_pycbc_numpy2.py @@ -0,0 +1,96 @@ +""" +Patch pycbc for numpy 2.x compatibility. + +pycbc <= 2.10.0 has two numpy 2.x incompatibilities: + + 1. events/threshold_cpu.py: np.array(..., copy=False) raises ValueError in + numpy 2.x when a copy is required; numpy.asarray() preserves the original + numpy 1.x semantics (copy only if necessary, silently). + + 2. filter/matchedfilter.py: numpy.real(x) / numpy.imag(x) dispatch via + x.real / x.imag. On pycbc Array objects these are plain methods, not + properties, so numpy.real() returns the bound method instead of the data. + Replacing with numpy.asarray(x).real / .imag forces conversion via + Array.__array__ first, which is safe and localized to the affected calls. + +Run once after installing pycbc: + python scripts/patch_pycbc_numpy2.py +""" + +import inspect +import re +import shutil +import sys +from pathlib import Path + + +def _clear_cache(path): + cache_dir = path.parent / "__pycache__" + if cache_dir.exists(): + shutil.rmtree(cache_dir) + print(f" Cleared __pycache__ in {cache_dir.parent.name}/") + + +def patch_threshold_cpu(): + try: + import pycbc.events.threshold_cpu as mod + except ImportError: + print("pycbc not found – nothing to patch.", file=sys.stderr) + sys.exit(1) + + path = Path(inspect.getfile(mod)) + src = path.read_text() + original = src + + src = src.replace( + "numpy.array(series.data, copy=False, dtype=numpy.complex64)", + "numpy.asarray(series.data, dtype=numpy.complex64)", + ) + src = re.sub( + r"numpy\.array\(series\.data,\s*copy=False,\s*\n(\s*)dtype=numpy\.complex64\)", + r"numpy.asarray(series.data,\n\1dtype=numpy.complex64)", + src, + ) + + if src == original: + print("threshold_cpu.py: already patched or pattern not found, skipping.") + else: + path.write_text(src) + _clear_cache(path) + print("threshold_cpu.py: patched (copy=False → asarray).") + + +def patch_matchedfilter(): + try: + import pycbc.filter.matchedfilter as mod + except ImportError: + print("pycbc not found – nothing to patch.", file=sys.stderr) + sys.exit(1) + + path = Path(inspect.getfile(mod)) + src = path.read_text() + original = src + + replacements = [ + ("numpy.real(hplus)", "numpy.asarray(hplus).real"), + ("numpy.real(hcross)", "numpy.asarray(hcross).real"), + ("numpy.imag(hplus)", "numpy.asarray(hplus).imag"), + ("numpy.imag(hcross)", "numpy.asarray(hcross).imag"), + ("numpy.real(hphccorr)", "numpy.asarray(hphccorr).real"), + ("numpy.real(hplus_cross_corr)", "numpy.asarray(hplus_cross_corr).real"), + ] + for old, new in replacements: + src = src.replace(old, new) + + if src == original: + print("matchedfilter.py: already patched or pattern not found, skipping.") + else: + path.write_text(src) + _clear_cache(path) + n = sum(1 for a, b in zip(original.splitlines(), src.splitlines()) if a != b) + print(f"matchedfilter.py: patched ({n} lines changed).") + + +if __name__ == "__main__": + patch_threshold_cpu() + patch_matchedfilter() diff --git a/setup.py b/setup.py index 7fada01..467517c 100644 --- a/setup.py +++ b/setup.py @@ -3,10 +3,36 @@ setup.py file for GWforge package """ -from setuptools import setup, find_packages +from setuptools import setup, find_packages, Command +from setuptools.command.install import install +from setuptools.command.develop import develop import os +PATCH_MESSAGE = """ +============================================================ + PyART post-install step required +------------------------------------------------------------ + pycbc <= 2.10.0 is incompatible with numpy 2.x. + Run the following command once to patch pycbc in-place: + + python scripts/patch_pycbc_numpy2.py + + The patch is idempotent and safe to re-run after pycbc + upgrades. +============================================================ +""" + + +def print_patch_notice(command_cls): + class Patched(command_cls): + def run(self): + super().run() + print(PATCH_MESSAGE) + + return Patched + + def find_files( dirname, relpath=None, @@ -52,6 +78,10 @@ def find_paths(directory): ignore_files = [".DS_Store", "*.pyc", "Thumbs.db", "*.sqlite", "*.swp", "*.env", ".env"] setup( + cmdclass={ + "install": print_patch_notice(install), + "develop": print_patch_notice(develop), + }, scripts=find_files( "bin/", relpath="./", ignore_dirs=ignore_dirs, ignore_files=ignore_files ), From 3111aa90f338a14e58b0ab97118b15ed92b6fbe0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 01:45:14 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 467517c..ee37bc4 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,6 @@ from setuptools.command.develop import develop import os - PATCH_MESSAGE = """ ============================================================ PyART post-install step required