Skip to content

Commit 81771ba

Browse files
authored
Merge pull request #381 from OpenBioSim/release_2025.3.0
Release 2025.3.0
2 parents 0d1b0c2 + 377e5c5 commit 81771ba

File tree

97 files changed

+3777
-732
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+3777
-732
lines changed

corelib/src/libs/SireIO/biosimspace.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1687,6 +1687,64 @@ namespace SireIO
16871687
return ion;
16881688
}
16891689

1690+
System setCoordinates(System &system, const QVector<QVector<float>> &coordinates, const bool is_lambda1, const PropertyMap &map)
1691+
{
1692+
// Make sure that the number of coordinates matches the number of atoms.
1693+
if (system.nAtoms() != coordinates.size())
1694+
{
1695+
throw SireError::incompatible_error(
1696+
QObject::tr("Number of coordinates (%1) does not match number of atoms in the system (%2)!")
1697+
.arg(coordinates.size())
1698+
.arg(system.nAtoms()),
1699+
CODELOC);
1700+
}
1701+
1702+
// Keep track of the current coordinate index.
1703+
unsigned coord_idx = 0;
1704+
1705+
// Loop over all molecules in the system in MolIdx order.
1706+
for (int i = 0; i < system.nMolecules(); ++i)
1707+
{
1708+
// Extract the molecule and make it editable.
1709+
auto molecule = system.molecule(MolIdx(i)).molecule().edit();
1710+
1711+
QString prop_name = "coordinates";
1712+
if (molecule.hasProperty("is_perturbable"))
1713+
{
1714+
if (is_lambda1)
1715+
prop_name = "coordinates1";
1716+
else
1717+
prop_name = "coordinates0";
1718+
}
1719+
1720+
// Get the coordinate property.
1721+
const auto coord_prop = map[prop_name];
1722+
1723+
// Loop over all atoms in the molecule.
1724+
for (int j = 0; j < molecule.nAtoms(); ++j)
1725+
{
1726+
// Construct the new coordinate.
1727+
const auto coord = Vector(coordinates[coord_idx + j][0],
1728+
coordinates[coord_idx + j][1],
1729+
coordinates[coord_idx + j][2]);
1730+
1731+
// Set the new coordinates.
1732+
molecule = molecule.edit()
1733+
.atom(AtomIdx(j))
1734+
.setProperty(coord_prop, coord)
1735+
.molecule();
1736+
}
1737+
1738+
// Update the molecule in the system.
1739+
system.update(molecule.commit());
1740+
1741+
// Update the coordinate index.
1742+
coord_idx += molecule.nAtoms();
1743+
}
1744+
1745+
return system;
1746+
}
1747+
16901748
Vector cross(const Vector &v0, const Vector &v1)
16911749
{
16921750
double nx = v0.y() * v1.z() - v0.z() * v1.y();

corelib/src/libs/SireIO/biosimspace.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,26 @@ namespace SireIO
351351
SIREIO_EXPORT Molecule createChlorineIon(
352352
const Vector &coords, const QString model, const PropertyMap &map = PropertyMap());
353353

354+
//! Set the coordinates of the entire system.
355+
/* \param system
356+
The molecular system of interest.
357+
358+
\param coordinates
359+
The new coordinates for the system.
360+
361+
\param is_lambda1
362+
Whether this is for the lambda = 1 state.
363+
364+
\param map
365+
A dictionary of user-defined molecular property names.
366+
367+
\retval system
368+
The system with updated coordinates.
369+
*/
370+
SIREIO_EXPORT SireSystem::System setCoordinates(
371+
SireSystem::System &system, const QVector<QVector<float>> &coordinates,
372+
const bool is_lambda1 = false, const PropertyMap &map = PropertyMap());
373+
354374
Vector cross(const Vector &v0, const Vector &v1);
355375
} // namespace SireIO
356376

@@ -366,6 +386,7 @@ SIRE_EXPOSE_FUNCTION(SireIO::updateAndPreserveOrder)
366386
SIRE_EXPOSE_FUNCTION(SireIO::updateCoordinatesAndVelocities)
367387
SIRE_EXPOSE_FUNCTION(SireIO::createSodiumIon)
368388
SIRE_EXPOSE_FUNCTION(SireIO::createChlorineIon)
389+
SIRE_EXPOSE_FUNCTION(SireIO::setCoordinates)
369390

370391
SIRE_END_HEADER
371392

corelib/src/libs/SireIO/moleculeparser.cpp

Lines changed: 156 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,7 +1669,35 @@ QStringList MoleculeParser::writeToFile(const QString &filename) const
16691669

16701670
QStringList written_files;
16711671

1672-
createDirectoryForFile(filename);
1672+
// Check whether list of trajectory frames have been specified via the map.
1673+
// If so, then the name will be prefixed with "frames_names:"
1674+
QStringList frame_names;
1675+
if (filename.contains("frames_names:"))
1676+
{
1677+
// Split the part of the string following "frames_names:" by commas to get
1678+
// individual frame names.
1679+
const QString frames_names_str = filename.section("frames_names:", 1);
1680+
frame_names = frames_names_str.split(",", Qt::SkipEmptyParts);
1681+
1682+
// Convert to absolute paths.
1683+
for (auto &frame_name : frame_names)
1684+
{
1685+
frame_name = QFileInfo(frame_name.trimmed()).absoluteFilePath();
1686+
}
1687+
1688+
if (frame_names.size() != frames_to_write.size())
1689+
{
1690+
throw SireError::program_bug(
1691+
QObject::tr("The number of frame names provided (%1) does not match the number of frames to write (%2).")
1692+
.arg(frame_names.size())
1693+
.arg(frames_to_write.size()),
1694+
CODELOC);
1695+
}
1696+
}
1697+
else
1698+
{
1699+
createDirectoryForFile(filename);
1700+
}
16731701

16741702
if (this->writingTrajectory() and this->isFrame())
16751703
{
@@ -1694,51 +1722,61 @@ QStringList MoleculeParser::writeToFile(const QString &filename) const
16941722

16951723
const int padding = QString::number(largest_frame).length();
16961724

1697-
// in this case, we are going to create a directory named after the
1698-
// filename, into which all the frames will be written
1699-
auto fileinfo = QFileInfo(filename);
1725+
QDir framedir;
1726+
QFileInfo fileinfo;
1727+
bool write_to_frame_dir = false;
17001728

1701-
if (fileinfo.exists())
1729+
if (frame_names.isEmpty())
17021730
{
1703-
// by default, we support overwriting of files - so remove this if it is a file
1704-
if (fileinfo.isDir())
1705-
{
1706-
_check_and_remove_frame_dir(fileinfo);
1731+
fileinfo = QFileInfo(filename);
1732+
write_to_frame_dir = true;
17071733

1708-
// rebuild so it is not cached
1709-
fileinfo = QFileInfo(filename);
1734+
// in this case, we are going to create a directory named after the
1735+
// filename, into which all the frames will be written
1736+
fileinfo = QFileInfo(filename);
17101737

1711-
if (fileinfo.exists())
1712-
throw SireError::file_error(QObject::tr(
1713-
"Could not write the trajectory for file '%1' as there "
1714-
"is already a directory with this name that contains "
1715-
"files that don't appear to have been created by sire.")
1716-
.arg(filename),
1717-
CODELOC);
1718-
}
1719-
else
1738+
if (fileinfo.exists())
17201739
{
1721-
auto dir = fileinfo.absoluteDir();
1722-
1723-
if (not dir.remove(fileinfo.fileName()))
1724-
throw SireError::file_error(QObject::tr(
1725-
"Could not write the trajectory for file '%1' as "
1726-
"we don't have permission to remove the existing "
1727-
"file with this name.")
1728-
.arg(filename),
1729-
CODELOC);
1740+
// by default, we support overwriting of files - so remove this if it is a file
1741+
if (fileinfo.isDir())
1742+
{
1743+
_check_and_remove_frame_dir(fileinfo);
1744+
1745+
// rebuild so it is not cached
1746+
fileinfo = QFileInfo(filename);
1747+
1748+
if (fileinfo.exists())
1749+
throw SireError::file_error(QObject::tr(
1750+
"Could not write the trajectory for file '%1' as there "
1751+
"is already a directory with this name that contains "
1752+
"files that don't appear to have been created by sire.")
1753+
.arg(filename),
1754+
CODELOC);
1755+
}
1756+
else
1757+
{
1758+
auto dir = fileinfo.absoluteDir();
1759+
1760+
if (not dir.remove(fileinfo.fileName()))
1761+
throw SireError::file_error(QObject::tr(
1762+
"Could not write the trajectory for file '%1' as "
1763+
"we don't have permission to remove the existing "
1764+
"file with this name.")
1765+
.arg(filename),
1766+
CODELOC);
1767+
}
17301768
}
1731-
}
17321769

1733-
QDir framedir(fileinfo.absoluteFilePath());
1770+
framedir = QDir(fileinfo.absoluteFilePath());
17341771

1735-
if (not framedir.mkpath("."))
1736-
throw SireError::file_error(QObject::tr(
1737-
"Could not create the directory into which to write the "
1738-
"trajectory for '%1'. Check that there is enough space "
1739-
"and you have the correct permissions.")
1740-
.arg(framedir.absolutePath()),
1741-
CODELOC);
1772+
if (not framedir.mkpath("."))
1773+
throw SireError::file_error(QObject::tr(
1774+
"Could not create the directory into which to write the "
1775+
"trajectory for '%1'. Check that there is enough space "
1776+
"and you have the correct permissions.")
1777+
.arg(framedir.absolutePath()),
1778+
CODELOC);
1779+
}
17421780

17431781
const auto suffix = fileinfo.completeSuffix();
17441782

@@ -1765,13 +1803,26 @@ QStringList MoleculeParser::writeToFile(const QString &filename) const
17651803
// construct a copy of this parser for this frame
17661804
auto parser = this->construct(thread_s, m);
17671805

1768-
// now write it to the file, numbered by the frame number and time
1769-
QString frame_filename = framedir.filePath(
1770-
"frame_" +
1771-
QString::number(i).rightJustified(padding, '0') +
1772-
"_" +
1773-
QString::number(time).replace(".", "-") +
1774-
"." + suffix);
1806+
QString frame_filename;
1807+
1808+
if (write_to_frame_dir)
1809+
{
1810+
// now write it to the file, numbered by the frame number and time
1811+
QDir framedir(fileinfo.absoluteFilePath());
1812+
1813+
frame_filename = framedir.filePath(
1814+
"frame_" +
1815+
QString::number(i).rightJustified(padding, '0') +
1816+
"_" +
1817+
QString::number(time).replace(".", "-") +
1818+
"." + suffix);
1819+
}
1820+
else
1821+
{
1822+
// write to the specified frame name
1823+
frame_filename = frame_names[i];
1824+
createDirectoryForFile(frame_filename);
1825+
}
17751826

17761827
parser.read().writeToFile(frame_filename);
17771828

@@ -1792,13 +1843,26 @@ QStringList MoleculeParser::writeToFile(const QString &filename) const
17921843
// construct a copy of this parser for this frame
17931844
auto parser = this->construct(s, m);
17941845

1795-
// now write it to the file, numbered by the frame number
1796-
QString frame_filename = framedir.filePath(
1797-
"frame_" +
1798-
QString::number(i).rightJustified(padding, '0') +
1799-
"_" +
1800-
QString::number(time).replace(".", "-") +
1801-
"." + suffix);
1846+
QString frame_filename;
1847+
1848+
if (write_to_frame_dir)
1849+
{
1850+
// now write it to the file, numbered by the frame number
1851+
QDir framedir(fileinfo.absoluteFilePath());
1852+
1853+
frame_filename = framedir.filePath(
1854+
"frame_" +
1855+
QString::number(i).rightJustified(padding, '0') +
1856+
"_" +
1857+
QString::number(time).replace(".", "-") +
1858+
"." + suffix);
1859+
}
1860+
else
1861+
{
1862+
// write to the specified frame name
1863+
frame_filename = frame_names[i];
1864+
createDirectoryForFile(frame_filename);
1865+
}
18021866

18031867
parser.read().writeToFile(frame_filename);
18041868

@@ -1810,7 +1874,14 @@ QStringList MoleculeParser::writeToFile(const QString &filename) const
18101874

18111875
// only return the directory name, as we can handle
18121876
// reading all the frames contained therein
1813-
written_files.append(framedir.absolutePath());
1877+
if (write_to_frame_dir)
1878+
{
1879+
written_files.append(framedir.absolutePath());
1880+
}
1881+
else
1882+
{
1883+
written_files.append(frame_names);
1884+
}
18141885
}
18151886
else
18161887
{
@@ -2452,6 +2523,38 @@ QStringList MoleculeParser::write(const System &system, const QString &filename,
24522523
}
24532524
}
24542525

2526+
// Check for a frame_names property in the map, which is used to control the naming of
2527+
// specific frames when writing trajectories.
2528+
if (map.specified("frame_names"))
2529+
{
2530+
const auto frame_names_property = map["frame_names"];
2531+
2532+
QStringList frame_names;
2533+
2534+
if (frame_names_property.hasSource())
2535+
{
2536+
frame_names = frame_names_property.source().split(",");
2537+
}
2538+
else
2539+
{
2540+
try
2541+
{
2542+
frame_names = frame_names_property.value().asA<StringProperty>().toString().split(",");
2543+
}
2544+
catch (...)
2545+
{
2546+
throw SireError::incompatible_error(
2547+
QObject::tr("The 'frame_names' property must be a StringProperty containing "
2548+
"a comma-separated list of filenames to use for each frame when "
2549+
"writing trajectories."),
2550+
CODELOC);
2551+
}
2552+
}
2553+
2554+
// add the special prefix to the first filename
2555+
filenames[0] = "frames_names:" + frame_names.join(",");
2556+
}
2557+
24552558
// now we have a list of filenames and associated formats, actually
24562559
// write the files
24572560
return ::pvt_write(system, filenames, fileformats, map);

corelib/src/libs/SireMM/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ set ( SIREMM_HEADERS
7171
intragroupff.h
7272
intraljff.h
7373
intrasoftcljff.h
74+
inversebondrestraints.h
7475
lj1264parameter.h
7576
ljfunction.h
7677
ljpair.h
@@ -167,6 +168,7 @@ set ( SIREMM_SOURCES
167168
intragroupff.cpp
168169
intraljff.cpp
169170
intrasoftcljff.cpp
171+
inversebondrestraints.cpp
170172
lj1264parameter.cpp
171173
ljpair.cpp
172174
ljparameter.cpp

0 commit comments

Comments
 (0)