From ef727583e43c207f3f45cdfadbc3b6bf6eedb4b4 Mon Sep 17 00:00:00 2001 From: Lester Hedges Date: Tue, 29 Sep 2026 20:43:57 +0100 Subject: [PATCH 1/2] Fix TriclinicBox losing its metric matrix when streamed or assigned. --- corelib/src/libs/SireVol/triclinicbox.cpp | 7 +++-- doc/source/changelog.rst | 4 +++ tests/vol/test_triclinic.py | 37 +++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/corelib/src/libs/SireVol/triclinicbox.cpp b/corelib/src/libs/SireVol/triclinicbox.cpp index 9f6ddad85..98e133cd6 100644 --- a/corelib/src/libs/SireVol/triclinicbox.cpp +++ b/corelib/src/libs/SireVol/triclinicbox.cpp @@ -74,6 +74,9 @@ QDataStream &operator>>(QDataStream &ds, TriclinicBox &box) else if (v == 2) { ds >> box.v0 >> box.v1 >> box.v2 >> box.rotation_matrix >> box.cell_matrix >> box.cell_matrix_inverse >> box.dist_max >> box._alpha >> box._beta >> box._gamma >> box.vol >> box.is_rotated >> box.is_reduced >> box.invlength; + + // M and max_length aren't streamed, so recompute them. + box.setAttributes(); } else throw version_error(v, "1,2", r_box, CODELOC); @@ -269,7 +272,6 @@ void TriclinicBox::rotate(double precision) this->v1.setZ(0); } - // Now set the box attributes. this->setAttributes(); } @@ -405,6 +407,7 @@ TriclinicBox &TriclinicBox::operator=(const TriclinicBox &other) rotation_matrix = other.rotation_matrix; cell_matrix = other.cell_matrix; cell_matrix_inverse = other.cell_matrix_inverse; + M = other.M; dist_max = other.dist_max; max_length = other.max_length; _alpha = other._alpha; @@ -571,7 +574,7 @@ SireUnits::Dimension::Length TriclinicBox::maximumCutoff() const if (this->isReduced()) { QList diagonals = {this->v0.x(), this->v1.y(), this->v2.z()}; - return SireUnits::Dimension::Length(*std::min_element(diagonals.begin(), diagonals.end())/2.0); + return SireUnits::Dimension::Length(*std::min_element(diagonals.begin(), diagonals.end()) / 2.0); } // Otherwise, use half the norm of the smallest box vector. else diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index 2ac03be4c..4bcf91be9 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -58,6 +58,10 @@ organisation on `GitHub `__. bindings. Structures are now exchanged with the gemmi Python module using gemmi's own binary serialization, removing the pybind11 dependency. +* Fixed ``TriclinicBox`` losing its metric matrix when streamed or assigned, which gave + wrong minimum image distances from ``calc_dist``, e.g. for trajectory frame spaces. This + broke ``sire.restraints.boresch_search()`` for triclinic boxes. + `2026.2.0 `__ - September 2026 ---------------------------------------------------------------------------------------------- diff --git a/tests/vol/test_triclinic.py b/tests/vol/test_triclinic.py index c9e929ea5..8112a899b 100644 --- a/tests/vol/test_triclinic.py +++ b/tests/vol/test_triclinic.py @@ -143,6 +143,43 @@ def test_stream(): assert recovered_box == box +def test_stream_calc_dist(): + """ + Test that a streamed TriclinicBox gives the same minimum image distance + as the original. + """ + + tmp_dir = tempfile.TemporaryDirectory() + s3_file = f"{tmp_dir.name}/box.s3" + + box = sr.vol.TriclinicBox.truncated_octahedron(50.0, True, True) + + sr.stream.save(box, s3_file) + recovered_box = sr.stream.load(s3_file) + + p0 = sr.maths.Vector(1, 2, 3) + p1 = sr.maths.Vector(7, -2, 5) + dist = sr.maths.Vector.distance(p0, p1).value() + + assert box.calc_dist(p0, p1) == pytest.approx(dist) + assert recovered_box.calc_dist(p0, p1) == pytest.approx(dist) + + +def test_default_calc_dist(): + """ + Test that a default constructed TriclinicBox gives Cartesian distances. + """ + + box = sr.vol.TriclinicBox() + + p0 = sr.maths.Vector(1, 2, 3) + p1 = sr.maths.Vector(7, -2, 5) + + assert box.calc_dist(p0, p1) == pytest.approx( + sr.maths.Vector.distance(p0, p1).value() + ) + + def test_max_cutoff(ala_mols): """ Test that the maximum cutoff is set correctly. From fd3a2d0f7dd1bc4074bfd0325ea48011dfdc9f42 Mon Sep 17 00:00:00 2001 From: Lester Hedges Date: Tue, 29 Sep 2026 20:56:12 +0100 Subject: [PATCH 2/2] Add note about ccache for development work. [ci skip] --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index dfb1ed92c..d0a664588 100644 --- a/README.rst +++ b/README.rst @@ -130,6 +130,10 @@ Now compile and install sire: A small word of warning, the compilation can easily take over an hour! +If `ccache `__ is on your ``PATH``, then ``setup.py`` will +use it automatically, which makes rebuilds much faster when developing. It +isn't included in the pixi environments, so install it separately if needed. + Other pixi environments are available depending on your needs: * ``pixi install -e default`` - core sire dependencies only