From 586ad077e856952ed5a058e6cd0c9a5f1495afd1 Mon Sep 17 00:00:00 2001 From: CMacM Date: Wed, 4 Mar 2026 14:48:47 +0000 Subject: [PATCH] Fixed installation bug causing Galsim libraies to not be correctly linked when BATSim is built. Updated readme with conda-preferred installation instructions and added unit test to ensure linkage was successful. --- README.md | 90 +++++++++++++++++++++--- conda/recipe/meta.yaml | 49 +++++++++++++ notebooks/dev/debug_getflucvec.ipynb | 101 +++++++++++++++++++++++++++ pyproject.toml | 13 ++-- setup.py | 53 +++++++++----- tests/test_c_layer.py | 25 +++++++ 6 files changed, 296 insertions(+), 35 deletions(-) create mode 100644 conda/recipe/meta.yaml create mode 100644 notebooks/dev/debug_getflucvec.ipynb create mode 100644 tests/test_c_layer.py diff --git a/README.md b/README.md index 113c8cc..4869bf5 100644 --- a/README.md +++ b/README.md @@ -10,25 +10,93 @@ Non-affine transforms can simualate complex shear effects such as intrinsic alignment, flexion, and optical field distrotion maps. Custom transform functions can also be passed to Stamp objects. -## Installation +## Building and installing BATSim from source (Conda + GalSim C++) -The package is not currently available on PyPI while in early development. +The package is not currently available on PyPI and only installable on Linux while in early development. We suggest using conda/mamba to build the package and install its dependencies. This is because the primary dependency, Galsim, does not ship a pre-built C++ library via pip. -First clone the repository: -```shell +The below instructions will work with pure conda, but we recommend using mamba to make the installation of dependencies much quicker. + +First, make sure to clone and switch to the repository root: + +```bash git clone https://github.com/CMacM/BATSim.git +cd BATSim ``` -Then install the package -```shell -cd BATSim -conda install --file requirements.txt -c conda-forge -pip install . --user +## Regular Install (not editable) + +### 1. Create build environment and activate +```bash +mamba create -n batsim -c conda-forge -c defaults python=3.10 conda-build boa +mamba activate batsim +``` + +### 2. Build the package +From the repository root: +```bash +# mambabuild is sometimes only recognised as a conda command +conda mambabuild --override-channels -c conda-forge -c defaults conda/recipe +``` + +This will: +- Create an isolated environment to build and run batsim +- Install all required dependencies +- Compile the BATSim C++ extension and link it to Galsim's C++ API +- Produce a conda package in: +```bash +$CONDA_PREFIX/conda-bld/linux-64/ +``` + +### 3. Install the built package +Install the locally built package into the new environment: +```bash +mamba install -c local batsim +``` + +If this fails, install directly from the build artifact: +```bash +mamba install $CONDA_PREFIX/conda-bld/linux-64/batsim-*.tar.bz2 ``` -Potential installation pitfalls: -- The above will install the python package of the dependence GalSim. However, you may need to install the C++ shared library of GalSim, not installed by default with the above installation. Find details on how to do this [here][https://galsim-developers.github.io/GalSim/_build/html/install_pip.html]. You may then need to update your LD\_LIBRARY\_PATH, LIBRARY\_PATH, and CPLUS\_INCLUDE\_PATH to point to build and include folders for the GalSim C++ shared library. +## Development Installation (Editable) + +For development you can install BATSim in editable mode so that Python changes +take effect immediately without reinstalling. + +### 1. Create a development environment and activate + +Note: You may encounter issues if some of these packages are already installed locally and have different builds. To fix, remove them and ensure they are installed through conda-forge. + +```bash +mamba create -n batsim-dev -c conda-forge -c defaults \ + python=3.10 \ + galsim \ + eigen \ + pybind11 \ + numpy \ + fitsio \ + astropy \ + matplotlib +mamba activate batsim-dev +``` + +### 2. Install BATSim in editable mode + +```bash +pip install -e . +``` + +After following either of the above installation routes, with the conda environment active, verify the installation: + +```bash +python -c "import batsim; import batsim._gsinterface; print('BATSim installed successfully')" +``` + +## Pip only installation + +We currently do not provide a pip only installation route as Galsim requires the shared C++ library to be built and linked manually when installed via pip. If you wish to build via pip only, you will need to install all dependencies and build and link the Galsim C++ headers. You can find details on how to do this [here][https://galsim-developers.github.io/GalSim/_build/html/install_pip.html]. You may then need to update your LD\_LIBRARY\_PATH, LIBRARY\_PATH, and CPLUS\_INCLUDE\_PATH to point to build and include folders for the GalSim C++ shared library. +We plan to add a pure pip installation route in future. ![BATSim Logo](./image/batsim_logo.png) diff --git a/conda/recipe/meta.yaml b/conda/recipe/meta.yaml new file mode 100644 index 0000000..d8a6a4f --- /dev/null +++ b/conda/recipe/meta.yaml @@ -0,0 +1,49 @@ +{% set name = "batsim" %} +{% set version = "0.0.0" %} + +package: + name: {{ name|lower }} + version: {{ version }} + +source: + path: ../.. + +build: + number: 0 + script: "{{ PYTHON }} -m pip install . -vv --no-deps --no-build-isolation" + # skip: true # [not linux] # uncomment if you want linux-only builds + +requirements: + # Do not request conda-forge compiler toolchain packages on this cluster + # (they are unavailable, and the solve fails). Use system gcc/g++ instead. + build: [] + host: + - python + - pip + - setuptools + - wheel + - numpy + - pybind11 + - galsim + - eigen + - fitsio + run: + - python + - numpy + - pybind11 + - galsim + - eigen + - fitsio + - matplotlib + - astropy + +test: + commands: + - test -d "$PREFIX/include/eigen3/Eigen" + imports: + - batsim + - batsim._gsinterface + +about: + license: MIT + summary: "BATSim package (links against GalSim C++ library)" \ No newline at end of file diff --git a/notebooks/dev/debug_getflucvec.ipynb b/notebooks/dev/debug_getflucvec.ipynb new file mode 100644 index 0000000..eecd970 --- /dev/null +++ b/notebooks/dev/debug_getflucvec.ipynb @@ -0,0 +1,101 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "5da4e60a", + "metadata": {}, + "outputs": [], + "source": [ + "import batsim\n", + "import galsim\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "49b826d8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/plain": [ + "array([[0.0285582]])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sb_obj = galsim.Sersic(n=4, half_light_radius=0.5)\n", + "\n", + "print(sb_obj._sbp)\n", + "\n", + "batsim._gsinterface.getFluxVec(scale=0.2, gsobj=sb_obj._sbp, xy_coords=np.array([[0.1, 0.1], [0.2, 0.2]]))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4abe2a44", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/plain": [ + "array([[0.0285582]])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trans_obj = galsim.Sersic(n=4, half_light_radius=0.5).shear(g1=0.1, g2=0.2).shift(0.5, 0.5)\n", + "\n", + "print(trans_obj._sbp)\n", + "\n", + "batsim._gsinterface.getFluxVec(scale=0.2, gsobj=sb_obj._sbp, xy_coords=np.array([[0.1, 0.1], [0.2, 0.2]]))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "batsim-dev", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.19" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyproject.toml b/pyproject.toml index 3263962..2e0d1cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,14 @@ [build-system] requires = [ - "setuptools>=38", # Used to build and package the Python project - "pybind11>=2.2", # Builds python - cpp interface for direct calls to galsim cpp layer - "wheel" + "setuptools>=61", + "wheel", + "pybind11>=2.2", + "numpy" ] build-backend = "setuptools.build_meta" [tool.pytest.ini_options] -testpaths = [ - "tests", -] +testpaths = ["tests"] addopts = "-vv -s" [tool.black] @@ -18,4 +17,4 @@ target-version = ["py38"] [tool.isort] profile = "black" -line_length = 110 +line_length = 110 \ No newline at end of file diff --git a/setup.py b/setup.py index 9dce8e0..7a3fc57 100644 --- a/setup.py +++ b/setup.py @@ -20,26 +20,45 @@ def run(self): super().run() def find_galsim_paths(self): - # Implement logic to locate GalSim's include and library directories - # This is a placeholder implementation; adjust based on your setup - - # Example: Assuming GalSim is installed in a conda environment - conda_prefix = os.environ.get("CONDA_PREFIX") include_dirs = [] lib_dirs = [] - if conda_prefix: - include_dirs.append(os.path.join(conda_prefix, "include")) - include_dirs.append(os.path.join(conda_prefix, "include/galsim")) - include_dirs.append(os.path.join(conda_prefix, "include/eigen3/")) - lib_dirs.append(os.path.join(conda_prefix, "lib")) - else: - # Fallback or other logic to locate GalSim - include_dirs.append("/usr/local/include") - include_dirs.append("/usr/local/include/galsim") - include_dirs.append("/usr/local/include/eigen3") - lib_dirs.append("/usr/local/lib") - return include_dirs, lib_dirs + # Prefer GalSim's own include directory if import works + try: + import galsim + inc = galsim.include_dir # .../site-packages/galsim/include + include_dirs.append(inc) + include_dirs.append(os.path.join(inc, "galsim")) # <-- add this + except Exception: + print("Error: Could not import GalSim to find include directory.") + + # Conda-build uses PREFIX for the host env (headers live here) + prefixes = [ + os.environ.get("PREFIX"), # conda-build host env + os.environ.get("CONDA_PREFIX"), # active env fallback + ] + + for p in prefixes: + if not p: + continue + include_dirs.append(os.path.join(p, "include")) + include_dirs.append(os.path.join(p, "include", "galsim")) + include_dirs.append(os.path.join(p, "include", "eigen3")) + lib_dirs.append(os.path.join(p, "lib")) + + # 3) Last-resort system paths + include_dirs += ["/usr/local/include", "/usr/include", "/usr/include/eigen3"] + lib_dirs += ["/usr/local/lib", "/usr/lib"] + + # De-duplicate preserving order + def uniq(xs): + out = [] + for x in xs: + if x and x not in out: + out.append(x) + return out + + return uniq(include_dirs), uniq(lib_dirs) # Define your extension module diff --git a/tests/test_c_layer.py b/tests/test_c_layer.py new file mode 100644 index 0000000..d9b5c25 --- /dev/null +++ b/tests/test_c_layer.py @@ -0,0 +1,25 @@ +import batsim +import galsim +import numpy as np + +def test_get_flux_vec(): + sb_obj = galsim.Sersic(n=4, half_light_radius=0.5) + trans_obj = sb_obj.shear(g1=0.1, g2=0.2).shift(0.5, 0.5) + + try: + sb_flux = batsim._gsinterface.getFluxVec( + scale=0.2, gsobj=sb_obj._sbp, xy_coords=np.array([[0.1, 0.1], [0.2, 0.2]]) + ) + except Exception as e: + print("Error in getFluxVec for Sersic profile:", e) + + try: + trans_flux = batsim._gsinterface.getFluxVec( + scale=0.2, gsobj=trans_obj._sbp, xy_coords=np.array([[0.1, 0.1], [0.2, 0.2]]) + ) + except Exception as e: + print("Error in getFluxVec for Transform profile:", e) + +if __name__ == "__main__": + test_get_flux_vec() +