Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
021f460
Add 2026.1.0 placeholder to CHANGELOG.
lohedges Feb 17, 2026
d54fa78
Update OpenCL instructions. [ci skip]
lohedges Mar 4, 2026
99a261d
Update pre-commit. [ci skip]
lohedges Mar 5, 2026
e5c1786
Allow GeneralUnit inputs.
lohedges Mar 9, 2026
4156f5b
Update logo. [ci skip]
lohedges Mar 13, 2026
90f0696
Standardise docstring formatting. [ci skip]
lohedges Mar 23, 2026
ff787c1
Fix CUDA context stack leak on crash by tracking outstanding push count.
lohedges Mar 24, 2026
480fffc
Merge pull request #15 from OpenBioSim/fix_14
lohedges Mar 24, 2026
065ef75
Add support for different alchemical softcore forms.
lohedges Mar 29, 2026
a6992fc
Optimise taylor_power branch.
lohedges Mar 29, 2026
81863dd
Merge pull request #16 from OpenBioSim/feature_softcore
lohedges Mar 29, 2026
aa87449
Clarify move acceptance probability. [ci skip]
lohedges Mar 31, 2026
537571d
Add methods to get and restore sampling statistics.
lohedges Apr 1, 2026
40098c3
Update CHANGELOG. [ci skip]
lohedges Apr 2, 2026
8151866
Handle XED force field virtual sites.
lohedges Apr 16, 2026
12f4e6b
Merge pull request #17 from OpenBioSim/feature_vsites
lohedges Apr 16, 2026
d90aae6
Fix vsite index offsets for OpenMM context vs Sire topology operations.
lohedges Apr 21, 2026
6cc74f0
Fix link. [ci skip]
lohedges Apr 21, 2026
0940cf3
Allow optional context to be passed to num_waters.
lohedges Apr 24, 2026
40f79b3
Add support for long-range LJ dispersion correction.
lohedges Apr 29, 2026
23bb1cd
Add Beutler soft-core form for ABFE.
lohedges Apr 30, 2026
c16f401
Optimise softcore.
lohedges May 1, 2026
474a24c
Optimise softcore.
lohedges May 1, 2026
0845bff
Recombine ghost nonbonded forces for performance.
lohedges May 6, 2026
12a8c94
Use Beutler LJ soft-core for decoupling and add support for LRC.
lohedges May 6, 2026
80636a3
Reverse lambda schedule when swap_end_states=True.
lohedges May 7, 2026
237c963
Merge branch 'devel' into feature_lrc.
lohedges May 7, 2026
fcd8e96
Update CHANGELOG.
lohedges May 7, 2026
65606fa
Merge pull request #18 from OpenBioSim/feature_lrc
lohedges May 7, 2026
87b7882
fix water template for dry system
z-gong May 25, 2026
b33a8be
Merge pull request #19 from z-gong/watertemplate
lohedges May 25, 2026
3f0160c
Update CHANGELOG. [ci skip]
lohedges May 25, 2026
59e3195
Fix softcore_form kwarg and use native SOMD2 decoupling schedule.
lohedges May 26, 2026
b57e903
Merge pull request #21 from OpenBioSim/fix_softcore_form
lohedges May 26, 2026
43349d2
Add support for the osmotic ensemble.
lohedges May 26, 2026
eda46b8
Merge pull request #22 from OpenBioSim/feature_osmotic_ensemble
lohedges May 26, 2026
2077da6
Fix non-uniform bulk GCMC insertion positions caused by wrong RNG dis…
lohedges May 28, 2026
3f2720e
Merge pull request #24 from OpenBioSim/fix_23
lohedges May 28, 2026
be2218a
Add method to update system with current water state and positions.
lohedges Jun 15, 2026
482a84d
Add method to return system without ghost waters.
lohedges Jun 15, 2026
ff7efee
Update space property.
lohedges Jun 15, 2026
b2d1022
Add unit test for finalise_system.
lohedges Jun 15, 2026
b3bbae2
Update CHANGELOG.
lohedges Jun 15, 2026
b137198
Merge pull request #26 from OpenBioSim/fix_25
lohedges Jun 15, 2026
bf51d8a
Merge branch 'main' into release_2026.1.0
lohedges Jun 29, 2026
3ebc5f1
Add BioSimSpace compatibility pin.
lohedges Jun 29, 2026
9fc660c
Update CHANGELOG for the 2026.1.0 release.
lohedges Jun 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .img/loch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ repos:

# Python formatting and linting
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.4
rev: v0.15.4
hooks:
# Run the formatter
- id: ruff-format
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
Changelog
=========

[2026.1.0](https://github.com/openbiosim/loch/compare/2025.2.0...2026.1.0) - Jun 2026
-------------------------------------------------------------------------------------

* Add support for getting and restoring sampling statistics.
* Handle XED force field virtual sites [#17](https://github.com/OpenBioSim/loch/pull/17).
* Add support for long-range Lennard-Jones dispersion correction [#18](https://github.com/OpenBioSim/loch/pull/18).
* Add support for Beutler soft-core Lennard-Jones form [#18](https://github.com/OpenBioSim/loch/pull/18).
* Fixed type check for ``water_template`` [#19](https://github.com/OpenBioSim/loch/pull/19).
* Add support for simulations in the osmotic ensemble [#22](https://github.com/OpenBioSim/loch/pull/22).
* Fixed non-uniform bulk insertion positions caused by use of normal rather than uniform random numbers [#24](https://github.com/OpenBioSim/loch/pull/24).
* Add methods to update the system with the current water state and return system without ghost waters [#26](https://github.com/OpenBioSim/loch/pull/26).

[2025.2.0](https://github.com/openbiosim/loch/compare/2025.1.0...2025.2.0) - Feb 2026
-------------------------------------------------------------------------------------

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,12 @@ pip install -e .
> [!Note]
> Pixi does not run conda post-link scripts, so the `ocl-icd-system`
> symlink needed for OpenCL won't be created automatically. After
> creating the environment, run the following once to fix this:
> creating the environment (or after a pixi update), run the following
> to fix this:
>
> ```bash
> pixi shell
> ln -s /etc/OpenCL/vendors "${CONDA_PREFIX}/etc/OpenCL/vendors/ocl-icd-system"
> ln -sfn /etc/OpenCL/vendors "${CONDA_PREFIX}/etc/OpenCL/vendors/ocl-icd-system"
> ```

### Installing from source (full OpenBioSim development)
Expand Down
5 changes: 4 additions & 1 deletion pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ platforms = ["linux-64", "osx-arm64", "win-64"]

[dependencies]
python = ">=3.10"
biosimspace = "*"
# main
biosimspace = ">=2026.1.0,<2026.2.0"
# devel
#biosimspace = "==2026.2.0.dev"
loguru = "*"
pyopencl = "*"

Expand Down
5 changes: 4 additions & 1 deletion recipes/loch/recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ requirements:
- setuptools
- versioningit
run:
- biosimspace
# main
- biosimspace >=2026.1.0,<2026.2.0
# devel
#- biosimspace ==2026.2.0.dev
- loguru
- pyopencl
- python
Expand Down
1 change: 1 addition & 0 deletions src/loch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
#####################################################################

from ._sampler import *
from ._softcore import *
from ._utils import *
from ._version import __version__
73 changes: 45 additions & 28 deletions src/loch/_kernels.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,8 @@
GLOBAL float* water_position,
int is_target,
GLOBAL float* randoms_rotation,
GLOBAL float* randoms_position,
GLOBAL float* randoms_position_sphere,
GLOBAL float* randoms_position_bulk,
GLOBAL float* randoms_radius,
GLOBAL const float* cell_matrix)
{
Expand Down Expand Up @@ -319,9 +320,9 @@
if (is_target == 1)
{
// Generate a random position around the target using pre-generated normals.
xyz[0] = randoms_position[tidx * 3];
xyz[1] = randoms_position[tidx * 3 + 1];
xyz[2] = randoms_position[tidx * 3 + 2];
xyz[0] = randoms_position_sphere[tidx * 3];
xyz[1] = randoms_position_sphere[tidx * 3 + 1];
xyz[2] = randoms_position_sphere[tidx * 3 + 2];

float norm = sqrtf(xyz[0] * xyz[0] + xyz[1] * xyz[1] + xyz[2] * xyz[2]);
xyz[0] /= norm;
Expand All @@ -337,9 +338,9 @@
{
// Use pre-generated uniform randoms for bulk sampling.
float r[3];
r[0] = randoms_position[tidx * 3];
r[1] = randoms_position[tidx * 3 + 1];
r[2] = randoms_position[tidx * 3 + 2];
r[0] = randoms_position_bulk[tidx * 3];
r[1] = randoms_position_bulk[tidx * 3 + 1];
r[2] = randoms_position_bulk[tidx * 3 + 2];

for (int i = 0; i < 3; i++)
{
Expand Down Expand Up @@ -394,9 +395,11 @@
float rf_cutoff,
float rf_kappa,
float rf_correction,
float sc_coulomb_power,
int softcore_form,
float sc_shift_coulomb,
float sc_shift_delta)
float sc_shift_delta,
int sc_taylor_power,
float sc_beutler_alpha)
{
// Work out the atom index.
const int idx_atom = GET_GLOBAL_ID(0);
Expand Down Expand Up @@ -538,7 +541,7 @@
energy_coul[idx] += (q0 * q1) * (r_inv + (rf_kappa * r2) - rf_correction);
}

// Zacharias soft-core potential.
// Soft-core potential for ghost atoms.
else
{
// Store required parameters.
Expand All @@ -549,35 +552,49 @@
const float e = sqrtf(e0 * e1);
const float a = alpha[idx_atom];

// Compute the distance between the atoms.
float r = sqrtf(r2);
// Clamp r2 to avoid singularities.
const float r2_sc = (r2 < 1e-6f) ? 1e-6f : r2;

// Truncate the distance.
if (r < 0.001f)
// Precompute r^6 and sigma^6 using r2 directly (avoids sqrtf and powf).
const float r6 = r2_sc * r2_sc * r2_sc;
const float s2 = s * s;
const float s6_val = s2 * s2 * s2;

// Compute the LJ interaction using the chosen soft-core form.
float sig6;
float lj_prefactor = 1.0f;
if (softcore_form == 1)
{
r = 0.001f;
// Taylor soft-core LJ:
// sig6 = sigma^6 / (alpha^m * sigma^6 + r^6)
const float alpha_m = (sc_taylor_power == 1) ? a
: (sc_taylor_power == 0) ? 1.0f
: powf(a, (float)sc_taylor_power);
sig6 = s6_val / (alpha_m * s6_val + r6);
}

// Compute the Lennard-Jones interaction.
const float delta_lj = sc_shift_delta * a;
const float s6 = powf(s, 6.0f) / powf((s * delta_lj) + (r * r), 3.0f);
energy_lj[idx] += 4.0f * e * s6 * (s6 - 1.0f);

// Compute the Coulomb power expression.
float cpe;
if (sc_coulomb_power == 0.0f)
else if (softcore_form == 2)
{
cpe = 1.0f;
// Beutler soft-core LJ:
// sig6 = sigma^6 / (sc_beutler_alpha * sigma^6 * alpha + r^6)
// V_LJ = (1 - alpha) * 4 * epsilon * sig6 * (sig6 - 1)
sig6 = s6_val / (sc_beutler_alpha * s6_val * a + r6);
lj_prefactor = 1.0f - a;
}
else
{
cpe = powf((1.0f - a), sc_coulomb_power);
// Zacharias soft-core LJ:
// sig6 = sigma^6 / (sigma*delta + r^2)^3
// delta = shift_delta * alpha
const float delta_lj = sc_shift_delta * a;
const float denom = (s * delta_lj) + r2_sc;
sig6 = s6_val / (denom * denom * denom);
}
energy_lj[idx] += lj_prefactor * 4.0f * e * sig6 * (sig6 - 1.0f);

// Compute the Coulomb interaction.
energy_coul[idx] += (q0 * q1) *
((cpe / sqrtf((sc_shift_coulomb * sc_shift_coulomb * a)
+ (r * r))) + (rf_kappa * r2) - rf_correction);
((1.0f / sqrtf((sc_shift_coulomb * sc_shift_coulomb * a)
+ r2_sc)) + (rf_kappa * r2) - rf_correction);

}
}
Expand Down
11 changes: 11 additions & 0 deletions src/loch/_platforms/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def __init__(

Parameters
----------

device : int
The GPU device index to use.

Expand Down Expand Up @@ -89,6 +90,7 @@ def compile_kernels(self) -> _Dict[str, _Callable]:

Returns
-------

dict
Dictionary mapping kernel names to callable kernel functions.
Expected keys: 'update_water', 'deletion', 'water', 'energy',
Expand All @@ -103,11 +105,13 @@ def to_gpu(self, array: _np.ndarray) -> _Any:

Parameters
----------

array : numpy.ndarray
Array to transfer to GPU.

Returns
-------

GPU buffer
Platform-specific GPU buffer object.
"""
Expand All @@ -120,6 +124,7 @@ def empty(self, shape, dtype) -> _Any:

Parameters
----------

shape : tuple
Shape of the array to allocate.

Expand All @@ -128,6 +133,7 @@ def empty(self, shape, dtype) -> _Any:

Returns
-------

GPU buffer
Platform-specific GPU buffer object.
"""
Expand All @@ -140,11 +146,13 @@ def from_gpu(self, buffer: _Any) -> _np.ndarray:

Parameters
----------

buffer : GPU buffer
Platform-specific GPU buffer to transfer from.

Returns
-------

numpy.ndarray
Array containing the data from GPU.
"""
Expand Down Expand Up @@ -185,6 +193,7 @@ def platform_name(self) -> str:

Returns
-------

str
Platform name ('cuda' or 'opencl').
"""
Expand All @@ -197,6 +206,7 @@ def cache_hit(self) -> bool:

Returns
-------

bool
True if kernels were loaded from cache, False if freshly compiled.
"""
Expand All @@ -209,6 +219,7 @@ def compiler_log(self) -> str:

Returns
-------

str
Compiler log output, or empty string if no warnings/messages.
"""
Expand Down
24 changes: 19 additions & 5 deletions src/loch/_platforms/_cuda.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def __init__(

Parameters
----------

device : int
The CUDA device index to use.

Expand Down Expand Up @@ -113,6 +114,7 @@ def __init__(
# Use the primary context (shared with OpenMM and other CUDA users).
self._pycuda_context = self._cuda_device.retain_primary_context()
self._pycuda_context.push()
self._push_count = 1

self._device = self._pycuda_context.get_device()

Expand All @@ -134,6 +136,7 @@ def compile_kernels(self) -> _Dict[str, _Callable]:

Returns
-------

dict
Dictionary mapping kernel names to callable kernel functions.
"""
Expand Down Expand Up @@ -198,11 +201,13 @@ def to_gpu(self, array: _np.ndarray) -> _Any:

Parameters
----------

array : numpy.ndarray
Array to transfer to GPU.

Returns
-------

pycuda.gpuarray.GPUArray
GPU array containing the data.
"""
Expand All @@ -214,6 +219,7 @@ def empty(self, shape, dtype) -> _Any:

Parameters
----------

shape : tuple
Shape of the array to allocate.

Expand All @@ -222,6 +228,7 @@ def empty(self, shape, dtype) -> _Any:

Returns
-------

pycuda.gpuarray.GPUArray
Allocated GPU array.
"""
Expand All @@ -233,11 +240,13 @@ def from_gpu(self, buffer: _Any) -> _np.ndarray:

Parameters
----------

buffer : pycuda.gpuarray.GPUArray
GPU array to transfer from.

Returns
-------

numpy.ndarray
Array containing the data from GPU.
"""
Expand All @@ -248,22 +257,26 @@ def push_context(self):
Push the primary context onto the calling thread's context stack.
"""
self._pycuda_context.push()
self._push_count += 1

def pop_context(self):
"""
Pop the primary context from the calling thread's context stack.
"""
self._pycuda_context.pop()
self._push_count -= 1

def cleanup(self):
"""
Clean up CUDA resources and pop the context pushed during __init__.
Clean up CUDA resources and pop all outstanding context pushes.
"""
if self._pycuda_context is not None:
try:
self._pycuda_context.pop()
except Exception:
pass
for _ in range(self._push_count):
try:
self._pycuda_context.pop()
except Exception:
pass
self._push_count = 0
self._pycuda_context = None

@property
Expand All @@ -273,6 +286,7 @@ def platform_name(self) -> str:

Returns
-------

str
Platform name ('cuda').
"""
Expand Down
Loading
Loading