From c019d65a8577f1dd4b224ec0ad3df0d7d3ee5e2c Mon Sep 17 00:00:00 2001 From: Lester Hedges Date: Thu, 5 Feb 2026 13:19:41 +0000 Subject: [PATCH 01/12] Switch to pixi and rattler build. --- .github/workflows/devel.yaml | 59 +++++++++--------- .github/workflows/main.yaml | 58 ++++++++++-------- .github/workflows/pr.yaml | 56 ++++++++--------- README.md | 42 ++++++++++--- actions/update_recipe.py | 90 +++++++++++++++------------- actions/upload_package.py | 60 +++++++++---------- environment.yaml | 11 ---- pixi.toml | 23 +++++++ recipes/loch/conda_build_config.yaml | 3 - recipes/loch/recipe.yaml | 41 +++++++++++++ recipes/loch/template.yaml | 77 ------------------------ 11 files changed, 269 insertions(+), 251 deletions(-) delete mode 100644 environment.yaml create mode 100644 pixi.toml delete mode 100644 recipes/loch/conda_build_config.yaml create mode 100644 recipes/loch/recipe.yaml delete mode 100644 recipes/loch/template.yaml diff --git a/.github/workflows/devel.yaml b/.github/workflows/devel.yaml index efffc43..7aea1fe 100644 --- a/.github/workflows/devel.yaml +++ b/.github/workflows/devel.yaml @@ -3,7 +3,7 @@ name: Release Devel on: workflow_dispatch: push: - branches: [ devel ] + branches: [devel] jobs: build: @@ -18,11 +18,9 @@ jobs: - { name: "linux", os: "ubuntu-latest", shell: "bash -l {0}" } - { name: "macos", os: "macos-latest", shell: "bash -l {0}" } exclude: - # Exclude all but the latest Python from all - # but Linux - platform: { name: "macos", os: "macos-latest", shell: "bash -l {0}" } - python-version: "3.12" # MacOS can't run 3.12 yet... We want 3.10 and 3.11 + python-version: "3.12" # MacOS can't run 3.12 yet... We want 3.10 and 3.11 environment: name: loch-build defaults: @@ -32,30 +30,37 @@ jobs: SIRE_DONT_PHONEHOME: 1 SIRE_SILENT_PHONEHOME: 1 steps: - - uses: conda-incubator/setup-miniconda@v3 + # + - uses: actions/checkout@v4 with: - auto-update-conda: true - python-version: ${{ matrix.python-version }} - activate-environment: loch_build - miniforge-version: latest -# - - name: Clone the devel branch - run: git clone -b devel https://github.com/openbiosim/loch -# - - name: Setup Conda - run: conda install -y -c conda-forge boa anaconda-client packaging -# - - name: Update Conda recipe - run: python ${{ github.workspace }}/loch/actions/update_recipe.py -# - - name: Prepare build location - run: mkdir ${{ github.workspace }}/build -# - - name: Build Conda package using conda build - run: conda build --no-test -c conda-forge -c openbiosim/label/dev ${{ github.workspace }}/loch/recipes/loch -# - - name: Upload Conda package - run: python ${{ github.workspace }}/loch/actions/upload_package.py + fetch-depth: 0 + # + - name: Compute version info + run: python ${{ github.workspace }}/actions/update_recipe.py + # + - name: Create sdist + run: pip install build && python -m build --sdist && mv dist/*.tar.gz loch-source.tar.gz + working-directory: ${{ github.workspace }} + # + - name: Install rattler-build + uses: prefix-dev/rattler-build-action@v0.2.34 + with: + tool-version: latest + build-args: --help + # + - name: Write Python variant config + shell: bash + run: printf 'python:\n - "${{ matrix.python-version }}"\n' > "${{ github.workspace }}/python_variant.yaml" + # + - name: Build package using rattler-build + shell: bash + run: rattler-build build --recipe "${{ github.workspace }}/recipes/loch" -c conda-forge -c openbiosim/label/dev --variant-config "${{ github.workspace }}/python_variant.yaml" + # + - name: Install anaconda-client + run: python -m pip install anaconda-client + # + - name: Upload package + run: python ${{ github.workspace }}/actions/upload_package.py env: ANACONDA_TOKEN: ${{ secrets.ANACONDA_TOKEN }} ANACONDA_LABEL: dev diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index efc7634..ab57b66 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -23,7 +23,7 @@ jobs: exclude: - platform: { name: "macos", os: "macos-latest", shell: "bash -l {0}" } - python-version: "3.12" # MacOS can't run 3.12 yet... We want 3.10 and 3.11 + python-version: "3.12" # MacOS can't run 3.12 yet... We want 3.10 and 3.11 environment: name: loch-build defaults: @@ -33,30 +33,40 @@ jobs: SIRE_DONT_PHONEHOME: 1 SIRE_SILENT_PHONEHOME: 1 steps: - - uses: conda-incubator/setup-miniconda@v3 + # + - uses: actions/checkout@v4 with: - auto-update-conda: true - python-version: ${{ matrix.python-version }} - activate-environment: loch_build - miniforge-version: latest -# - - name: Clone the main branch - run: git clone -b main https://github.com/openbiosim/loch -# - - name: Setup Conda - run: conda install -y -c conda-forge boa anaconda-client packaging -# - - name: Update Conda recipe - run: python ${{ github.workspace }}/loch/actions/update_recipe.py -# - - name: Prepare build location - run: mkdir ${{ github.workspace }}/build -# - - name: Build Conda package using conda build - run: conda build --no-test -c conda-forge -c openbiosim/label/main ${{ github.workspace }}/loch/recipes/loch -# - - name: Upload Conda package - run: python ${{ github.workspace }}/loch/actions/upload_package.py + ref: main + fetch-depth: 0 + # + - name: Compute version info + run: python ${{ github.workspace }}/actions/update_recipe.py + # + - name: Create sdist + run: pip install build && python -m build --sdist && mv dist/*.tar.gz loch-source.tar.gz + working-directory: ${{ github.workspace }} + # + - name: Install rattler-build + uses: prefix-dev/rattler-build-action@v0.2.34 + with: + tool-version: latest + build-args: --help + # + - name: Write Python variant config + shell: bash + run: printf 'python:\n - "${{ matrix.python-version }}"\n' > "${{ github.workspace }}/python_variant.yaml" + # + - name: Build package using rattler-build + shell: bash + run: rattler-build build --recipe "${{ github.workspace }}/recipes/loch" -c conda-forge -c openbiosim/label/main --variant-config "${{ github.workspace }}/python_variant.yaml" + # + - name: Install anaconda-client + run: python -m pip install anaconda-client + if: github.event.inputs.upload_packages == 'true' + # + - name: Upload package + run: python ${{ github.workspace }}/actions/upload_package.py env: ANACONDA_TOKEN: ${{ secrets.ANACONDA_TOKEN }} ANACONDA_LABEL: main + if: github.event.inputs.upload_packages == 'true' diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index c6d7b35..0df1896 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -17,14 +17,12 @@ jobs: - { name: "linux", os: "ubuntu-latest", shell: "bash -l {0}" } - { name: "macos", os: "macos-latest", shell: "bash -l {0}" } exclude: - # Exclude all but the latest Python from all - # but Linux - platform: { name: "macos", os: "macos-latest", shell: "bash -l {0}" } python-version: "3.10" - platform: { name: "macos", os: "macos-latest", shell: "bash -l {0}" } - python-version: "3.12" # MacOS can't run 3.12 yet... + python-version: "3.12" # MacOS can't run 3.12 yet... environment: name: loch-build defaults: @@ -33,31 +31,35 @@ jobs: env: SIRE_DONT_PHONEHOME: 1 SIRE_SILENT_PHONEHOME: 1 - REPO: "${{ github.event.pull_request.head.repo.full_name || github.repository }}" steps: - - uses: conda-incubator/setup-miniconda@v3 + # + - uses: actions/checkout@v4 with: - auto-update-conda: true - python-version: ${{ matrix.python-version }} - activate-environment: loch_build - miniforge-version: latest -# - - name: Clone the feature branch - run: git clone -b ${{ github.head_ref }} --single-branch https://github.com/${{ env.REPO }} loch -# - - name: Setup Conda - run: conda install -y -c conda-forge boa anaconda-client packaging -# - - name: Update Conda recipe - run: python ${{ github.workspace }}/loch/actions/update_recipe.py -# - - name: Prepare build location - run: mkdir ${{ github.workspace }}/build -# - - name: Build Conda package using conda build using main channel + fetch-depth: 0 + # + - name: Compute version info + run: python ${{ github.workspace }}/actions/update_recipe.py + # + - name: Create sdist + run: pip install build && python -m build --sdist && mv dist/*.tar.gz loch-source.tar.gz + working-directory: ${{ github.workspace }} + # + - name: Install rattler-build + uses: prefix-dev/rattler-build-action@v0.2.34 + with: + tool-version: latest + build-args: --help + # + - name: Write Python variant config + shell: bash + run: printf 'python:\n - "${{ matrix.python-version }}"\n' > "${{ github.workspace }}/python_variant.yaml" + # + - name: Build package using rattler-build (main channel) if: ${{ github.base_ref == 'main' }} - run: conda build --no-test -c conda-forge -c openbiosim/label/main ${{ github.workspace }}/loch/recipes/loch -# - - name: Build Conda package using conda build using dev channel + shell: bash + run: rattler-build build --recipe "${{ github.workspace }}/recipes/loch" -c conda-forge -c openbiosim/label/main --variant-config "${{ github.workspace }}/python_variant.yaml" + # + - name: Build package using rattler-build (dev channel) if: ${{ github.base_ref != 'main' }} - run: conda build --no-test -c conda-forge -c openbiosim/label/dev ${{ github.workspace }}/loch/recipes/loch + shell: bash + run: rattler-build build --recipe "${{ github.workspace }}/recipes/loch" -c conda-forge -c openbiosim/label/dev --variant-config "${{ github.workspace }}/python_variant.yaml" diff --git a/README.md b/README.md index 6fc0064..6eb965e 100644 --- a/README.md +++ b/README.md @@ -19,31 +19,57 @@ and [PyOpenCL](https://documen.tician.de/pyopencl/). ## Installation -First, create a conda environment with the required dependencies: +### Conda package + +Install `loch` directly from the `openbiosim` channel: + +``` +conda install -c conda-forge -c openbiosim loch +``` + +Or, for the development version: ``` -conda env create -f environment.yaml -conda activate loch +conda install -c conda-forge -c openbiosim/label/dev loch ``` -Next, clone the repository and install the package: +### Installing from source (standalone) + +To install from source using [pixi](https://pixi.sh), which will +automatically create an environment with all required dependencies +(including pre-built [Sire](https://github.com/OpenBioSim/sire) and +[BioSimSpace](https://github.com/OpenBioSim/biosimspace)): ``` git clone https://github.com/openbiosim/loch cd loch +pixi install +pixi shell pip install -e . ``` -Alternatively, to install `loch` into an existing conda environment: +### Installing from source (full OpenBioSim development) + +If you are developing across the full OpenBioSim stack, first install +[Sire](https://github.com/OpenBioSim/sire) from source by following the +instructions [here](https://github.com/OpenBioSim/sire#installation), then +activate its pixi environment: ``` -conda install -c conda-forge openbiosim loch +pixi shell --manifest-path /path/to/sire/pixi.toml -e dev ``` -Or, for the development version: +You may also need to install other packages from source, e.g. +[BioSimSpace](https://github.com/OpenBioSim/biosimspace): ``` -conda install -c conda-forge -c openbiosim/label/dev loch +pip install -e /path/to/biosimspace/python +``` + +Then install `loch` into the environment: + +``` +pip install -e . ``` ## How does it work? diff --git a/actions/update_recipe.py b/actions/update_recipe.py index 9586933..8d82bcb 100644 --- a/actions/update_recipe.py +++ b/actions/update_recipe.py @@ -1,50 +1,58 @@ -import sys +"""Compute git version info for rattler-build. + +This script computes GIT_DESCRIBE_TAG and GIT_DESCRIBE_NUMBER from the +git history and outputs them in GitHub Actions format for setting +environment variables. + +It also writes a _version.py file so that versioningit has a fallback +when .git is not available (e.g., when rattler-build excludes it). +""" + import os import subprocess +import sys -# Get the name of the script. script = os.path.abspath(sys.argv[0]) - -# we want to import the 'get_requirements' package from this directory -sys.path.insert(0, os.path.dirname(script)) - -# go up one directories to get the source directory -# (this script is in BioSimSpace/actions/) srcdir = os.path.dirname(os.path.dirname(script)) - -condadir = os.path.join(srcdir, "recipes", "loch") - -print(f"conda recipe in {condadir}") - -# Store the name of the recipe and template YAML files. -recipe = os.path.join(condadir, "meta.yaml") -template = os.path.join(condadir, "template.yaml") - gitdir = os.path.join(srcdir, ".git") def run_cmd(cmd): - p = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) - return str(p.stdout.read().decode("utf-8")).lstrip().rstrip() - - -# Get the remote. -remote = run_cmd( - f"git --git-dir={gitdir} --work-tree={srcdir} config --get remote.origin.url" -) -print(remote) - -# Get the branch. -branch = run_cmd( - f"git --git-dir={gitdir} --work-tree={srcdir} rev-parse --abbrev-ref HEAD" -) -print(branch) - -lines = open(template, "r").readlines() - -with open(recipe, "w") as FILE: - for line in lines: - line = line.replace("LOCH_REMOTE", remote) - line = line.replace("LOCH_BRANCH", branch) - - FILE.write(line) + p = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, _ = p.communicate() + return stdout.decode("utf-8").strip() + + +# Get the full git describe output (e.g., "2024.1.0-5-gabcdef" or "2024.1.0") +describe = run_cmd(f"git --git-dir={gitdir} --work-tree={srcdir} describe --tags") + +if "-" in describe: + # Format: tag-number-hash (e.g., "2024.1.0-5-gabcdef") + parts = describe.rsplit("-", 2) + tag = parts[0] + number = parts[1] + rev = parts[2] # e.g., "gabcdef" + version = f"{tag}+{number}.{rev}" +else: + # Exactly on a tag + tag = describe + number = "0" + version = tag + +print(f"GIT_DESCRIBE_TAG={tag}") +print(f"GIT_DESCRIBE_NUMBER={number}") +print(f"Version={version}") + +# Write to GITHUB_ENV if running in GitHub Actions +github_env = os.environ.get("GITHUB_ENV") +if github_env: + with open(github_env, "a") as f: + f.write(f"GIT_DESCRIBE_TAG={tag}\n") + f.write(f"GIT_DESCRIBE_NUMBER={number}\n") + print("Exported to GITHUB_ENV") + +# Write _version.py for versioningit fallback +version_file = os.path.join(srcdir, "src", "loch", "_version.py") +with open(version_file, "w") as f: + f.write(f'__version__ = "{version}"\n') +print(f"Wrote {version_file}") diff --git a/actions/upload_package.py b/actions/upload_package.py index 26c1299..96984fa 100644 --- a/actions/upload_package.py +++ b/actions/upload_package.py @@ -1,16 +1,18 @@ +"""Upload built packages to the openbiosim Anaconda Cloud channel.""" + import os import sys import glob +import subprocess script = os.path.abspath(sys.argv[0]) -# go up one directories to get the source directory -# (this script is in loch/actions/) +# Go up one directory to get the source directory. srcdir = os.path.dirname(os.path.dirname(script)) print(f"Loch source is in {srcdir}\n") -# Get the anaconda token to authorise uploads +# Get the anaconda token to authorise uploads. if "ANACONDA_TOKEN" in os.environ: conda_token = os.environ["ANACONDA_TOKEN"] else: @@ -22,42 +24,30 @@ else: conda_label = "dev" -# get the root conda directory -conda = os.environ["CONDA"] - -# Set the path to the conda-bld directory. -conda_bld = os.path.join(conda, "envs", "loch_build", "conda-bld") - -print(f"conda_bld = {conda_bld}") +# Search for rattler-build output first. +packages = glob.glob(os.path.join("output", "**", "*.conda"), recursive=True) -# Find the packages to upload -loch_pkg = glob.glob(os.path.join(conda_bld, "*-*", "loch-*.tar.bz2")) +# Fall back to conda-bld output. +if not packages: + if "CONDA" in os.environ: + conda = os.environ["CONDA"] + conda_bld = os.path.join(conda, "envs", "loch_build", "conda-bld") + packages = glob.glob( + os.path.join(conda_bld, "**", "loch-*.tar.bz2"), recursive=True + ) -if len(loch_pkg) == 0: +if not packages: print("No loch packages to upload?") sys.exit(-1) -packages = loch_pkg - -print(f"Uploading packages:") -print(" * ", "\n * ".join(packages)) - -packages = " ".join(packages) - - -def run_cmd(cmd): - import subprocess - - p = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) - return str(p.stdout.read().decode("utf-8")).lstrip().rstrip() +print("Uploading packages:") +for pkg in packages: + print(f" * {pkg}") - -gitdir = os.path.join(srcdir, ".git") - -tag = run_cmd(f"git --git-dir={gitdir} --work-tree={srcdir} tag --contains") +packages_str = " ".join(packages) # Upload the packages to the openbiosim channel on Anaconda Cloud. -cmd = f"anaconda --token {conda_token} upload --user openbiosim --label {conda_label} --force {packages}" +cmd = f"anaconda --token {conda_token} upload --user openbiosim --label {conda_label} --force {packages_str}" print(f"\nUpload command:\n\n{cmd}\n") @@ -65,8 +55,12 @@ def run_cmd(cmd): print("Not uploading as the ANACONDA_TOKEN is not set!") sys.exit(-1) -output = run_cmd(cmd) -print(output) +def run_cmd(cmd): + p = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) + return str(p.stdout.read().decode("utf-8")).lstrip().rstrip() + +output = run_cmd(cmd) +print(output) print("Package uploaded!") diff --git a/environment.yaml b/environment.yaml deleted file mode 100644 index be4b621..0000000 --- a/environment.yaml +++ /dev/null @@ -1,11 +0,0 @@ -name: loch - -channels: - - conda-forge - - openbiosim/label/dev - -dependencies: - - biosimspace - - loguru - - pycuda - - pyopencl diff --git a/pixi.toml b/pixi.toml new file mode 100644 index 0000000..993d157 --- /dev/null +++ b/pixi.toml @@ -0,0 +1,23 @@ +[workspace] +name = "loch" +channels = ["conda-forge", "openbiosim/label/dev"] +# No Windows - pycuda/pyopencl only available on linux-64 +platforms = ["linux-64", "osx-arm64"] + +[dependencies] +python = ">=3.10" +biosimspace = "*" +loguru = "*" +pyopencl = "*" + +[target.linux-64.dependencies] +pycuda = "*" + +[feature.test.dependencies] +pytest = "*" +black = "*" + +[environments] +default = [] +test = ["test"] +dev = ["test"] diff --git a/recipes/loch/conda_build_config.yaml b/recipes/loch/conda_build_config.yaml deleted file mode 100644 index 3e8e203..0000000 --- a/recipes/loch/conda_build_config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -pin_run_as_build: - sire: - max_pin: x.x diff --git a/recipes/loch/recipe.yaml b/recipes/loch/recipe.yaml new file mode 100644 index 0000000..0ccd676 --- /dev/null +++ b/recipes/loch/recipe.yaml @@ -0,0 +1,41 @@ +context: + name: loch + +package: + name: ${{ name }} + version: ${{ env.get('GIT_DESCRIBE_TAG', default='PR') }} + +source: + path: ../../loch-source.tar.gz + +build: + number: ${{ env.get('GIT_DESCRIBE_NUMBER', default='0') }} + script: python -m pip install . --no-deps --ignore-installed -vv + +requirements: + host: + - pip + - python + - setuptools + - versioningit + run: + - biosimspace + - loguru + - numpy <2.3 # Remove when nglview >=4.1 is released + - pyopencl + - python + - if: not osx + then: + - pycuda + +about: + homepage: https://github.com/openbiosim/loch + license: GPL-3.0-or-later + license_file: LICENSE + summary: "CUDA accelerated Grand Canonical Monte Carlo (GCMC) water sampling code." + repository: https://github.com/openbiosim/loch + documentation: https://github.com/openbiosim/loch + +extra: + recipe-maintainers: + - lohedges diff --git a/recipes/loch/template.yaml b/recipes/loch/template.yaml deleted file mode 100644 index 39320ee..0000000 --- a/recipes/loch/template.yaml +++ /dev/null @@ -1,77 +0,0 @@ -{% set name = "loch" %} - -package: - name: {{ name }} - version: {{ environ.get('GIT_DESCRIBE_TAG', 'PR') }} - -source: - git_url: LOCH_REMOTE - git_tag: LOCH_BRANCH - -build: - number: {{ environ.get('GIT_DESCRIBE_NUMBER', 0) }} - script: {{ PYTHON }} -m pip install . --no-deps --ignore-installed -vv - -requirements: - host: - - biosimspace - - loguru - - pip - - pycuda # [not osx] - - pyopencl - - python - - setuptools - - sire - - versioningit - run: - - biosimspace - - loguru - - pycuda # [not osx] - - pyopencl - - python - - sire - -test: - script_env: - - SIRE_DONT_PHONEHOME - - SIRE_SILENT_PHONEHOME - requires: - - black == 25 # [linux and x86_64 and py==311] - - pytest - - pytest-black # [linux and x86_64 and py==311] - imports: - - loch - source_files: - - src/loch - - tests - commands: - - pytest -vvv --color=yes --black src/loch # [linux and x86_64 and py==311] - - pytest -vvv --color=yes --import-mode=importlib tests - -about: - home: https://github.com/openbiosim/loch - license: GPL-3.0-or-later - license_file: '{{ environ["RECIPE_DIR"] }}/LICENSE' - summary: "CUDA accelerated Grand Canonical Monte Carlo (GCMC) water sampling code." - dev_url: https://github.com/openbiosim/loch - doc_url: https://github.com/openbiosim/loch - description: | - CUDA accelerated Grand Canonical Monte Carlo (GCMC) water sampling code. - Built on top of Sire, BioSimSpace, and OpenMM. - - To install: - - `conda install -c conda-forge -c openbiosim loch` - - To install the development version: - - `conda install -c conda-forge -c openbiosim/label/dev loch` - - When updating the development version it is generally advised to - update Sire at the same time: - - `conda install -c conda-forge -c openbiosim/label/dev loch sire` - -extra: - recipe-maintainers: - - lohedges From 938596e47b2434e61d5ff7836876295a76d4e7ed Mon Sep 17 00:00:00 2001 From: Lester Hedges Date: Fri, 6 Feb 2026 14:11:07 +0000 Subject: [PATCH 02/12] Add auto-generated version file to gitignore. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index ec886b6..62feb86 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ output.yaml # Conda recipe (it is auto-generated) recipes/loch/meta.yaml + +# Auto-generated version file. +src/loch/_version.py From dd328930552d6ef075d6446d597e21a24ed45fb5 Mon Sep 17 00:00:00 2001 From: Lester Hedges Date: Mon, 9 Feb 2026 09:13:43 +0000 Subject: [PATCH 03/12] Fix install path for BioSimSpace. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6eb965e..050cad0 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ You may also need to install other packages from source, e.g. [BioSimSpace](https://github.com/OpenBioSim/biosimspace): ``` -pip install -e /path/to/biosimspace/python +pip install -e /path/to/biosimspace ``` Then install `loch` into the environment: @@ -97,7 +97,7 @@ import sire as sr mols = sr.load_test_files("bpti.prm7", "bpti.rst7") ``` -2) Create a `GCMCSampler`: +1) Create a `GCMCSampler`: ```python from loch import GCMCSampler @@ -131,7 +131,7 @@ The GPU platform is controlled via the `platform` argument, which can be set to attempt to use the CUDA platform first, falling back to OpenCL if CUDA is not available. -3) Get the GCMC system: +1) Get the GCMC system: In order to perform a simulation we need to get back the GCMC system, which contains an additional `num_ghost_waters` number of ghost water molecules @@ -141,7 +141,7 @@ that are used for insertion moves. gcmc_system = sampler.system() ``` -4) Create an OpenMM context: +1) Create an OpenMM context: We can directly use the Sire dynamics interface to create an OpenMM context for us, e.g.: @@ -177,7 +177,7 @@ a crash: sampler.bind_dynamics(d) ``` -5) Run dynamics with GCMC sampling: +1) Run dynamics with GCMC sampling: ```python # Set the cycle frequency for saving ghost residue indices. From 0847c146e593184fc29827e7ba63c137b9e0f1d6 Mon Sep 17 00:00:00 2001 From: Lester Hedges Date: Mon, 9 Feb 2026 09:43:50 +0000 Subject: [PATCH 04/12] Remove numpy pin. --- recipes/loch/recipe.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/recipes/loch/recipe.yaml b/recipes/loch/recipe.yaml index 0ccd676..96172ee 100644 --- a/recipes/loch/recipe.yaml +++ b/recipes/loch/recipe.yaml @@ -21,7 +21,6 @@ requirements: run: - biosimspace - loguru - - numpy <2.3 # Remove when nglview >=4.1 is released - pyopencl - python - if: not osx From 70d19a8282a9534291f9d5cc739fbddee73d391c Mon Sep 17 00:00:00 2001 From: Lester Hedges Date: Mon, 9 Feb 2026 12:57:22 +0000 Subject: [PATCH 05/12] Fix shell quoting. --- .github/workflows/devel.yaml | 7 +++++-- .github/workflows/main.yaml | 7 +++++-- .github/workflows/pr.yaml | 3 ++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/devel.yaml b/.github/workflows/devel.yaml index 7aea1fe..d7eb4dc 100644 --- a/.github/workflows/devel.yaml +++ b/.github/workflows/devel.yaml @@ -36,7 +36,8 @@ jobs: fetch-depth: 0 # - name: Compute version info - run: python ${{ github.workspace }}/actions/update_recipe.py + shell: bash + run: python "${{ github.workspace }}/actions/update_recipe.py" # - name: Create sdist run: pip install build && python -m build --sdist && mv dist/*.tar.gz loch-source.tar.gz @@ -57,10 +58,12 @@ jobs: run: rattler-build build --recipe "${{ github.workspace }}/recipes/loch" -c conda-forge -c openbiosim/label/dev --variant-config "${{ github.workspace }}/python_variant.yaml" # - name: Install anaconda-client + shell: bash run: python -m pip install anaconda-client # - name: Upload package - run: python ${{ github.workspace }}/actions/upload_package.py + shell: bash + run: python "${{ github.workspace }}/actions/upload_package.py" env: ANACONDA_TOKEN: ${{ secrets.ANACONDA_TOKEN }} ANACONDA_LABEL: dev diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index ab57b66..26aaa54 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -40,7 +40,8 @@ jobs: fetch-depth: 0 # - name: Compute version info - run: python ${{ github.workspace }}/actions/update_recipe.py + shell: bash + run: python "${{ github.workspace }}/actions/update_recipe.py" # - name: Create sdist run: pip install build && python -m build --sdist && mv dist/*.tar.gz loch-source.tar.gz @@ -61,11 +62,13 @@ jobs: run: rattler-build build --recipe "${{ github.workspace }}/recipes/loch" -c conda-forge -c openbiosim/label/main --variant-config "${{ github.workspace }}/python_variant.yaml" # - name: Install anaconda-client + shell: bash run: python -m pip install anaconda-client if: github.event.inputs.upload_packages == 'true' # - name: Upload package - run: python ${{ github.workspace }}/actions/upload_package.py + shell: bash + run: python "${{ github.workspace }}/actions/upload_package.py" env: ANACONDA_TOKEN: ${{ secrets.ANACONDA_TOKEN }} ANACONDA_LABEL: main diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 0df1896..535698f 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -38,7 +38,8 @@ jobs: fetch-depth: 0 # - name: Compute version info - run: python ${{ github.workspace }}/actions/update_recipe.py + shell: bash + run: python "${{ github.workspace }}/actions/update_recipe.py" # - name: Create sdist run: pip install build && python -m build --sdist && mv dist/*.tar.gz loch-source.tar.gz From b2b30d7c66f70a1132e25cb9039b03e511de5444 Mon Sep 17 00:00:00 2001 From: Lester Hedges Date: Mon, 9 Feb 2026 13:04:19 +0000 Subject: [PATCH 06/12] Use relative paths to avoid path mangling. --- .github/workflows/devel.yaml | 4 ++-- .github/workflows/main.yaml | 4 ++-- .github/workflows/pr.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/devel.yaml b/.github/workflows/devel.yaml index d7eb4dc..7113f4f 100644 --- a/.github/workflows/devel.yaml +++ b/.github/workflows/devel.yaml @@ -37,7 +37,7 @@ jobs: # - name: Compute version info shell: bash - run: python "${{ github.workspace }}/actions/update_recipe.py" + run: python actions/update_recipe.py # - name: Create sdist run: pip install build && python -m build --sdist && mv dist/*.tar.gz loch-source.tar.gz @@ -63,7 +63,7 @@ jobs: # - name: Upload package shell: bash - run: python "${{ github.workspace }}/actions/upload_package.py" + run: python actions/upload_package.py env: ANACONDA_TOKEN: ${{ secrets.ANACONDA_TOKEN }} ANACONDA_LABEL: dev diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 26aaa54..6321b3a 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -41,7 +41,7 @@ jobs: # - name: Compute version info shell: bash - run: python "${{ github.workspace }}/actions/update_recipe.py" + run: python actions/update_recipe.py # - name: Create sdist run: pip install build && python -m build --sdist && mv dist/*.tar.gz loch-source.tar.gz @@ -68,7 +68,7 @@ jobs: # - name: Upload package shell: bash - run: python "${{ github.workspace }}/actions/upload_package.py" + run: python actions/upload_package.py env: ANACONDA_TOKEN: ${{ secrets.ANACONDA_TOKEN }} ANACONDA_LABEL: main diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 535698f..2597b3f 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -39,7 +39,7 @@ jobs: # - name: Compute version info shell: bash - run: python "${{ github.workspace }}/actions/update_recipe.py" + run: python actions/update_recipe.py # - name: Create sdist run: pip install build && python -m build --sdist && mv dist/*.tar.gz loch-source.tar.gz From 540f204220daaaac31578d3ea520275e531d1396 Mon Sep 17 00:00:00 2001 From: Lester Hedges Date: Wed, 11 Feb 2026 09:26:58 +0000 Subject: [PATCH 07/12] Add linting tools to dev environment. --- pixi.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pixi.toml b/pixi.toml index 993d157..611213e 100644 --- a/pixi.toml +++ b/pixi.toml @@ -17,7 +17,11 @@ pycuda = "*" pytest = "*" black = "*" +[feature.lint.dependencies] +pre-commit = "*" +ruff = "*" + [environments] default = [] test = ["test"] -dev = ["test"] +dev = ["test", "lint"] From 1269989d4ea6b5ab01986a335513816e251a65b2 Mon Sep 17 00:00:00 2001 From: Lester Hedges Date: Wed, 11 Feb 2026 11:00:21 +0000 Subject: [PATCH 08/12] Add pre-commit. --- .pre-commit-config.yaml | 23 +++++++++++++++++++++++ README.md | 18 ++++++++++++++++++ pyproject.toml | 6 ++++++ src/loch/_sampler.py | 1 - 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0043ca4 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,23 @@ +files: ^(src|tests)/ +exclude: ^tests/(input|output)/ + +repos: + # General file quality checks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-added-large-files + args: [--maxkb=1000] # Prevent files larger than 1MB + - id: check-merge-conflict + + # Python formatting and linting + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.4 + hooks: + # Run the formatter + - id: ruff-format + # Run the linter (optional - remove if too strict) + - id: ruff + args: [--fix, --exit-zero] # Auto-fix but don't block commits diff --git a/README.md b/README.md index 050cad0..ecac423 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,24 @@ Then install `loch` into the environment: pip install -e . ``` +## Development + +Pre-commit hooks are used to ensure consistent code formatting and linting. +To set up pre-commit in your development environment: + +``` +pixi shell -e dev +pre-commit install +``` + +This will run [ruff](https://docs.astral.sh/ruff/) formatting and linting +checks automatically on each commit. To run the checks manually against all +files: + +``` +pre-commit run --all-files +``` + ## How does it work? Instead of computing the energy change for each trial insertion/deletion with diff --git a/pyproject.toml b/pyproject.toml index bf6e2e6..4271342 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,3 +29,9 @@ distance-dirty = "{base_version}+{distance}.{vcs}{rev}.dirty" [tool.versioningit.write] file = "src/loch/_version.py" + +[tool.ruff.lint] +ignore = ["E402"] + +[tool.ruff.lint.per-file-ignores] +"tests/**" = ["F841"] diff --git a/src/loch/_sampler.py b/src/loch/_sampler.py index d17057c..ba36f95 100644 --- a/src/loch/_sampler.py +++ b/src/loch/_sampler.py @@ -2394,7 +2394,6 @@ def _set_water_state(self, context, indices=None, states=None, force=False): # Loop over the indices and states. for idx, state in zip(indices, states): - # Skip if the state is unchanged. if not force and self._water_state[idx] == state: continue From ad6474f1f80eaafbb4684b9315823b84d58b4b2d Mon Sep 17 00:00:00 2001 From: Lester Hedges Date: Wed, 11 Feb 2026 12:06:36 +0000 Subject: [PATCH 09/12] Update gitignore. --- .gitignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.gitignore b/.gitignore index 62feb86..e2611c2 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,6 @@ setup.err dist/ build/ loch.egg-info -src/loch/_version.py # Test output. output.yaml @@ -35,8 +34,5 @@ output.yaml # VSCode config .vscode/ -# Conda recipe (it is auto-generated) -recipes/loch/meta.yaml - # Auto-generated version file. src/loch/_version.py From daf479c4130bb09a13b38bfc79b825ea93518275 Mon Sep 17 00:00:00 2001 From: Lester Hedges Date: Wed, 11 Feb 2026 14:04:59 +0000 Subject: [PATCH 10/12] Add rattler-build to development environment. --- pixi.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pixi.toml b/pixi.toml index 611213e..fe72127 100644 --- a/pixi.toml +++ b/pixi.toml @@ -19,6 +19,7 @@ black = "*" [feature.lint.dependencies] pre-commit = "*" +rattler-build = "*" ruff = "*" [environments] From 5148e2dc13aa5c10a5e954a6de5af0785d6cb3c7 Mon Sep 17 00:00:00 2001 From: Lester Hedges Date: Wed, 11 Feb 2026 15:06:33 +0000 Subject: [PATCH 11/12] Use pixi install of rattler-build. --- .github/workflows/devel.yaml | 11 +++++++---- .github/workflows/main.yaml | 11 +++++++---- .github/workflows/pr.yaml | 11 +++++++---- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/workflows/devel.yaml b/.github/workflows/devel.yaml index 7113f4f..ee0200e 100644 --- a/.github/workflows/devel.yaml +++ b/.github/workflows/devel.yaml @@ -43,11 +43,14 @@ jobs: run: pip install build && python -m build --sdist && mv dist/*.tar.gz loch-source.tar.gz working-directory: ${{ github.workspace }} # - - name: Install rattler-build - uses: prefix-dev/rattler-build-action@v0.2.34 + - name: Install pixi + uses: prefix-dev/setup-pixi@v0.9.4 with: - tool-version: latest - build-args: --help + run-install: false + # + - name: Install rattler-build + shell: bash + run: pixi global install rattler-build # - name: Write Python variant config shell: bash diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 6321b3a..a80b5c2 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -47,11 +47,14 @@ jobs: run: pip install build && python -m build --sdist && mv dist/*.tar.gz loch-source.tar.gz working-directory: ${{ github.workspace }} # - - name: Install rattler-build - uses: prefix-dev/rattler-build-action@v0.2.34 + - name: Install pixi + uses: prefix-dev/setup-pixi@v0.9.4 with: - tool-version: latest - build-args: --help + run-install: false + # + - name: Install rattler-build + shell: bash + run: pixi global install rattler-build # - name: Write Python variant config shell: bash diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 2597b3f..54cef13 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -45,11 +45,14 @@ jobs: run: pip install build && python -m build --sdist && mv dist/*.tar.gz loch-source.tar.gz working-directory: ${{ github.workspace }} # - - name: Install rattler-build - uses: prefix-dev/rattler-build-action@v0.2.34 + - name: Install pixi + uses: prefix-dev/setup-pixi@v0.9.4 with: - tool-version: latest - build-args: --help + run-install: false + # + - name: Install rattler-build + shell: bash + run: pixi global install rattler-build # - name: Write Python variant config shell: bash From d303506a8e612cf1d130710d962c36bc4e9a1baf Mon Sep 17 00:00:00 2001 From: Lester Hedges Date: Thu, 12 Feb 2026 09:09:04 +0000 Subject: [PATCH 12/12] Remove redundant setup.py file. --- setup.py | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 setup.py diff --git a/setup.py b/setup.py deleted file mode 100644 index 6068493..0000000 --- a/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -from setuptools import setup - -setup()