Skip to content

Commit 5de3c7c

Browse files
authored
Merge pull request #407 from OpenBioSim/fix_glycam
Fix parsing of GLYCAM topology files
2 parents 208c15f + c0cd911 commit 5de3c7c

14 files changed

Lines changed: 1477 additions & 670 deletions

File tree

.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/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

corelib/src/libs/SireIO/gro87.cpp

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1889,43 +1889,59 @@ System Gro87::startSystem(const PropertyMap &map) const
18891889

18901890
int ncg = 0;
18911891

1892-
QSet<ResNum> completed_residues;
1892+
// Track used residue numbers to handle topologies where numbering
1893+
// restarts (e.g. glycan residues numbered from 1 after a protein
1894+
// chain also starting from 1). Duplicate ResNums cause errors when
1895+
// looking up residues by number.
1896+
// next_unique is always > every number assigned so far, so conflict
1897+
// resolution is O(1) rather than a linear scan.
1898+
QSet<int> used_resnums;
1899+
int next_unique = 0;
1900+
auto unique_resnum = [&](int resnum) -> ResNum
1901+
{
1902+
if (!used_resnums.contains(resnum))
1903+
{
1904+
used_resnums.insert(resnum);
1905+
if (resnum >= next_unique)
1906+
next_unique = resnum + 1;
1907+
return ResNum(resnum);
1908+
}
1909+
// conflict: assign next number beyond all previously seen
1910+
used_resnums.insert(next_unique);
1911+
return ResNum(next_unique++);
1912+
};
1913+
1914+
// Track the original residue number/name to detect residue boundaries.
1915+
// We compare against the original topology values (not remapped numbers).
1916+
// Store editors for the current residue and CutGroup so reparenting uses
1917+
// O(1) index lookups rather than O(n) name scans.
1918+
int current_orig_resnum = -1;
1919+
QString current_resname;
1920+
ResStructureEditor current_res;
1921+
CGStructureEditor current_cg;
18931922

18941923
for (int i = 0; i < atmnams.count(); ++i)
18951924
{
18961925
auto atom = moleditor.add(AtomNum(atmnums[i]));
18971926
atom = atom.rename(AtomName(atmnams[i]));
18981927

1899-
const ResNum resnum(resnums[i]);
1900-
1901-
if (completed_residues.contains(resnum))
1902-
{
1903-
auto res = moleditor.residue(resnum);
1904-
1905-
if (res.name().value() != resnams[i])
1906-
{
1907-
// different residue
1908-
res = moleditor.add(resnum);
1909-
res = res.rename(ResName(resnams[i]));
1910-
ncg += 1;
1911-
moleditor.add(CGName(QString::number(ncg)));
1912-
}
1928+
const int orig_resnum = resnums[i];
1929+
const QString &resnam = resnams[i];
19131930

1914-
atom = atom.reparent(res.index());
1915-
atom = atom.reparent(CGName(QString::number(ncg)));
1916-
}
1917-
else
1931+
if (orig_resnum != current_orig_resnum || resnam != current_resname)
19181932
{
1919-
auto res = moleditor.add(resnum);
1920-
res = res.rename(ResName(resnams[i]));
1921-
atom = atom.reparent(res.index());
1933+
// new residue
1934+
current_res = moleditor.add(unique_resnum(orig_resnum));
1935+
current_res = current_res.rename(ResName(resnam));
1936+
current_orig_resnum = orig_resnum;
1937+
current_resname = resnam;
19221938

19231939
ncg += 1;
1924-
auto cg = moleditor.add(CGName(QString::number(ncg)));
1925-
atom = atom.reparent(cg.index());
1926-
1927-
completed_residues.insert(resnum);
1940+
current_cg = moleditor.add(CGName(QString::number(ncg)));
19281941
}
1942+
1943+
atom = atom.reparent(current_res.index());
1944+
atom = atom.reparent(current_cg.index());
19291945
}
19301946

19311947
// we have created the molecule - now add in the coordinates/velocities as needed

0 commit comments

Comments
 (0)