Skip to content

Commit c1272a4

Browse files
authored
Merge branch 'OpenBioSim:devel' into feature_DMR
2 parents 88d5a70 + 4330138 commit c1272a4

38 files changed

Lines changed: 2914 additions & 705 deletions

.clang-format

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
BasedOnStyle: LLVM
2+
UseTab: Never
3+
IndentWidth: 4
4+
TabWidth: 4
5+
BreakBeforeBraces: Allman
6+
AllowShortIfStatementsOnASingleLine: false
7+
IndentCaseLabels: false
8+
ColumnLimit: 0
9+
AccessModifierOffset: -4
10+
NamespaceIndentation: All
11+
FixNamespaceComments: false

actions/generate_recipe.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,9 @@ def generate_recipe(data, features, git_remote, git_branch, git_version, git_num
375375

376376
# Script test (pytest)
377377
lines.append(" - script:")
378+
lines.append(" - if: win")
379+
lines.append(" then: set PYTHONUTF8=1")
380+
lines.append(" else: export PYTHONUTF8=1")
378381
lines.append(" - pytest -vvv --color=yes --runveryslow ./tests")
379382
lines.append(" files:")
380383
lines.append(" source:")

corelib/src/libs/SireCAS/lambdaschedule.cpp

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ static RegisterMetaType<LambdaSchedule> r_schedule;
6565

6666
QDataStream &operator<<(QDataStream &ds, const LambdaSchedule &schedule)
6767
{
68-
writeHeader(ds, r_schedule, 3);
68+
writeHeader(ds, r_schedule, 4);
6969

7070
SharedDataStream sds(ds);
7171

@@ -75,6 +75,7 @@ QDataStream &operator<<(QDataStream &ds, const LambdaSchedule &schedule)
7575
<< schedule.default_equations
7676
<< schedule.stage_equations
7777
<< schedule.mol_schedules
78+
<< schedule.coupled_levers
7879
<< static_cast<const Property &>(schedule);
7980

8081
return ds;
@@ -91,21 +92,30 @@ QDataStream &operator>>(QDataStream &ds, LambdaSchedule &schedule)
9192
{
9293
VersionID v = readHeader(ds, r_schedule);
9394

94-
if (v == 1 or v == 2 or v == 3)
95+
if (v == 1 or v == 2 or v == 3 or v == 4)
9596
{
9697
SharedDataStream sds(ds);
9798

9899
sds >> schedule.constant_values;
99100

100-
if (v == 3)
101+
if (v >= 3)
101102
sds >> schedule.force_names;
102103

103104
sds >> schedule.lever_names >> schedule.stage_names >>
104105
schedule.default_equations >> schedule.stage_equations;
105106

106-
if (v == 2 or v == 3)
107+
if (v >= 2)
107108
sds >> schedule.mol_schedules;
108109

110+
if (v >= 4)
111+
sds >> schedule.coupled_levers;
112+
else
113+
{
114+
// Populate the default coupling for streams written before v4
115+
schedule.coupled_levers[_get_lever_name("cmap", "cmap_grid")] =
116+
_get_lever_name("torsion", "torsion_k");
117+
}
118+
109119
if (v < 3)
110120
{
111121
// need to make sure that the lever names are namespaced
@@ -146,13 +156,17 @@ QDataStream &operator>>(QDataStream &ds, LambdaSchedule &schedule)
146156
}
147157
}
148158
else
149-
throw version_error(v, "1, 2, 3", r_schedule, CODELOC);
159+
throw version_error(v, "1, 2, 3, 4", r_schedule, CODELOC);
150160

151161
return ds;
152162
}
153163

154164
LambdaSchedule::LambdaSchedule() : ConcreteProperty<LambdaSchedule, Property>()
155165
{
166+
// By default, cmap_grid falls back to torsion_k so that customising
167+
// torsion scaling automatically keeps the CMAP correction in sync.
168+
coupled_levers[_get_lever_name("cmap", "cmap_grid")] =
169+
_get_lever_name("torsion", "torsion_k");
156170
}
157171

158172
LambdaSchedule::LambdaSchedule(const LambdaSchedule &other)
@@ -162,7 +176,8 @@ LambdaSchedule::LambdaSchedule(const LambdaSchedule &other)
162176
force_names(other.force_names),
163177
lever_names(other.lever_names), stage_names(other.stage_names),
164178
default_equations(other.default_equations),
165-
stage_equations(other.stage_equations)
179+
stage_equations(other.stage_equations),
180+
coupled_levers(other.coupled_levers)
166181
{
167182
}
168183

@@ -1006,6 +1021,32 @@ void LambdaSchedule::removeEquation(const QString &stage,
10061021
this->stage_equations[idx].remove(_get_lever_name(force, lever));
10071022
}
10081023

1024+
/** Couple the lever 'force::lever' to 'fallback_force::fallback_lever'.
1025+
* If no custom equation has been set for 'force::lever' at any stage,
1026+
* the equation for 'fallback_force::fallback_lever' will be used instead
1027+
* of the stage default. This is a single level of indirection — the
1028+
* fallback lever is not itself followed further.
1029+
*
1030+
* By default, 'cmap::cmap_grid' is coupled to 'torsion::torsion_k' so
1031+
* that custom torsion schedules automatically keep the CMAP correction
1032+
* in sync.
1033+
*/
1034+
void LambdaSchedule::coupleLever(const QString &force, const QString &lever,
1035+
const QString &fallback_force,
1036+
const QString &fallback_lever)
1037+
{
1038+
coupled_levers[_get_lever_name(force, lever)] =
1039+
_get_lever_name(fallback_force, fallback_lever);
1040+
}
1041+
1042+
/** Remove any coupling for the lever 'force::lever', reverting it to
1043+
* use the stage default equation when no custom equation is set.
1044+
*/
1045+
void LambdaSchedule::removeCoupledLever(const QString &force, const QString &lever)
1046+
{
1047+
coupled_levers.remove(_get_lever_name(force, lever));
1048+
}
1049+
10091050
/** Return whether or not the specified 'lever' in the specified 'force'
10101051
* at the specified 'stage' has a custom equation set for it
10111052
*/
@@ -1077,6 +1118,32 @@ SireCAS::Expression LambdaSchedule::_getEquation(int stage,
10771118
return it.value();
10781119
}
10791120

1121+
// Check coupled levers: if this lever is coupled to another, try to
1122+
// find a custom equation for the coupled lever before falling back to
1123+
// the stage default. This is a single level of indirection (no recursion)
1124+
// to prevent loops.
1125+
auto coupled_it = this->coupled_levers.find(lever_name);
1126+
1127+
if (coupled_it != this->coupled_levers.end())
1128+
{
1129+
const auto &coupled_name = coupled_it.value();
1130+
const int sep = coupled_name.indexOf("::");
1131+
const QString coupled_force = sep >= 0 ? coupled_name.left(sep) : "*";
1132+
const QString coupled_lever_part = sep >= 0 ? coupled_name.mid(sep + 2) : coupled_name;
1133+
1134+
it = equations.find(coupled_name);
1135+
if (it != equations.end())
1136+
return it.value();
1137+
1138+
it = equations.find(_get_lever_name(coupled_force, "*"));
1139+
if (it != equations.end())
1140+
return it.value();
1141+
1142+
it = equations.find(_get_lever_name("*", coupled_lever_part));
1143+
if (it != equations.end())
1144+
return it.value();
1145+
}
1146+
10801147
// we don't have any match, so return the default equation for this stage
10811148
return this->default_equations[stage];
10821149
}

corelib/src/libs/SireCAS/lambdaschedule.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ namespace SireCAS
164164
const QString &force = "*",
165165
const QString &lever = "*") const;
166166

167+
void coupleLever(const QString &force, const QString &lever,
168+
const QString &fallback_force,
169+
const QString &fallback_lever);
170+
171+
void removeCoupledLever(const QString &force, const QString &lever);
172+
167173
SireCAS::Expression getEquation(const QString &stage = "*",
168174
const QString &force = "*",
169175
const QString &lever = "*") const;
@@ -248,6 +254,12 @@ namespace SireCAS
248254
particular stage */
249255
QVector<QHash<QString, SireCAS::Expression>> stage_equations;
250256

257+
/** Coupled lever fallbacks: if a lever (force::lever) has no custom
258+
equation set, use the equation for the paired lever instead of
259+
falling straight back to the stage default. Stored as
260+
_get_lever_name(force, lever) → _get_lever_name(fb_force, fb_lever). */
261+
QHash<QString, QString> coupled_levers;
262+
251263
/** The symbol used to represent the lambda value */
252264
static SireCAS::Symbol lambda_symbol;
253265

corelib/src/libs/SireIO/biosimspace.cpp

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,14 @@
3232

3333
#include "SireError/errors.h"
3434

35+
#include "SireMM/cljnbpairs.h"
36+
3537
#include "SireMol/atomelements.h"
3638
#include "SireMol/atommasses.h"
3739
#include "SireMol/connectivity.h"
3840
#include "SireMol/core.h"
3941
#include "SireMol/mgname.h"
42+
#include "SireMol/moleculeinfodata.h"
4043
#include "SireMol/moleditor.h"
4144
#include "SireMol/molidx.h"
4245

@@ -49,6 +52,7 @@
4952

5053
using namespace SireBase;
5154
using namespace SireMaths;
55+
using namespace SireMM;
5256
using namespace SireMol;
5357
using namespace SireUnits;
5458
using namespace SireVol;
@@ -1627,7 +1631,7 @@ namespace SireIO
16271631

16281632
Molecule createSodiumIon(const Vector &coords, const QString model, const PropertyMap &map)
16291633
{
1630-
// Strip all whitespace from the model name and convert to upper case.
1634+
// Strip all whitespace from the model name and convert to upper case.
16311635
auto _model = model.simplified().replace(" ", "").toUpper();
16321636

16331637
// Create a hash between the allowed model names and their templace files.
@@ -1658,7 +1662,7 @@ namespace SireIO
16581662

16591663
Molecule createChlorineIon(const Vector &coords, const QString model, const PropertyMap &map)
16601664
{
1661-
// Strip all whitespace from the model name and convert to upper case.
1665+
// Strip all whitespace from the model name and convert to upper case.
16621666
auto _model = model.simplified().replace(" ", "").toUpper();
16631667

16641668
// Create a hash between the allowed model names and their templace files.
@@ -1730,9 +1734,9 @@ namespace SireIO
17301734

17311735
// Set the new coordinates.
17321736
molecule = molecule.edit()
1733-
.atom(AtomIdx(j))
1734-
.setProperty(coord_prop, coord)
1735-
.molecule();
1737+
.atom(AtomIdx(j))
1738+
.setProperty(coord_prop, coord)
1739+
.molecule();
17361740
}
17371741

17381742
// Update the molecule in the system.
@@ -1754,4 +1758,75 @@ namespace SireIO
17541758
return Vector(nx, ny, nz);
17551759
}
17561760

1761+
SireBase::PropertyList mergeIntrascale(const CLJNBPairs &nb0,
1762+
const CLJNBPairs &nb1,
1763+
const MoleculeInfoData &merged_info,
1764+
const QHash<AtomIdx, AtomIdx> &mol0_merged_mapping,
1765+
const QHash<AtomIdx, AtomIdx> &mol1_merged_mapping)
1766+
{
1767+
// Helper lambda: copy the non-default scaling factors from 'nb' to
1768+
// 'nb_merged' according to the provided mapping. Takes nb_merged by
1769+
// reference to avoid copies.
1770+
auto copyIntrascale = [&](const CLJNBPairs &nb, CLJNBPairs &nb_merged,
1771+
const QHash<AtomIdx, AtomIdx> &mapping)
1772+
{
1773+
const int n = nb.nAtoms();
1774+
1775+
for (int i = 0; i < n; ++i)
1776+
{
1777+
const AtomIdx ai(i);
1778+
1779+
// Get the index of this atom in the merged system.
1780+
const AtomIdx merged_ai = mapping.value(ai, AtomIdx(-1));
1781+
1782+
// If this atom hasn't been mapped to the merged system, then we
1783+
// can skip it, as any scaling factors involving this atom will
1784+
// just use the default.
1785+
if (merged_ai == AtomIdx(-1))
1786+
continue;
1787+
1788+
for (int j = i; j < n; ++j)
1789+
{
1790+
const AtomIdx aj(j);
1791+
1792+
// Get the scaling factor for this pair of atoms.
1793+
const CLJScaleFactor sf = nb.get(ai, aj);
1794+
1795+
// This is a non-default scaling factor, so we need to copy
1796+
// it across to the merged intrascale object according to
1797+
// the mapping.
1798+
if (sf.coulomb() != 1.0 or sf.lj() != 1.0)
1799+
{
1800+
// Get the index of the second atom in the merged system.
1801+
const AtomIdx merged_aj = mapping.value(aj, AtomIdx(-1));
1802+
1803+
// Only set the scaling factor if both atoms have been
1804+
// mapped to the merged system. If one of the atoms
1805+
// hasn't been mapped, then we can just use the default.
1806+
if (merged_aj != AtomIdx(-1))
1807+
nb_merged.set(merged_ai, merged_aj, sf);
1808+
}
1809+
}
1810+
}
1811+
};
1812+
1813+
// Create the intrascale objects for the merged end-states.
1814+
CLJNBPairs intra0(merged_info);
1815+
CLJNBPairs intra1(merged_info);
1816+
1817+
// Copy the non-default scaling factors from the original intrascale
1818+
// objects to the merged intrascale objects according to the provided
1819+
// mappings.
1820+
copyIntrascale(nb1, intra0, mol1_merged_mapping);
1821+
copyIntrascale(nb0, intra0, mol0_merged_mapping);
1822+
copyIntrascale(nb0, intra1, mol0_merged_mapping);
1823+
copyIntrascale(nb1, intra1, mol1_merged_mapping);
1824+
1825+
// Assemble the intrascale objects into a property list to return.
1826+
SireBase::PropertyList ret;
1827+
ret.append(intra0);
1828+
ret.append(intra1);
1829+
return ret;
1830+
}
1831+
17571832
} // namespace SireIO

corelib/src/libs/SireIO/biosimspace.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@
3636

3737
#include "SireMaths/vector.h"
3838

39+
#include "SireBase/propertylist.h"
40+
41+
#include "SireMM/cljnbpairs.h"
42+
43+
#include "SireMol/atomidxmapping.h"
44+
#include "SireMol/moleculeinfodata.h"
3945
#include "SireMol/select.h"
4046

4147
SIRE_BEGIN_HEADER
@@ -372,6 +378,43 @@ namespace SireIO
372378
const bool is_lambda1 = false, const PropertyMap &map = PropertyMap());
373379

374380
Vector cross(const Vector &v0, const Vector &v1);
381+
382+
//! Merge the CLJNBPairs (intrascale) of two molecules into a perturbable
383+
/*! merged molecule's end-state intrascales.
384+
385+
Expands nb0 from molecule0's atom index space into the merged
386+
molecule's space (preserving actual per-pair scale factors, including
387+
force-field-specific overrides such as GLYCAM funct=2 pairs), then
388+
calls CLJNBPairs::merge to produce intrascale0 and intrascale1.
389+
390+
\param nb0
391+
The CLJNBPairs for molecule0 in its original atom index space.
392+
393+
\param nb1
394+
The CLJNBPairs for molecule1 in its original atom index space.
395+
396+
\param merged_info
397+
The MoleculeInfoData for the merged molecule.
398+
399+
\param mol0_merged_mapping
400+
A hash mapping each AtomIdx in molecule0's original space to
401+
the corresponding AtomIdx in the merged molecule's space.
402+
403+
\param atom_mapping
404+
The AtomIdxMapping describing how merged-molecule atom indices
405+
correspond to molecule1 atom indices (used by CLJNBPairs::merge).
406+
407+
\retval [intrascale0, intrascale1]
408+
A PropertyList containing the CLJNBPairs for the lambda=0 and
409+
lambda=1 end states of the merged molecule.
410+
*/
411+
SIREIO_EXPORT SireBase::PropertyList mergeIntrascale(
412+
const SireMM::CLJNBPairs &nb0,
413+
const SireMM::CLJNBPairs &nb1,
414+
const SireMol::MoleculeInfoData &merged_info,
415+
const QHash<SireMol::AtomIdx, SireMol::AtomIdx> &mol0_merged_mapping,
416+
const QHash<SireMol::AtomIdx, SireMol::AtomIdx> &mol1_merged_mapping);
417+
375418
} // namespace SireIO
376419

377420
SIRE_EXPOSE_FUNCTION(SireIO::isAmberWater)
@@ -387,6 +430,7 @@ SIRE_EXPOSE_FUNCTION(SireIO::updateCoordinatesAndVelocities)
387430
SIRE_EXPOSE_FUNCTION(SireIO::createSodiumIon)
388431
SIRE_EXPOSE_FUNCTION(SireIO::createChlorineIon)
389432
SIRE_EXPOSE_FUNCTION(SireIO::setCoordinates)
433+
SIRE_EXPOSE_FUNCTION(SireIO::mergeIntrascale)
390434

391435
SIRE_END_HEADER
392436

0 commit comments

Comments
 (0)