diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 800ec31..6457996 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -84,6 +84,9 @@ jobs: # Set LD_LIBRARY_PATH so auditwheel can find shared libs to bundle CIBW_ENVIRONMENT_LINUX: > LD_LIBRARY_PATH=/usr/local/deps/lib:/usr/local/deps/lib64:$LD_LIBRARY_PATH + CMAKE_PREFIX_PATH=/usr/local/deps + OMEGA_H_ROOT=/usr/local/deps + KOKKOS_ROOT=/usr/local/deps # auditwheel repair is run automatically by cibuildwheel diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca37430..e67cdec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: strategy: matrix: build_type: ["Release", "Debug"] - python-version: ["3.11", "3.12", "3.13"] + python-version: ["3.11", "3.13"] name: "Python ${{ matrix.python-version }} - ${{ matrix.build_type }}" @@ -117,6 +117,9 @@ jobs: shell: bash run: | cd ${GITHUB_WORKSPACE} + export CMAKE_PREFIX_PATH="${{ runner.temp }}/build-omega_h/install:${{ runner.temp }}/build-kokkos/install:$CMAKE_PREFIX_PATH" + export OMEGA_H_ROOT="${{ runner.temp }}/build-omega_h/install/" + export KOKKOS_ROOT="${{ runner.temp }}/build-kokkos/install" python -m build pip install .[test] @@ -129,61 +132,15 @@ jobs: export OMP_NUM_THREADS=2 pytest -vv -s - - name: Create and repair wheel + # Creating the wheel is enough now since it will be repaired by cibuildwheel + - name: Create wheel shell: bash run: | cd ${GITHUB_WORKSPACE} - pip install auditwheel patchelf # Build the wheel + export CMAKE_PREFIX_PATH="${{ runner.temp }}/build-omega_h/install:${{ runner.temp }}/build-kokkos/install:$CMAKE_PREFIX_PATH" + export OMEGA_H_ROOT="${{ runner.temp }}/build-omega_h/install/" + export KOKKOS_ROOT="${{ runner.temp }}/build-kokkos/install" python -m build --wheel --outdir dist/ echo "---- Built wheels ----" ls -la dist/ - # Repair the wheel — bundle shared libs (omega_h, kokkos, etc.) - export LD_LIBRARY_PATH="${{ runner.temp }}/build-omega_h/install/lib:${{ runner.temp }}/build-kokkos/install/lib:${LD_LIBRARY_PATH}" - auditwheel show dist/*.whl - auditwheel repair dist/*.whl --wheel-dir repaired_wheels/ - echo "---- Repaired wheels ----" - ls -la repaired_wheels/ - - - name: Upload repaired wheel - uses: actions/upload-artifact@v4 - with: - name: wheel-py${{ matrix.python-version }}-${{ matrix.build_type }} - path: repaired_wheels/*.whl - - test-wheel: - needs: main - runs-on: ubuntu-24.04 - strategy: - matrix: - python-version: ["3.11"] - - name: "Test Wheel - Python ${{ matrix.python-version }}" - - steps: - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Download repaired wheel - uses: actions/download-artifact@v4 - with: - name: wheel-py${{ matrix.python-version }}-Release - path: wheel/ - - - name: Install wheel - shell: bash - run: | - ls -la wheel/ - pip install --extra-index-url https://shimwell.github.io/wheels openmc - pip install netCDF4==1.7.2 - pip install wheel/*.whl - - - name: Test convert2degas2 --help - shell: bash - run: convert2degas2 --help - - - name: Test convert2openmc --help - shell: bash - run: convert2openmc --help diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 4f3c852..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,12 +0,0 @@ -include CMakeLists.txt -include LICENSE -include pyproject.toml -include pytest.toml -global-include *.cmake -global-include *.cmake.in -recursive-include pythonAPI/omegah2csg/lib *.so -recursive-include src *.cc -recursive-include src *.cpp -recursive-include src *.h -recursive-include tests *.py -recursive-include tests *.osh diff --git a/README.md b/README.md index 79f9af1..6480abc 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # Convert `Omega_h` Mesh to CSG Format [![License](https://img.shields.io/badge/license-BSD--3--Clause-02B36C)](https://github.com/Fuad-HH/readOH2csg/blob/parallel/LICENSE) [![GitHub Actions build status (Linux)](https://github.com/Fuad-HH/readOH2csg/actions/workflows/ci.yml/badge.svg?branch=parallel)](https://github.com/Fuad-HH/readOH2csg/actions/workflows/ci.yml) -[![Release](https://img.shields.io/github/v/release/Fuad-HH/readOH2csg?include_prereleases -)](https://github.com/Fuad-HH/readOH2csg/releases) +[![Release](https://img.shields.io/github/v/release/Fuad-HH/readOH2csg?include_prereleases)](https://github.com/Fuad-HH/readOH2csg/releases) Efficiently convert Tokamak mesh of `Omega_h` (`.osh`) format to Constructive Solid Geometry (CSG). It supports creating @@ -16,6 +15,8 @@ CSG representation in ### 1. Pre-built Wheel This is the recommended way to install this package. We provide a pre-built wheel (`Kokkos` with `OpenMP` backend) for Python. Follow these steps to install the latest version of `readOH2csg` using pip: +Wheels are built in CI and uploaded as workflow artifacts for each Python version and build type. + 1. On any Linux system, make sure you have Python (Version ≥ 3.11) installed. Go to the terminal and `cd` to the directory where you want to install the Python virtual environment. ```bash @@ -31,13 +32,17 @@ pip install --extra-index-url https://shimwell.github.io/wheels openmc pip install netCDF4==1.7.2 ``` 3. Install `readOH2csg`: + ```bash -pip install https://github.com/Fuad-HH/readOH2csg/releases/download/v0.0.1-alpha/omegah2csg-0.0.1-py3-none-any.whl omegah2csg +pip install omegah2csg ``` -or +Or if you want to install the latest release from GitHub: ```bash -pip install -i https://test.pypi.org/simple/ omegah2csg +pip install omegah2csg ``` +>[!TIP] +> Find the latest release on the [Releases Page](https://github.com/Fuad-HH/readOH2csg/releases/) +> and copy the link to the wheel file for your Python version and system architecture. 4. Try running the CLI tool, and it should print the help message: ```bash @@ -51,7 +56,7 @@ with [`Kokkos`](https://github.com/kokkos/kokkos) and it supports any [`Kokkos`] Follow these steps to build and install `readOH2csg` from source: 1. Make sure you have compilers (at least `g++` or some alternative), `Python` (Version ≥ 3.11), and `CMake` available. -1. Install [`Kokkos`](https://github.com/kokkos/kokkos) following the instructions in +2. Install [`Kokkos`](https://github.com/kokkos/kokkos) following the instructions in [Kokkos Build Documentation](https://kokkos.org/kokkos-core-wiki/get-started/building-from-source.html#configuring-and-building-kokkos). You can choose any backend supported. Here's an example for building with the `OpenMP` backend: ```bash git clone --depth=2 --branch 4.7.02 https://github.com/kokkos/kokkos.git @@ -98,10 +103,13 @@ cmake --build build -j4 --target install >[!WARNING] > For now, only the Python API and CLI work. Please do not turn off the option for Python bindings. It is > enabled by default. + 5. Create Python virtual environment and install dependencies as described in the [Pre-built Wheel](#1-pre-built-wheel) section. 6. Install `readOH2csg` and run the CLI tool, and it should print the help message: ```bash # from the source directory +export KOKKOS_ROOT= +export OMEGA_H_ROOT= python -m pip install . convert2degas2 --help ``` diff --git a/doc/DEVELOPERS_GUIDE.md b/doc/DEVELOPERS_GUIDE.md index 6c4fda0..ce8b413 100644 --- a/doc/DEVELOPERS_GUIDE.md +++ b/doc/DEVELOPERS_GUIDE.md @@ -4,13 +4,14 @@ It describes the packaging strategy and choices of tools. To get started with Python packaging, check out the [Python Packaging User Guide](https://packaging.python.org/en/latest/). ## Formatting Style -All codes in this project are formatted with formatting tools. All these formattings are enforced in the CI pipeline. - ->[!TIP] If formatting is passing locally but failing in CI, make sure you are using the same version of the formatting tools as specified in the CI pipeline. +Formatting tools are used to maintain a consistent code style across the project. They are also enforced in CI to ensure that all contributions adhere to the same style guidelines. The formatting tools used in this project are: - C++ uses `clang-format` with the configuration specified in the [.clang-format](../.clang-format) file. - CMake files use `cmake-format` with the configuration specified in the [.cmake-format](../.cmake-format.yaml) file. -- Python files uses [`ruff`](https://docs.astral.sh/ruff/formatter/) to format the code with the configuration specified in the [pyproject.toml](../pyproject.toml) file. +- Python files use [`ruff`](https://docs.astral.sh/ruff/formatter/) to format the code with the configuration specified in the [pyproject.toml](../pyproject.toml) file. + +>[!TIP] +> If formatting is passing locally but failing in CI, make sure you are using the same version of the formatting tools as specified in the CI pipeline. ### C++ and CMake Formatting The C++ codes use the [.clang-format](../.clang-format) file and to format the code, you can use `clang-format` tool @@ -296,47 +297,51 @@ pytest ``` ## Packaging -### Create Distribution Files -After development and testing, to create the archive and wheel for distribution, install `build` using `pip` and run: +### Overview +This project uses `scikit-build-core` to build a Python wheel that includes the native +`libomegah2csg.so` shared library. You can build locally or use the CI workflow to +produce manylinux wheels. + +### Local build (developer machine) +You must have Omega_h and Kokkos installed and discoverable by CMake before building. +1. Install build tooling: ```bash -python -m build +python -m pip install -U build ``` -which will create the distribution files in the `dist/` directory. - -### Repair the Wheel with `auditwheel` -The built wheel contains a shared library (`omegah2csg.so`) that links against external shared -libraries (Omega\_h, Kokkos, etc.). To make the wheel portable and self-contained, use -[`auditwheel`](https://github.com/pypa/auditwheel) to bundle those shared libraries into the wheel. -First, install `auditwheel` and `patchelf`: +2. Provide CMake hints (use your install prefixes): ```bash -pip install auditwheel patchelf +export CMAKE_PREFIX_PATH="::${CMAKE_PREFIX_PATH}" ``` -Make sure the shared libraries that the wheel depends on are discoverable via `LD_LIBRARY_PATH`. -For example, if Omega\_h and Kokkos are installed under `/path/to/omega_h` and `/path/to/kokkos`: +3. Build sdist + wheel: ```bash -export LD_LIBRARY_PATH="/path/to/omega_h/lib64:/path/to/kokkos/lib64:${LD_LIBRARY_PATH}" +python -m build ``` -Then inspect and repair the wheel: +Artifacts are written to `dist/`. + +### CI build (manylinux wheels) +The `Build Manylinux Wheels` workflow builds Omega_h and Kokkos in the container and +then builds wheels with `cibuildwheel`. The resulting wheels are uploaded as workflow +artifacts named `manylinux-wheels`. + +To download wheels from CI with GitHub CLI: ```bash -# Show external shared library dependencies -auditwheel show dist/*.whl +# Find the workflow run you want +gh run list --workflow "Build Manylinux Wheels" -# Repair the wheel — bundles shared libs into the wheel -auditwheel repair dist/*.whl --wheel-dir repaired_wheels/ +# Download the wheels from a specific run +gh run download --name manylinux-wheels ``` -The repaired wheel will be in the `repaired_wheels/` directory and can be installed on any -compatible Linux system without needing Omega\_h or Kokkos installed separately. - ### Upload to PyPI/TestPyPI -To upload the distribution files to PyPI or TestPyPI, install `twine` using `pip` and run: +To upload the distribution files to PyPI or TestPyPI, install `twine` and run: ```bash +python -m pip install -U twine python -m twine upload --repository testpypi dist/* ``` -It will require a `testpypi` `index-server` entry in your `~/.pypirc` file. See PyPI documentation -for more details. +This requires a `testpypi` `index-server` entry in your `~/.pypirc`. See the PyPI +documentation for details. diff --git a/doc/top_bottom_flag.md b/doc/top_bottom_flag.md index 8ff2f2c..c8c745f 100644 --- a/doc/top_bottom_flag.md +++ b/doc/top_bottom_flag.md @@ -1,6 +1,6 @@ # Top or Bottom of a Cone This figure illustrates that both on the line segments here create two different cones. But for -our case, we only want to half cone that touches the line. +our case, we only want the half cone that touches the line. -![Figure shows two different cones generated by two lines](images/compute_coefficients.svg) \ No newline at end of file +![Figure shows two different cones generated by two lines](images/compute_coefficients.svg) diff --git a/pyproject.toml b/pyproject.toml index e0bb576..eacf278 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["setuptools >= 77.0.3", "wheel"] -build-backend = "setuptools.build_meta" +requires = ["scikit-build-core>=0.12.2"] +build-backend = "scikit_build_core.build" [project] name = "omegah2csg" @@ -9,7 +9,7 @@ authors = [ ] description = "Convert Omega_h mesh files generated using TOMMS for TOKAMAKs to CSG format for OpenMC and Degas2." readme = "README.md" -version = "0.0.1" +dynamic = ["version"] requires-python = ">=3.11" classifiers = [ "Intended Audience :: Developers", @@ -39,21 +39,38 @@ test = [ Homepage = "https://github.com/Fuad-HH/readOH2csg" Issues = "https://github.com/Fuad-HH/readOH2csg/issues" -[tool.setuptools.packages.find] -where = ["pythonAPI"] -include = ["omegah2csg*"] -exclude = ["tests*"] -namespaces = true - - -[tool.setuptools.package-data] -"omegah2csg" = ["*.txt", "*.dat", "*.osh", "*.so"] - [project.scripts] convert2openmc = "omegah2csg.convert2openmc.convert2openmc_cli:app" convert2degas2 = "omegah2csg.convert2degas2.convert2degas2_cli:app" +[tool.scikit-build] +minimum-version = "build-system.requires" +wheel.packages = ["pythonAPI/omegah2csg"] +metadata.version.provider = "scikit_build_core.metadata.regex" +metadata.version.input = "CMakeLists.txt" +metadata.version.regex = "project\\([^)]*?\\bVERSION\\s+(?P[0-9]+\\.[0-9]+\\.[0-9]+)[^)]*\\)" +sdist.include = [ + "CMakeLists.txt", + "cmake/**", + "src/**", + "pythonAPI/**", + "tests/**", + "README.md", + "LICENSE", + "pyproject.toml", + "pytest.toml", +] + +[tool.scikit-build.cmake.define] +CMAKE_PREFIX_PATH = { env = "CMAKE_PREFIX_PATH", default = "" } +Omega_h_ROOT = { env = "OMEGA_H_ROOT", default = "" } +Kokkos_ROOT = { env = "KOKKOS_ROOT", default = "" } +CMAKE_C_COMPILER = { env = "CC", default = "" } +CMAKE_CXX_COMPILER = { env = "CXX", default = "" } +CMAKE_BUILD_TYPE = { env = "CMAKE_BUILD_TYPE", default = "RelWithDebInfo" } + + [tool.ruff] include = ["pyproject.toml", "pythonAPI/omegah2csg/**/*.py", "tests/**/*.py"] line-length = 88 diff --git a/pythonAPI/omegah2csg/__init__.py b/pythonAPI/omegah2csg/__init__.py index 0d1d32c..57ec7cd 100644 --- a/pythonAPI/omegah2csg/__init__.py +++ b/pythonAPI/omegah2csg/__init__.py @@ -1,3 +1,7 @@ +from importlib import metadata +from pathlib import Path +import re + from .config import KokkosRuntime, kokkos_runtime from .OmegaHMesh import ( OmegaHMesh, @@ -9,6 +13,29 @@ ) from .convert2degas2 import convert2degas2 + +def _read_cmake_version() -> str | None: + cmake_path = Path(__file__).resolve().parents[2] / "CMakeLists.txt" + if not cmake_path.is_file(): + return None + content = cmake_path.read_text(encoding="utf-8") + match = re.search( + r"project\([^)]*?\bVERSION\s+([0-9]+\.[0-9]+\.[0-9]+)[^)]*\)", + content, + re.DOTALL, + ) + return match.group(1) if match else None + + +def _get_version() -> str: + try: + return metadata.version("omegah2csg") + except metadata.PackageNotFoundError: + return _read_cmake_version() or "0.0.0" + + +__version__ = _get_version() + __all__ = [ # runtime "KokkosRuntime", @@ -21,4 +48,6 @@ "convert2openmcXML", # degas2 "convert2degas2", + # version + "__version__", ] diff --git a/pythonAPI/omegah2csg/convert2degas2/convert2degas2_cli.py b/pythonAPI/omegah2csg/convert2degas2/convert2degas2_cli.py index f92b611..e28d64c 100644 --- a/pythonAPI/omegah2csg/convert2degas2/convert2degas2_cli.py +++ b/pythonAPI/omegah2csg/convert2degas2/convert2degas2_cli.py @@ -1,5 +1,6 @@ import argparse +from .. import __version__ from .convert2degas2 import convert2degas2 @@ -7,6 +8,11 @@ def main(): parser = argparse.ArgumentParser( description="Convert Omega_h mesh to DEGAS2 geometry (netcdf file)" ) + parser.add_argument( + "--version", + action="version", + version=f"%(prog)s {__version__}", + ) parser.add_argument("filename", help="Omega_h mesh file (.osh)") parser.add_argument("--tol", type=float, help="tolerance", default=1e-10) parser.add_argument( diff --git a/pythonAPI/omegah2csg/convert2openmc/convert2openmc_cli.py b/pythonAPI/omegah2csg/convert2openmc/convert2openmc_cli.py index 59e9dca..ba80553 100644 --- a/pythonAPI/omegah2csg/convert2openmc/convert2openmc_cli.py +++ b/pythonAPI/omegah2csg/convert2openmc/convert2openmc_cli.py @@ -1,5 +1,6 @@ import argparse +from .. import __version__ from .convert2openmc import convert2openmcXML @@ -7,6 +8,11 @@ def main(): parser = argparse.ArgumentParser( description="Convert Omega_h mesh to OpenMC geometry" ) + parser.add_argument( + "--version", + action="version", + version=f"%(prog)s {__version__}", + ) parser.add_argument("filename", help="Omega_h mesh file (.osh)") parser.add_argument("--tol", type=float, help="tolerance", default=1e-10) args = parser.parse_args() diff --git a/tests/assets/simple_degas2_case/README.md b/tests/assets/simple_degas2_case/README.md index e7c83db..b438a6a 100644 --- a/tests/assets/simple_degas2_case/README.md +++ b/tests/assets/simple_degas2_case/README.md @@ -4,13 +4,13 @@ This case was created to verify `convert2degas2` tool and related functions. At first, we were trying to directly convert the LTX mesh, but it was difficult to track down each of the arrays produced by both the original `definegeometry2d` and our tool `convert2degas2`. To make a case that we could manually track -and understand what each arrays actually mean in `geometry.nc` files. +and understand what each array actually mean in `geometry.nc` files. We started with a simple box geometry, but `definegeometry2d` was not working with it (I suspect it was due to the limitation of the wall walking algorithm) and it was too simple to have all kinds of behaviors. Then we created a relatively complex geometry with `GMSH`'s Python API by hand and got -[minDG2mesh.msh](./original-mesh-files/minDG2Mesh.msh). +[minDG2Mesh.msh](./original-mesh-files/minDG2Mesh.msh). ![The original mesh we started with](images/original-mesh.png) @@ -55,7 +55,7 @@ index dc4e0ae..03ec9a0 100644 ``` This dumped all the triangles after the geometry is complete in `definegeometry2d`. Based on this, -we extracted the triangles and nodes using the scripts in [`nc2mesh.ipynb`](https://github.com/Fuad-HH/Degas2-Geom-Debug/blob/main/nc2mesh.ipynb). From this `maplotlib.tri` object, we created +we extracted the triangles and nodes using the scripts in [`nc2mesh.ipynb`](https://github.com/Fuad-HH/Degas2-Geom-Debug/blob/main/nc2mesh.ipynb). From this `matplotlib.tri` object, we created the `.msh` and `.osh` files. After that, we had to modify the `.osh` file to add the `isOnWall` and `offset_face` tags (used [this program](https://github.com/Fuad-HH/Degas2-Geom-Debug/blob/main/src/set_tags.cpp)). Finally, the [`tagged-dg2mesh.osh`](./tagged-dg2mesh.osh) file ready to be used in `convert2degas2`. A standard one is stored for test cases as [gold-geometry.nc](./gold-geometry.nc). @@ -68,6 +68,6 @@ installation and related input files. An example case is given in [`degas2-case` With this case, we found that the outermost boundary created by `definegeometry2d` is always a rectangle and each edge can only have a single triangle adjacent to it. To connect with one layer of elements for concave geometry, `definegeometry2d` fills the concave part with triangles and then creates a bounding rectangle around the geometry. -![definegeometry2d created mesh for a concave geoemtry](images/recBoundary-dg2d.png) +![definegeometry2d created mesh for a concave geometry](images/recBoundary-dg2d.png) *The sideways U shape is the original geometry, then a wall layer is created around it (using degas2 python script `setup_xgc_case`), and then the bounding rectangle is created around it (using `definegeometry2d`).* diff --git a/tests/assets/simple_degas2_case/degas2-case/README.md b/tests/assets/simple_degas2_case/degas2-case/README.md index 807ece3..c3ab08f 100644 --- a/tests/assets/simple_degas2_case/degas2-case/README.md +++ b/tests/assets/simple_degas2_case/degas2-case/README.md @@ -2,7 +2,7 @@ 1. First, install [`degas2`](https://github.com/gjwilkie/degas2) with its related dependencies. -Degas2 has a extensive list of dependencies and can be difficult to install. I have this following [`Spack`](https://spack.io/) configuration file that can be used to install `degas2` dependencies. +Degas2 has an extensive list of dependencies and can be difficult to install. I have this following [`Spack`](https://spack.io/) configuration file that can be used to install `degas2` dependencies. ```yaml spack: