Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 4 additions & 3 deletions .github/workflows/build-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ env:
CIBW_TEST_COMMAND: >-
python -c "import importlib.util, XpongeCPP, Xponge,
XpongeCPP.io_bundle, XpongeCPP.metal_assignment;
assert XpongeCPP.__version__ == '0.2.0';
assert XpongeCPP.__version__ == '0.2.6';
assert not hasattr(XpongeCPP, 'MC' + 'PB');
assert importlib.util.find_spec('XpongeCPP.' + ('MC' + 'PB').lower()) is None"
CIBW_BEFORE_ALL_LINUX: >-
if command -v dnf >/dev/null 2>&1; then dnf install -y hdf5-devel;
elif command -v yum >/dev/null 2>&1; then yum install -y hdf5-devel;
else apt-get update && apt-get install -y libhdf5-dev; fi
CIBW_REPAIR_WHEEL_COMMAND_LINUX: "auditwheel repair -w {dest_dir} {wheel}"
CIBW_BEFORE_BUILD_WINDOWS: "python -m pip install delvewheel"
CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel repair -w {dest_dir} {wheel}"

Expand Down Expand Up @@ -118,10 +119,10 @@ jobs:
tests/test_bundle_protocol.py
tests/test_bundle_native.py

- name: Run Xponge-origin 1.7b8 parity cohorts
- name: Run Xponge-origin compatibility parity cohorts
run: >-
python -m pytest -q
tests/test_release_020.py
tests/test_release_025.py
tests/test_origin_resp_linear_constraints.py
tests/test_origin_io_bundle.py
tests/test_origin_io_bundle_reverse.py
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ env:
CIBW_TEST_COMMAND: >-
python -c "import importlib.util, XpongeCPP, Xponge,
XpongeCPP.io_bundle, XpongeCPP.metal_assignment;
assert XpongeCPP.__version__ == '0.2.0';
assert XpongeCPP.__version__ == '0.2.6';
assert not hasattr(XpongeCPP, 'MC' + 'PB');
assert importlib.util.find_spec('XpongeCPP.' + ('MC' + 'PB').lower()) is None"
CIBW_BEFORE_ALL_LINUX: >-
if command -v dnf >/dev/null 2>&1; then dnf install -y hdf5-devel;
elif command -v yum >/dev/null 2>&1; then yum install -y hdf5-devel;
else apt-get update && apt-get install -y libhdf5-dev; fi
CIBW_REPAIR_WHEEL_COMMAND_LINUX: "auditwheel repair -w {dest_dir} {wheel}"
CIBW_BEFORE_BUILD_WINDOWS: "python -m pip install delvewheel"
CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel repair -w {dest_dir} {wheel}"

Expand Down
14 changes: 14 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ endif()
target_compile_options(_core PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wall -Wextra -Wpedantic>
)
if(UNIX AND NOT APPLE)
set_target_properties(_core PROPERTIES
BUILD_RPATH_USE_ORIGIN TRUE
INSTALL_RPATH "$ORIGIN"
INSTALL_RPATH_USE_LINK_PATH FALSE
INSTALL_REMOVE_ENVIRONMENT_RPATH TRUE
)
endif()

option(XPONGECPP_SANITIZE "Build with AddressSanitizer and UndefinedBehaviorSanitizer" OFF)
if (XPONGECPP_SANITIZE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
Expand All @@ -95,3 +103,9 @@ if (XPONGECPP_SANITIZE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
endif()

install(TARGETS _core DESTINATION XpongeCPP)
install(
DIRECTORY third_party/xponge_reference_forcefield/
DESTINATION XpongeCPP/data/reference_forcefield
PATTERN "__pycache__" EXCLUDE
PATTERN "*.pyc" EXCLUDE
)
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,10 @@ XpongeCPP.save_sponge_input_raw(molecule, "system", "inputs")
XpongeCPP.save_sponge_input_bundle(molecule, "system", "inputs")
```

RESP supports a multi-backend strategy:
RESP automatically selects the first available backend:

- default backend: `PySCF`
- optional backend: `Psi4`
- preferred backend: `PySCF`
- fallback backend: `Psi4`
- shared dispatch layer: `XpongeCPP.qm`

Example:
Expand All @@ -161,7 +161,8 @@ qm.run_scf(assign, backend="pyscf")
qm.optimize_geometry(assign, backend="pyscf")
```

For Windows RESP workflows, install `Psi4` separately and select it explicitly:
Windows does not install PySCF automatically. If a compatible PySCF is
available, it is selected; otherwise install Psi4 separately as the fallback:

```bash
conda install -c conda-forge psi4
Expand Down
121 changes: 121 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,124 @@
# XpongeCPP 0.2.6

Compatibility target: Xponge-origin 1.7b10.

This parity release changes QM backend selection from an operating-system rule
to runtime capability detection. XpongeCPP now prefers an installed PySCF
backend, falls back to Psi4 when PySCF is absent, and reports a clear error
when neither backend is available. Windows packaging continues to skip PySCF;
users may supply a compatible PySCF installation or install Psi4 separately.

Regression coverage verifies PySCF preference, Psi4 fallback, and the
no-backend error path. Windows wheel CI remains a packaging smoke test; it does
not claim native Windows PySCF support.

# XpongeCPP 0.2.5

Compatibility target: Xponge-origin 1.7b9.

This patch preserves source-atom identity when raw SPONGE export reorders
noncontiguous linked residue components. The native layer now reports the
saved atom permutation before serialization, and the compatibility layer
applies the same permutation to caller-provided source IDs. This keeps Mokda
trajectory topology and atom-order mappings aligned for proteins, ligands,
metals, solvent, and repeated solvent molecules without matching atoms by
name or coordinates.

Focused release validation covers covalent and coordination-driven residue
reordering, source-ID mapping, and the complete native bundle regression
cohort. The complete repository suite passes with 716 tests, 10 optional
skips, and 1 expected xfail. The change adds one linear residue/atom
permutation pass before raw save and does not move force-field assignment back
to Python.

# XpongeCPP 0.2.4

Compatibility target: Xponge-origin 1.7b9.

This compatibility patch preserves residue-link bonds in public MOL2 exports,
keeps mixed standard/GAFF systems intact when invoking GAFF or GAFF2
parameterization, and aligns the legacy MDAnalysis raw-SPONGE reader with the
format hints exposed by Xponge. These fixes cover the Mokda SPONGE-input path
without moving force-field assignment back to Python.

Release validation includes the focused MOL2, mixed-force-field, and
MDAnalysis compatibility regressions. The complete repository suite passes
with 715 tests, 10 optional skips, and 1 expected xfail. A CPython 3.11 wheel
was built and imported from an isolated environment under the Mokda resource
gate with `OOMKillDelta=0`.

# XpongeCPP 0.2.3

Compatibility target: Xponge-origin 1.7b9.

This release completes the Mokda-used same-script compatibility surface and
adds scientific-output comparison for real systems. It preserves native
template bonds when an mmCIF residue also supplies explicit covalent bonds,
including model pseudo-bonds such as the TIP3P H-H distance constraint, while
still honoring explicit Mokda edit deletions. It also synchronizes temporary
legacy residue-link overrides with the native registry and canonicalizes CMAP
grid identities independently of force-field declaration order. The legacy
MDAnalysis raw-SPONGE reader now advertises the `SPONGE_MASS` format and
recognizes `_mass.txt`, matching the Xponge interface used by Mokda H5MD
analysis.

Linux x86_64 release-candidate evidence:

- XpongeCPP repository suite under the coherent managed 0.2.3 runtime after the
raw-reader compatibility fix: 717 passed, 7 optional skips, 1 expected
xfail;
- Xponge-origin 1.7b9 tests through the installed compatibility namespace:
36/36 passed;
- Mokda auto-discovered Xponge consumer matrix: 327 passed, 14 conditional
skips, and 12 subtests under both providers;
- real 3GOU/4EWL Life Modeling under the isolated XpongeCPP 0.2.3 managed
runtime completed in 205.656 s with a 1.09 GiB peak and
`OOMKillDelta=0`;
- complete Mokda Lipid21/1BL8 tier: 8/8 files passed, with a 1.995 GiB peak
and `OOMKillDelta=0`;
- provider-neutral scientific manifests match for repaired 1EMA, 1KV2+B96,
8RYK, 1BNA, 1GYA, and 1BL8; the existing 3GOU/4EWL four-quadrant metal
acceptance also passes under both managed providers;
- provider-neutral 1KV2 benchmark remains 42,600 atoms in both
implementations; XpongeCPP median 0.0588 s versus Xponge 12.47 s, and the
measured XpongeCPP pre/post change is +2.57%, within the 5% release
tolerance.

# XpongeCPP 0.2.2

Compatibility target: Xponge-origin 1.7b9.

This compatibility release closes the Mokda-used Xponge surface under the
same-script `import Xponge` contract. It synchronizes legacy atom-type and
residue-link state with the native registries, aligns GAFF/GAFF2, PDB/link,
Lipid17/Lipid21 and non-Amber behavior with the current reference, and ships
the non-Amber reference force-field data inside wheels. The release also adds
a fail-closed machine-readable capability manifest and validates both source
and installed-wheel execution.

Linux x86_64 release-candidate evidence:

- installed CPython 3.12 wheel: 701 passed, 7 optional skips, 1 expected
unsupported legacy-frcmod workflow;
- Xponge-origin 1.7b9 suite through the compatibility namespace: 36/36 passed;
- Mokda auto-discovered Xponge consumer matrix: 324 passed, 13 conditional
skips and 12 subtests;
- provider-neutral 1KV2 benchmark: 42,600 atoms from both implementations;
XpongeCPP median 0.0588 s versus Xponge 12.47 s;
- pre/post XpongeCPP micro-benchmark change: +2.57% total, within the 5%
release tolerance; no assignment hot path was moved back to Python.

# XpongeCPP 0.2.1

Compatibility target: Xponge-origin 1.7b9.

This patch release corrects GLYCAM terminal-zero residue metadata across the
native template registry, bundled Python data, and the pinned reference
force-field copy. Terminal-zero names such as `0MA`, `0aA`, `0AD`, and `0aD`
now carry no synthetic `O0`/`C0` head attachment. The release adds parity and
standalone PDB-export regression coverage for all four pyranose/furanose and
D/L representative families.

# XpongeCPP 0.2.0

Compatibility target: Xponge-origin 1.7b8.
Expand Down
65 changes: 65 additions & 0 deletions benchmarks/bench_same_script_1kv2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""Provider-neutral 1KV2 assembly benchmark used by release parity gates."""

from __future__ import annotations

import argparse
import json
import statistics
import tempfile
import time
from pathlib import Path

import Xponge
import Xponge.forcefield.amber.ff14sb # noqa: F401
import Xponge.forcefield.amber.tip3p # noqa: F401


def _median(values):
return statistics.median(values)


def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("pdb", type=Path)
parser.add_argument("--repeat", type=int, default=5)
parser.add_argument("--n-solvent", type=int, default=0)
args = parser.parse_args()

timings = {"load_pdb": [], "solvate": [], "save_sponge_input": [], "total": []}
atom_counts = []
water = Xponge.get_template_molecule("WAT")
for _ in range(args.repeat):
total_start = time.perf_counter()
start = time.perf_counter()
molecule = Xponge.load_pdb(str(args.pdb))
timings["load_pdb"].append(time.perf_counter() - start)

start = time.perf_counter()
solvent_options = {"tolerance": 2.5}
if args.n_solvent > 0:
solvent_options["n_solvent"] = args.n_solvent
Xponge.Add_Solvent_Box(molecule, water, 8.0, **solvent_options)
timings["solvate"].append(time.perf_counter() - start)

with tempfile.TemporaryDirectory(prefix="xponge-parity-bench-") as output:
start = time.perf_counter()
Xponge.Save_SPONGE_Input(molecule, prefix="input", dirname=output)
timings["save_sponge_input"].append(time.perf_counter() - start)
timings["total"].append(time.perf_counter() - total_start)
atom_counts.append(len(molecule.atoms))

if len(set(atom_counts)) != 1:
raise RuntimeError(f"non-deterministic atom counts: {atom_counts}")
print(json.dumps({
"implementation": str(getattr(Xponge, "__mokda_backend__", "xponge") or "xponge"),
"version": str(getattr(Xponge, "__version__", "")),
"atom_count": atom_counts[0],
"repeat": args.repeat,
"median_seconds": {key: _median(values) for key, values in timings.items()},
}, sort_keys=True))
return 0


if __name__ == "__main__":
raise SystemExit(main())
15 changes: 8 additions & 7 deletions cpp/assign/resp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@ std::vector<std::array<double, 3>> fibonacci_grid(int npoints, const std::array<
return out;
}
out.reserve(static_cast<std::size_t>(npoints));
const double golden_angle = kPi * (3.0 - std::sqrt(5.0));
const double golden_angle = kPi * (std::sqrt(5.0) - 1.0);
for (int i = 0; i < npoints; ++i) {
const double y = 1.0 - (2.0 * i + 1.0) / static_cast<double>(npoints);
const double r = std::sqrt(std::max(1.0 - y * y, 0.0));
const double theta = golden_angle * static_cast<double>(i);
const double index = static_cast<double>(i + 1);
const double z = (2.0 * index - 1.0) / static_cast<double>(npoints) - 1.0;
const double planar_radius = std::sqrt(std::max(1.0 - z * z, 0.0));
const double theta = golden_angle * index;
out.push_back({
center[0] + radius * std::cos(theta) * r,
center[1] + radius * y,
center[2] + radius * std::sin(theta) * r,
center[0] + radius * planar_radius * std::cos(theta),
center[1] + radius * planar_radius * std::sin(theta),
center[2] + radius * z,
});
}
return out;
Expand Down
4 changes: 4 additions & 0 deletions cpp/core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,7 @@ void add_solvent_box(Molecule& molecule, const Molecule& solvent, const std::arr
double tolerance, std::int64_t n_solvent, std::uint64_t seed = 0);
void add_ions(Molecule& molecule, const std::unordered_map<std::string, std::int64_t>& counts,
std::uint64_t seed = 0, const std::string& solvent_residue = "WAT");
std::vector<AtomId> prepare_sponge_atom_order(Molecule& molecule);
std::unordered_map<std::string, std::filesystem::path> save_sponge_input(Molecule& molecule,
const std::string& prefix,
const std::filesystem::path& dirname);
Expand Down Expand Up @@ -662,6 +663,9 @@ Molecule merge_force_field(const Molecule& molecule_a, const Molecule& molecule_
const std::unordered_map<std::string, double>& specific_lambda);
void register_residue_templates_from_mol2_text(const std::string& text);
void register_residue_templates_from_mol2_file(const std::filesystem::path& filename);
void register_new_residue_templates_from_mol2_text(const std::string& text);
void register_new_residue_templates_from_mol2_file(const std::filesystem::path& filename);
void register_residue_type_template(const ResidueType& residue_type);
void register_template_molecule_from_mol2_file(const std::filesystem::path& filename);
void register_template_virtual_atom2(const std::string& template_name, const std::string& virtual_atom,
const std::string& atom0, const std::string& atom1, const std::string& atom2,
Expand Down
Loading
Loading