diff --git a/README.rst b/README.rst index 451ab17aa..eea059fd2 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ **** -`Sire `__ +`Sire `__ **** .. image:: https://github.com/openbiosim/sire/workflows/Build/badge.svg @@ -121,7 +121,7 @@ For bug reports/sugguestions/complains please file an issue on Developers guide ---------------- -Please `visit the website `__ for information on how to +Please `visit the website `__ for information on how to develop applications using sire. GitHub actions diff --git a/corelib/build/INSTALL b/corelib/build/INSTALL index d0805b3e3..9c5003606 100644 --- a/corelib/build/INSTALL +++ b/corelib/build/INSTALL @@ -108,4 +108,4 @@ as that on which you compiled the binary. To get further help, please get in touch with the authors via the Sire mailing lists, or via the email links on the -Sire website, http://sire.openbiosim.org +Sire website, https://sire.openbiosim.org diff --git a/corelib/build/download_compile_sire.py b/corelib/build/download_compile_sire.py index c85a90c6e..baa20208f 100644 --- a/corelib/build/download_compile_sire.py +++ b/corelib/build/download_compile_sire.py @@ -1,4 +1,3 @@ - import os import sys import shutil @@ -6,18 +5,36 @@ for arg in sys.argv[1:]: if arg == "-h" or arg == "--help": print("python download_compile_sire.py OPTIONS") - print("\nScript to download and (optionally) compile and install Sire.") + print( + "\nScript to download and (optionally) compile and install Sire." + ) print("\nOptions:") - print(" -r / --rebuild Rebuild Sire from scratch every time you run this script.") - print(" --download-only Download Sire only. Don't compile or install.") - print(" -b / --branch Choose which branch of the source to download and install.") - print(" By default the 'trunk' (most up-to-date, stable version)") + print( + " -r / --rebuild Rebuild Sire from scratch every time you run this script." + ) + print( + " --download-only Download Sire only. Don't compile or install." + ) + print( + " -b / --branch Choose which branch of the source to download and install." + ) + print( + " By default the 'trunk' (most up-to-date, stable version)" + ) print(" will be downloaded.") - print(" -d / --directory Supply the directory into which the source will be downloaded") - print(" and Sire will be compiled and installed. By default, the") + print( + " -d / --directory Supply the directory into which the source will be downloaded" + ) + print( + " and Sire will be compiled and installed. By default, the" + ) print(" current directory will be used.") - print(" --no-execute Only show the commands that will be run. Don't actually run anything.") - print("\nSire is released under the GPL. For more information see http://sire.openbiosim.org") + print( + " --no-execute Only show the commands that will be run. Don't actually run anything." + ) + print( + "\nSire is released under the GPL. For more information see https://sire.openbiosim.org" + ) sys.exit(0) @@ -31,28 +48,37 @@ rundir = os.getcwd() branch = "trunk" -for i in range(1,len(sys.argv)): +for i in range(1, len(sys.argv)): arg = sys.argv[i] if arg == "--download-only": - print("\nSire will only be downloaded. It will not be compiled or installed.") + print( + "\nSire will only be downloaded. It will not be compiled or installed." + ) download_only = True elif arg == "-r" or arg == "--rebuild": - print("\nSire will be rebuilt from scratch after download. This will be quite slow...") + print( + "\nSire will be rebuilt from scratch after download. This will be quite slow..." + ) rebuild = True elif arg == "--no-execute": - print("\nThis script will only print the commands to be run. It won't actually run anything...") + print( + "\nThis script will only print the commands to be run. It won't actually run anything..." + ) no_execute = True elif arg == "-d" or arg == "--directory": - print("\nDownloading, compiling and installing Sire in directory %s" % sys.argv[i+1]) - rundir = sys.argv[i+1] + print( + "\nDownloading, compiling and installing Sire in directory %s" + % sys.argv[i + 1] + ) + rundir = sys.argv[i + 1] elif arg == "-b" or arg == "--branch": - print("\nUsing the %s branch of Sire" % sys.argv[i+1]) - branch = sys.argv[i+1] + print("\nUsing the %s branch of Sire" % sys.argv[i + 1]) + branch = sys.argv[i + 1] if must_exit: sys.exit(0) @@ -107,7 +133,12 @@ if no_execute: print("svn co %s ./python" % python) elif os.system("svn co %s ./python" % python) != 0: - if os.system("svn co %s ./python" % python.replace("python","python2")) != 0: + if ( + os.system( + "svn co %s ./python" % python.replace("python", "python2") + ) + != 0 + ): print("Failed to checkout the source for the python wrappers") sys.exit(-1) else: @@ -125,7 +156,9 @@ if rebuild: if os.path.exists("build"): - print("Rebuilding from scratch so removing existing build directory...") + print( + "Rebuilding from scratch so removing existing build directory..." + ) if no_execute: print("rm -rf ./build") @@ -161,7 +194,10 @@ if no_execute: print("nice cmake ../../corelib -DSIRE_INSTALL_PREFIX=%s" % sire_app) -elif os.system("nice cmake ../../corelib -DSIRE_INSTALL_PREFIX=%s" % sire_app) != 0: +elif ( + os.system("nice cmake ../../corelib -DSIRE_INSTALL_PREFIX=%s" % sire_app) + != 0 +): print("Could not successfully run cmake on corelib") sys.exit(-1) diff --git a/corelib/build/templates/header b/corelib/build/templates/header index 9b02d8cf0..f1f3426d4 100644 --- a/corelib/build/templates/header +++ b/corelib/build/templates/header @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/share/build/install_sire.sh b/corelib/share/build/install_sire.sh index 051f78c21..fe8679330 100755 --- a/corelib/share/build/install_sire.sh +++ b/corelib/share/build/install_sire.sh @@ -48,7 +48,7 @@ if [ ! -d "$install_dir" ]; then echo "* WARNING - INSTALLATION FAILED" echo "* PLEASE CHECK THAT YOU CAN WRITE TO $install_dir" echo "* IF YOU CAN, PLEASE CONTACT THE DEVELOPERS AT" - echo "* http://sire.openbiosim.org" + echo "* https://sire.openbiosim.org" echo "************************************************" echo " " exit -1 diff --git a/corelib/src/apps/sire/main.cpp b/corelib/src/apps/sire/main.cpp index 690a7420d..bb206a109 100644 --- a/corelib/src/apps/sire/main.cpp +++ b/corelib/src/apps/sire/main.cpp @@ -893,7 +893,7 @@ int main(int argc, char **argv) "under certain conditions; type \"sire -l\" or \"sire --license\"\n" "for warranty and licensing conditions.\n\n" "For more information and to contact the authors please visit\n\n" - "http://sire.openbiosim.org")); + "https://sire.openbiosim.org")); printBox(QObject::tr("%4@%5: Starting primary node (%1 of %2): nThreads()=%3") .arg(Cluster::getRank()) diff --git a/corelib/src/libs/SireBase/progressbar.cpp b/corelib/src/libs/SireBase/progressbar.cpp index 978169fdd..a50ae866f 100644 --- a/corelib/src/libs/SireBase/progressbar.cpp +++ b/corelib/src/libs/SireBase/progressbar.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireBase/progressbar.h b/corelib/src/libs/SireBase/progressbar.h index afce1e48c..3ae7fd4e3 100644 --- a/corelib/src/libs/SireBase/progressbar.h +++ b/corelib/src/libs/SireBase/progressbar.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireBase/properties.cpp b/corelib/src/libs/SireBase/properties.cpp index d457dcb40..e94fcb20a 100644 --- a/corelib/src/libs/SireBase/properties.cpp +++ b/corelib/src/libs/SireBase/properties.cpp @@ -36,6 +36,8 @@ #include "SireStream/datastream.h" #include "SireStream/shareddatastream.h" +#include + #include using namespace SireBase; @@ -61,22 +63,21 @@ namespace SireBase bool operator==(const PropertiesData &other) const; bool operator!=(const PropertiesData &other) const; + bool hasMetadata() const; + bool hasPropsMetadata() const; + bool hasLinks() const; + /** The metadata for this set of properties itself */ - Properties metadata; + std::shared_ptr metadata; /** The collection of properties, indexed by name */ QHash properties; /** The metadata for each property, indexed by name */ - QHash props_metadata; - - static const SharedDataPointer &getNullData(); + std::shared_ptr> props_metadata; - private: - /** Constructor used only once to create the global-null PropertiesData */ - PropertiesData(bool) : metadata(false) - { - } + /** The set of linked properties */ + std::shared_ptr> prop_links; }; } // namespace detail @@ -85,32 +86,19 @@ namespace SireBase using namespace SireBase::detail; -static SharedDataPointer nulldata_ptr; - -/** Return a shared pointer to the null object - this is not - as trivial as it sounds, as there is a circular reference! */ -const SharedDataPointer &PropertiesData::getNullData() -{ - if (nulldata_ptr.constData() == 0) - { - // use a constructor where the circular reference is broken - PropertiesData *ptr = new PropertiesData(false); - - nulldata_ptr = ptr; - - // fix the reference - ptr->metadata = Properties(); - } - - return nulldata_ptr; -} - /** Serialise to a binary data stream */ QDataStream &operator<<(QDataStream &ds, const PropertiesData &props) { SharedDataStream sds(ds); - sds << props.metadata; + if (props.metadata.get() == 0) + { + sds << Properties(); + } + else + { + sds << *(props.metadata); + } sds << quint32(props.properties.count()); @@ -120,12 +108,19 @@ QDataStream &operator<<(QDataStream &ds, const PropertiesData &props) sds << it.key() << it.value(); } - sds << quint32(props.props_metadata.count()); - - for (QHash::const_iterator it = props.props_metadata.constBegin(); - it != props.props_metadata.constEnd(); ++it) + if (props.props_metadata.get() == 0) { - sds << it.key() << it.value(); + sds << quint32(0); + } + else + { + sds << quint32(props.props_metadata->count()); + + for (QHash::const_iterator it = props.props_metadata->constBegin(); + it != props.props_metadata->constEnd(); ++it) + { + sds << it.key() << it.value(); + } } return ds; @@ -136,7 +131,18 @@ QDataStream &operator>>(QDataStream &ds, PropertiesData &props) { SharedDataStream sds(ds); - sds >> props.metadata; + Properties metadata; + + sds >> metadata; + + if (metadata.isEmpty()) + { + props.metadata.reset(); + } + else + { + props.metadata.reset(new Properties(metadata)); + } quint32 nprops; @@ -161,11 +167,18 @@ QDataStream &operator>>(QDataStream &ds, PropertiesData &props) sds >> nprops; - props.props_metadata.clear(); + if (nprops <= 0) + { + props.props_metadata.reset(); + } + else + { + props.props_metadata.reset(new QHash()); + } if (nprops > 0) { - props.props_metadata.reserve(nprops); + props.props_metadata->reserve(nprops); for (quint32 i = 0; i < nprops; ++i) { @@ -174,7 +187,7 @@ QDataStream &operator>>(QDataStream &ds, PropertiesData &props) sds >> key >> value; - props.props_metadata.insert(key, value); + props.props_metadata->insert(key, value); } } @@ -188,8 +201,8 @@ PropertiesData::PropertiesData() /** Copy constructor */ PropertiesData::PropertiesData(const PropertiesData &other) - : metadata(other.metadata), properties(other.properties), props_metadata(other.props_metadata) { + this->operator=(other); } /** Destructor */ @@ -202,24 +215,108 @@ PropertiesData &PropertiesData::operator=(const PropertiesData &other) { if (this != &other) { - metadata = other.metadata; properties = other.properties; - props_metadata = other.props_metadata; + + if (other.metadata.get() != 0) + { + metadata.reset(new Properties(*(other.metadata))); + } + + if (other.props_metadata.get() != 0) + { + props_metadata.reset(new QHash(*(other.props_metadata))); + } + + if (other.prop_links.get() != 0) + { + prop_links.reset(new QHash(*(other.prop_links))); + } } return *this; } +bool PropertiesData::hasMetadata() const +{ + if (metadata.get() != 0) + return not metadata->isEmpty(); + else + return false; +} + +bool PropertiesData::hasPropsMetadata() const +{ + if (props_metadata.get() != 0) + return not props_metadata->isEmpty(); + else + return false; +} + +bool PropertiesData::hasLinks() const +{ + if (prop_links.get() != 0) + return not prop_links->isEmpty(); + else + return false; +} + /** Comparison operator */ bool PropertiesData::operator==(const PropertiesData &other) const { - return metadata == other.metadata and properties == other.properties and props_metadata == other.props_metadata; + if (this == &other) + return true; + + if (properties == other.properties) + { + if (this->hasMetadata()) + { + if (other.hasMetadata()) + { + if (*metadata != *(other.metadata)) + return false; + } + else + return false; + } + else if (other.hasMetadata()) + return false; + + if (this->hasPropsMetadata()) + { + if (other.hasPropsMetadata()) + { + if (*props_metadata != *(other.props_metadata)) + return false; + } + else + return false; + } + else if (other.hasPropsMetadata()) + return false; + + if (this->hasLinks()) + { + if (other.hasLinks()) + { + if (*prop_links != *(other.prop_links)) + return false; + } + else + return false; + } + else if (other.hasLinks()) + return false; + + return true; + } + else + return false; } /** Comparison operator */ bool PropertiesData::operator!=(const PropertiesData &other) const { - return metadata != other.metadata or properties != other.properties or props_metadata != other.props_metadata; + return not this->operator==(other); } ///////////// @@ -231,7 +328,12 @@ static const RegisterMetaType r_props; /** Serialise to a binary data stream */ QDataStream &operator<<(QDataStream &ds, const Properties &props) { - writeHeader(ds, r_props, 1); + const bool has_links = props.hasLinks(); + + if (has_links) + writeHeader(ds, r_props, 2); + else + writeHeader(ds, r_props, 1); SharedDataStream sds(ds); @@ -241,9 +343,16 @@ QDataStream &operator<<(QDataStream &ds, const Properties &props) // properties object - this is to prevent circular references sds << qint32(0); } + else if (has_links) + { + sds << qint32(1) << props.d + << *(props.d->prop_links) + << static_cast(props); + } else { - sds << qint32(1) << props.d << static_cast(props); + sds << qint32(1) << props.d + << static_cast(props); } return ds; @@ -254,7 +363,30 @@ QDataStream &operator>>(QDataStream &ds, Properties &props) { VersionID v = readHeader(ds, r_props); - if (v == 1) + if (v == 2) + { + SharedDataStream sds(ds); + + qint32 not_empty; + + sds >> not_empty; + + if (not_empty) + { + QHash links; + + sds >> props.d >> links >> static_cast(props); + + if (not props.isEmpty()) + props.d->prop_links.reset(new QHash(links)); + } + else + { + // this is an empty Properties object + props = Properties(); + } + } + else if (v == 1) { SharedDataStream sds(ds); @@ -273,18 +405,18 @@ QDataStream &operator>>(QDataStream &ds, Properties &props) } } else - throw version_error(v, "1", r_props, CODELOC); + throw version_error(v, "1, 2", r_props, CODELOC); return ds; } -/** Private constructor used to avoid the problem of the circular reference! */ +/** Private constructor - not used any more */ Properties::Properties(bool) : ConcreteProperty(), d(0) { } /** Null constructor - construct an empty set of properties */ -Properties::Properties() : ConcreteProperty(), d(PropertiesData::getNullData()) +Properties::Properties() : ConcreteProperty(), d(0) { } @@ -310,63 +442,190 @@ Properties &Properties::operator=(const Properties &other) /** Comparison operator */ bool Properties::operator==(const Properties &other) const { - return d == other.d or *d == *(other.d); + if (this->isEmpty() or other.isEmpty()) + { + return d == other.d; + } + else + { + return d == other.d or *d == *(other.d); + } } /** Comparison operator */ bool Properties::operator!=(const Properties &other) const { - return d != other.d and *d != *(other.d); + return not this->operator==(other); } /** Return whether this is empty (has no values) */ bool Properties::isEmpty() const { - return d->properties.isEmpty(); + return d.constData() == 0; +} + +/** Add a link from the property 'key' to the property 'linked_property'. + * The linked_property will be returned if there is no property + * called 'key' in this set. + * + * Note that the linked property must already be contained in this set. + */ +void Properties::addLink(const QString &key, const QString &linked_property) +{ + if (key.simplified().isEmpty()) + throw SireError::invalid_key(QObject::tr( + "You can't use an empty string as a link name"), + CODELOC); + + this->assertContainsProperty(linked_property); + + if (d->properties.contains(key)) + throw SireError::invalid_key(QObject::tr( + "You cannot create the link '%1' as a property with this name " + "already exists.") + .arg(key), + CODELOC); + + if (d->prop_links.get() == 0) + d->prop_links.reset(new QHash()); + + d->prop_links->insert(key, linked_property); +} + +/** Remove the link associated with the key 'key' */ +void Properties::removeLink(const QString &key) +{ + if (not this->isEmpty()) + { + if (d->prop_links.get() == 0) + return; + + d->prop_links->remove(key); + + if (d->prop_links->isEmpty()) + d->prop_links.reset(); + } +} + +/** Remove all property links from this set */ +void Properties::removeAllLinks() +{ + if (not this->isEmpty()) + { + d->prop_links.reset(); + } +} + +/** Return whether or not there are any property links */ +bool Properties::hasLinks() const +{ + if (this->isEmpty()) + return false; + else + return d->prop_links.get() != 0; +} + +/** Return all of the property links */ +QHash Properties::getLinks() const +{ + if (not this->isEmpty()) + { + if (d->prop_links.get() != 0) + return *(d->prop_links); + } + + return QHash(); } /** Return the keys for all of the properties in this set */ QStringList Properties::propertyKeys() const { - return d->properties.keys(); + if (this->isEmpty()) + return QStringList(); + else + return d->properties.keys(); } /** Return an iterator pointing to the first property in this set */ Properties::const_iterator Properties::begin() const { - return d->properties.begin(); + if (this->isEmpty()) + return Properties::const_iterator(); + else + return d->properties.begin(); } /** Return an iterator pointing to the first property in this set */ Properties::const_iterator Properties::constBegin() const { - return d->properties.constBegin(); + if (this->isEmpty()) + return Properties::const_iterator(); + else + return d->properties.constBegin(); +} + +/** Internal function used to get the linked property name + * for 'key'. This returns an empty string if there + * is no link + */ +QString Properties::_getLink(const QString &key) const +{ + if (this->isEmpty()) + return QString(); + else if (d->prop_links.get() == 0) + return QString(); + else + return d->prop_links->value(key, QString()); } /** Return an iterator pointing to the property with key 'key', or Properties::end() if there is no such property */ Properties::const_iterator Properties::find(const QString &key) const { - return d->properties.find(key); + if (this->isEmpty()) + return Properties::const_iterator(); + else + { + auto it = d->properties.find(key); + + if (it == d->properties.constEnd()) + { + auto linked = this->_getLink(key); + + if (not linked.isEmpty()) + it = d->properties.find(linked); + } + + return it; + } } /** Return an iterator pointing to the property with key 'key', or Properties::end() if there is no such property */ Properties::const_iterator Properties::constFind(const QString &key) const { - return d->properties.constFind(key); + if (this->isEmpty()) + return Properties::const_iterator(); + else + return d->properties.constFind(key); } /** Return an iterator pointing one beyond the last property in this set */ Properties::const_iterator Properties::end() const { - return d->properties.end(); + if (this->isEmpty()) + return Properties::const_iterator(); + else + return d->properties.end(); } /** Return an iterator pointing one beyond the last property in this set */ Properties::const_iterator Properties::constEnd() const { - return d->properties.end(); + if (this->isEmpty()) + return Properties::const_iterator(); + else + return d->properties.end(); } /** Assert that this set contains a property with key 'key' @@ -375,24 +634,53 @@ Properties::const_iterator Properties::constEnd() const */ void Properties::assertContainsProperty(const PropertyName &key) const { - if (key.hasSource() and not d->properties.contains(key.source()) and not key.hasDefaultValue()) + if (this->isEmpty()) { - throw SireBase::missing_property(QObject::tr("There is no property with key \"%1\". Available keys are ( %2 ).") - .arg(key.source(), this->propertyKeys().join(", ")), + throw SireBase::missing_property(QObject::tr("There is no property with key \"%1\" as this set of properties is empty.") + .arg(key.source()), CODELOC); } + else if (key.hasSource() and not d->properties.contains(key.source()) and not key.hasDefaultValue()) + { + auto linked = this->_getLink(key.source()); + + if (linked.isEmpty() or not d->properties.contains(linked)) + throw SireBase::missing_property(QObject::tr("There is no property with key \"%1\". Available keys are ( %2 ).") + .arg(key.source(), this->propertyKeys().join(", ")), + CODELOC); + } } /** Return the list of metadata keys */ QStringList Properties::metadataKeys() const { - return d->metadata.propertyKeys(); + if (this->isEmpty()) + return QStringList(); + else if (d->metadata.get() == 0) + return QStringList(); + else + return d->metadata->propertyKeys(); } /** Assert that this contains the metadata at metakey 'metakey' */ void Properties::assertContainsMetadata(const PropertyName &metakey) const { - return d->metadata.assertContainsProperty(metakey); + if (this->isEmpty()) + { + throw SireBase::missing_property(QObject::tr("There is no metadata with metakey \"%1\" as this set of properties is empty.") + .arg(metakey.source()), + CODELOC); + } + else if (d->metadata.get() == 0) + { + throw SireBase::missing_property(QObject::tr("There is no metadata with metakey \"%1\" as there is no metadata.") + .arg(metakey.source()), + CODELOC); + } + else + { + return d->metadata->assertContainsProperty(metakey); + } } /** Return the list of metadata keys for the property with key 'key' @@ -426,13 +714,26 @@ void Properties::assertContainsMetadata(const PropertyName &key, const PropertyN /** Return whether or not this contains a property with key 'key' */ bool Properties::hasProperty(const PropertyName &key) const { - return key.hasValue() or d->properties.contains(key.source()) or key.hasDefaultValue(); + if (this->isEmpty()) + return false; + else if (key.hasValue() or d->properties.contains(key.source()) or key.hasDefaultValue()) + return true; + else + { + auto linked = this->_getLink(key.source()); + return (not linked.isEmpty()) and d->properties.contains(linked); + } } /** Return whether or not this contains the metadata with metakey 'metakey' */ bool Properties::hasMetadata(const PropertyName &metakey) const { - return d->metadata.hasProperty(metakey); + if (this->isEmpty()) + return false; + else if (d->metadata.get() == 0) + return false; + else + return d->metadata->hasProperty(metakey); } /** Return the property with key 'key' @@ -445,6 +746,17 @@ const Property &Properties::operator[](const PropertyName &key) const return key.value(); else { + if (this->isEmpty()) + { + if (key.hasDefaultValue()) + return key.value(); + else + throw SireBase::missing_property(QObject::tr("There is no property with name \"%1\" " + "as there are no properties in this set.") + .arg(key.source()), + CODELOC); + } + QHash::const_iterator it = d->properties.constFind(key.source()); if (it == d->properties.constEnd()) @@ -452,24 +764,37 @@ const Property &Properties::operator[](const PropertyName &key) const if (key.hasDefaultValue()) return key.value(); else - throw SireBase::missing_property(QObject::tr("There is no property with name \"%1\". " - "Available properties are [ %2 ].") - .arg(key.source(), this->propertyKeys().join(", ")), - CODELOC); + { + auto linked = this->_getLink(key.source()); + + if (not linked.isEmpty()) + it = d->properties.constFind(linked); + + if (it == d->properties.constEnd()) + throw SireBase::missing_property(QObject::tr("There is no property with name \"%1\". " + "Available properties are [ %2 ].") + .arg(key.source(), this->propertyKeys().join(", ")), + CODELOC); + } } return *it; } } +static Properties null_properties; + /** Return all of the metadata associated with this properties object */ const Properties &Properties::allMetadata() const { - return d->metadata; + if (this->isEmpty()) + return null_properties; + else if (d->metadata.get() == 0) + return null_properties; + else + return *(d->metadata); } -static Properties null_properties; - /** Return the metadata for the property with key 'key' \throw SireBase::missing_property @@ -478,22 +803,21 @@ const Properties &Properties::allMetadata(const PropertyName &key) const { if (key.hasValue()) return null_properties; + + this->assertContainsProperty(key); + + if (d->props_metadata.get() == 0) + { + return null_properties; + } else { - QHash::const_iterator it = d->props_metadata.constFind(key.source()); - - if (it == d->props_metadata.constEnd()) - { - if (key.hasDefaultValue()) - return null_properties; - else - throw SireBase::missing_property(QObject::tr("There is no property with name \"%1\". " - "Available properties are [ %2 ].") - .arg(key.source(), propertyKeys().join(", ")), - CODELOC); - } + auto it = d->props_metadata->constFind(key.source()); - return *it; + if (it == d->props_metadata->constEnd()) + return null_properties; + else + return it.value(); } } @@ -531,6 +855,10 @@ const Property &Properties::property(const PropertyName &key, const Property &de { return key.value(); } + else if (this->isEmpty()) + { + return default_value; + } else { auto it = d->properties.constFind(key.source()); @@ -538,7 +866,19 @@ const Property &Properties::property(const PropertyName &key, const Property &de if (it != d->properties.constEnd()) return it.value(); else + { + auto linked = this->_getLink(key.source()); + + if (not linked.isEmpty()) + { + it = d->properties.constFind(linked); + + if (it != d->properties.constEnd()) + return it.value(); + } + return default_value; + } } } @@ -550,7 +890,23 @@ const Property &Properties::property(const PropertyName &key, const Property &de */ const Property &Properties::metadata(const PropertyName &metakey) const { - return d->metadata.property(metakey); + if (this->isEmpty()) + { + if (metakey.hasValue()) + return metakey.value(); + } + else if (d->metadata.get() == 0) + { + if (metakey.hasValue()) + return metakey.value(); + } + else + { + return d->metadata->property(metakey); + } + + this->assertContainsMetadata(metakey); + return metakey.value(); } /** Return the metadata at metakey 'metakey' - note that if 'metakey' @@ -560,7 +916,22 @@ const Property &Properties::metadata(const PropertyName &metakey) const returned */ const Property &Properties::metadata(const PropertyName &metakey, const Property &default_value) const { - return d->metadata.property(metakey, default_value); + if (this->isEmpty()) + { + if (metakey.hasValue()) + return metakey.value(); + } + else if (d->metadata.get() == 0) + { + if (metakey.hasValue()) + return metakey.value(); + } + else + { + return d->metadata->property(metakey, default_value); + } + + return default_value; } /** Return the metadata at metakey 'metakey' that is associated with @@ -591,10 +962,32 @@ void Properties::setProperty(const QString &key, const Property &value, bool cle if (key.isEmpty()) throw SireError::invalid_arg(QObject::tr("You cannot insert a property with an empty key!"), CODELOC); + if (this->isEmpty()) + { + d = new PropertiesData(); + } + d->properties.insert(key, value); - if (clear_metadata or not d->props_metadata.contains(key)) - d->props_metadata.insert(key, Properties()); + // remove any link associated with this key name + if (d->prop_links.get() != 0) + { + d->prop_links->remove(key); + + if (d->prop_links->isEmpty()) + d->prop_links.reset(); + } + + if (d->props_metadata.get() != 0) + { + if (clear_metadata or not d->props_metadata->contains(key)) + { + d->props_metadata->remove(key); + + if (d->props_metadata->isEmpty()) + d->props_metadata.reset(); + } + } } void Properties::setProperty(const QString &key, const Property &value) @@ -606,7 +999,17 @@ void Properties::setProperty(const QString &key, const Property &value) This replaces any existing metadata with this metakey */ void Properties::setMetadata(const QString &metakey, const Property &value) { - d->metadata.setProperty(metakey, value); + if (this->isEmpty()) + { + d = new PropertiesData(); + } + + if (d->metadata.get() == 0) + { + d->metadata.reset(new Properties()); + } + + d->metadata->setProperty(metakey, value); } /** Set the metadata at metakey 'metakey' for the property at key 'key'. @@ -614,7 +1017,13 @@ void Properties::setMetadata(const QString &metakey, const Property &value) void Properties::setMetadata(const QString &key, const QString &metakey, const Property &value) { this->assertContainsProperty(key); - d->props_metadata.find(key)->setProperty(metakey, value); + + if (d->props_metadata.get() == 0) + { + d->props_metadata.reset(new QHash()); + } + + d->props_metadata->find(key)->setProperty(metakey, value); } /** Remove the property with key 'key' and all of its metadata */ @@ -623,7 +1032,37 @@ void Properties::removeProperty(const QString &key) if (this->hasProperty(key)) { d->properties.remove(key); - d->props_metadata.remove(key); + + if (d->props_metadata.get() != 0) + { + d->props_metadata->remove(key); + + if (d->props_metadata->isEmpty()) + { + d->props_metadata.reset(); + } + } + + if (d->prop_links.get() != 0) + { + QMutableHashIterator it(*(d->prop_links)); + + while (it.hasNext()) + { + it.next(); + + if (it.value() == key) + it.remove(); + } + + if (d->prop_links->isEmpty()) + d->prop_links.reset(); + } + + if (d->properties.isEmpty() and d->metadata.get() == 0) + { + d = 0; + } } } @@ -632,14 +1071,33 @@ void Properties::removeMetadata(const QString &metakey) { if (this->hasMetadata(metakey)) { - d->metadata.removeProperty(metakey); + d->metadata->removeProperty(metakey); + + if (d->metadata->isEmpty()) + { + d->metadata.reset(); + + if (d->properties.isEmpty()) + { + d = 0; + } + } } } /** Remove all of the top-level metadata */ void Properties::removeAllMetadata() { - d->metadata.clear(); + if (this->isEmpty()) + return; + + if (d->metadata.get() != 0) + { + d->metadata->clear(); + + if (d->properties.isEmpty()) + d = 0; + } } /** Remove the metadata at metakey 'metakey' for the @@ -648,7 +1106,12 @@ void Properties::removeMetadata(const QString &key, const QString &metakey) { if (this->hasMetadata(key, metakey)) { - d->props_metadata.find(key)->removeProperty(metakey); + d->props_metadata->find(key)->removeProperty(metakey); + + if (d->props_metadata->isEmpty()) + { + d->props_metadata.reset(); + } } } @@ -657,13 +1120,21 @@ void Properties::removeMetadata(const QString &key, const QString &metakey) void Properties::removeAllMetadata(const QString &key) { if (this->hasProperty(key)) - d->props_metadata.insert(key, Properties()); + { + if (d->props_metadata.get() != 0) + { + d->props_metadata->remove(key); + + if (d->props_metadata->isEmpty()) + d->props_metadata.reset(); + } + } } /** Completely clear this object of all properties and metadata */ void Properties::clear() { - this->operator=(Properties()); + d = 0; } /** Return the type name of the property at key 'key' @@ -697,19 +1168,22 @@ const char *Properties::metadataType(const PropertyName &key, const PropertyName /** Return the number of properties in this set */ int Properties::count() const { - return d->properties.count(); + if (this->isEmpty()) + return 0; + else + return d->properties.count(); } /** Return the number of properties in this set */ int Properties::size() const { - return d->properties.count(); + return this->count(); } /** Return the number of properties in this set */ int Properties::nProperties() const { - return d->properties.count(); + return this->count(); } /** Return a string representation of this set of properties */ @@ -736,12 +1210,27 @@ QString Properties::toString() const */ Property *Properties::getEditableProperty(const QString &key) { + if (this->isEmpty()) + return 0; + auto it = d->properties.find(key); if (it != d->properties.end()) return it.value().data(); else + { + auto linked = this->_getLink(key); + + if (not linked.isEmpty()) + { + it = d->properties.find(linked); + + if (it != d->properties.end()) + return it.value().data(); + } + return 0; + } } /** Update the passed property to have the value 'value'. This does diff --git a/corelib/src/libs/SireBase/properties.h b/corelib/src/libs/SireBase/properties.h index 7ee594fd4..96297345a 100644 --- a/corelib/src/libs/SireBase/properties.h +++ b/corelib/src/libs/SireBase/properties.h @@ -141,6 +141,13 @@ namespace SireBase bool updatePropertyFrom(const QString &key, const V &values, bool auto_add = true); + void addLink(const QString &key, const QString &linked_property); + void removeLink(const QString &key); + void removeAllLinks(); + + bool hasLinks() const; + QHash getLinks() const; + void setProperty(const QString &key, const Property &value); void setProperty(const QString &key, const Property &value, bool clear_metadata); @@ -183,6 +190,8 @@ namespace SireBase void assertContainsMetadata(const PropertyName &key, const PropertyName &metakey) const; private: + QString _getLink(const QString &key) const; + Properties(bool); Property *getEditableProperty(const QString &key); diff --git a/corelib/src/libs/SireBase/releasegil.cpp b/corelib/src/libs/SireBase/releasegil.cpp index 4bf1e429a..10d19577e 100644 --- a/corelib/src/libs/SireBase/releasegil.cpp +++ b/corelib/src/libs/SireBase/releasegil.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireBase/releasegil.h b/corelib/src/libs/SireBase/releasegil.h index afb5f98f7..8c9260891 100644 --- a/corelib/src/libs/SireBase/releasegil.h +++ b/corelib/src/libs/SireBase/releasegil.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireCAS/lambdaschedule.cpp b/corelib/src/libs/SireCAS/lambdaschedule.cpp index abcd84b5c..78b9bed63 100644 --- a/corelib/src/libs/SireCAS/lambdaschedule.cpp +++ b/corelib/src/libs/SireCAS/lambdaschedule.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ @@ -174,30 +174,75 @@ QString LambdaSchedule::toString() const .arg(lines.join("\n")); } +/** Return a LambdaSchedule that represents a standard morph, + * where every forcefield parameter is scaled by + * (1-:lambda:).initial + :lambda:.final + */ +LambdaSchedule LambdaSchedule::standard_morph() +{ + LambdaSchedule l; + l.addMorphStage(); + return l; +} + +/** Return a LambdaSchedule that represents a central morph + * stage that is sandwiched between a charge descaling, + * and a charge rescaling stage. The first stage scales + * the "charge" lever down from 1.0 to `scale`. This + * is followed by a standard morph stage using the + * descaled charges. This the finished with a recharging + * stage that restores the charges back to their + * original values. + */ +LambdaSchedule LambdaSchedule::charge_scaled_morph(double scale) +{ + LambdaSchedule l; + l.addMorphStage(); + l.addChargeScaleStages(scale); + + return l; +} + Symbol LambdaSchedule::lambda_symbol("λ"); Symbol LambdaSchedule::initial_symbol("initial"); Symbol LambdaSchedule::final_symbol("final"); +/** Return the symbol used to represent the :lambda: coordinate. + * This symbol is used to represent the per-stage :lambda: + * variable that goes from 0.0-1.0 within that stage. + */ Symbol LambdaSchedule::lam() { return lambda_symbol; } +/** Return the symbol used to represent the initial + * (:lambda:=0) value of the forcefield parameter + */ Symbol LambdaSchedule::initial() { return initial_symbol; } +/** Return the symbol used to represent the final + * (:lambda:=1) value of the forcefield parameter + */ Symbol LambdaSchedule::final() { return final_symbol; } +/** Set the value of a constant that may be used in any + * of the stage equations. + */ SireCAS::Symbol LambdaSchedule::setConstant(const QString &constant, double value) { return this->setConstant(this->getConstantSymbol(constant), value); } +/** Set the value of a constant that may be used in any + * of the stage equations. + */ SireCAS::Symbol LambdaSchedule::setConstant(const SireCAS::Symbol &constant, double value) { @@ -205,21 +250,33 @@ SireCAS::Symbol LambdaSchedule::setConstant(const SireCAS::Symbol &constant, return constant; } +/** Return the value of the passed constant that may be + * used in any of the stage equations + */ double LambdaSchedule::getConstant(const QString &constant) { return this->getConstant(this->getConstantSymbol(constant)); } +/** Return the value of the passed constant that may be + * used in any of the stage equations + */ double LambdaSchedule::getConstant(const SireCAS::Symbol &constant) const { return this->constant_values.value(constant); } +/** Get the Symbol used to represent the named constant 'constant' */ SireCAS::Symbol LambdaSchedule::getConstantSymbol(const QString &constant) const { return SireCAS::Symbol(constant); } +/** Add a lever to the schedule. This is only useful if you want to + * plot how the equations would affect the lever. Levers will be + * automatically added by any perturbation run that needs them, + * so you don't need to add them manually yourself. + */ void LambdaSchedule::addLever(const QString &lever) { if (this->lever_names.contains(lever)) @@ -228,6 +285,11 @@ void LambdaSchedule::addLever(const QString &lever) this->lever_names.append(lever); } +/** Add some levers to the schedule. This is only useful if you want to + * plot how the equations would affect the lever. Levers will be + * automatically added by any perturbation run that needs them, + * so you don't need to add them manually yourself. + */ void LambdaSchedule::addLevers(const QStringList &levers) { for (const auto &lever : levers) @@ -237,6 +299,10 @@ void LambdaSchedule::addLevers(const QStringList &levers) } } +/** Remove a lever from the schedule. This will not impact any + * perturbation runs that use this schedule, as any missing + * levers will be re-added. + */ void LambdaSchedule::removeLever(const QString &lever) { if (not this->lever_names.contains(lever)) @@ -249,6 +315,10 @@ void LambdaSchedule::removeLever(const QString &lever) this->stage_names.removeAt(idx); } +/** Remove some levers from the schedule. This will not impact any + * perturbation runs that use this schedule, as any missing + * levers will be re-added. + */ void LambdaSchedule::removeLevers(const QStringList &levers) { for (const auto &lever : levers) @@ -257,31 +327,51 @@ void LambdaSchedule::removeLevers(const QStringList &levers) } } +/** Return the number of levers that have been explicitly added + * to the schedule. Note that levers will be automatically added + * by any perturbation run that needs them, so you don't normally + * need to manage them manually yourself. + */ int LambdaSchedule::nLevers() const { return this->lever_names.count(); } +/** Return all of the levers that have been explicitly added + * to the schedule. Note that levers will be automatically added + * by any perturbation run that needs them, so you don't normally + * need to manage them manually yourself. + */ QStringList LambdaSchedule::getLevers() const { return this->lever_names; } +/** Return the number of stages in this schedule */ int LambdaSchedule::nStages() const { return this->stage_names.count(); } +/** Return the names of all of the stages in this schedule, in + * the order they will be performed + */ QStringList LambdaSchedule::getStages() const { return this->stage_names; } +/** Clamp and return the passed lambda value so that it is between a valid + * range for this schedule (typically between [0.0-1.0] inclusive). + */ double LambdaSchedule::clamp(double lambda_value) const { return std::max(0.0, std::min(lambda_value, 1.0)); } +/** Internal function used to resolve the global value of lambda down + * to a stage number and stage-lambda value + */ std::tuple LambdaSchedule::resolve_lambda(double lambda_value) const { if (this->nStages() == 0) @@ -310,6 +400,9 @@ std::tuple LambdaSchedule::resolve_lambda(double lambda_value) cons return std::tuple(int(stage), resolved - stage); } +/** Return the name of the stage that controls the forcefield parameters + * at the global value of :lambda: equal to `lambda_value` + */ QString LambdaSchedule::getStage(double lambda_value) const { auto resolved = this->resolve_lambda(lambda_value); @@ -317,6 +410,9 @@ QString LambdaSchedule::getStage(double lambda_value) const return this->stage_names[std::get<0>(resolved)]; } +/** Return the stage-local value of :lambda: that corresponds to the + * global value of :lambda: at `lambda_value` + */ double LambdaSchedule::getLambdaInStage(double lambda_value) const { if (this->nStages() == 0) @@ -327,6 +423,7 @@ double LambdaSchedule::getLambdaInStage(double lambda_value) const return std::get<1>(resolved); } +/** Completely clear all stages and levers */ void LambdaSchedule::clear() { this->stage_names.clear(); @@ -335,12 +432,24 @@ void LambdaSchedule::clear() this->constant_values = Values(); } +/** Append a morph stage onto this schedule. The morph stage is a + * standard stage that scales each forcefield parameter by + * (1-:lambda:).initial + :lambda:.final + */ void LambdaSchedule::addMorphStage() { this->addStage("morph", (this->lam() * this->final()) + ((1 - this->lam()) * this->initial())); } +/** Sandwich the current set of stages with a charge-descaling and + * a charge-scaling stage. This prepends a charge-descaling stage + * that scales the charge parameter down from `initial` to + * :gamma:.initial (where :gamma:=`scale`). The charge parameter in all of + * the exising stages in this schedule are then multiplied + * by :gamma:. A final charge-rescaling stage is then appended that + * scales the charge parameter from :gamma:.final to final. + */ void LambdaSchedule::addChargeScaleStages(double scale) { auto scl = this->setConstant("γ", scale); @@ -360,6 +469,11 @@ void LambdaSchedule::addChargeScaleStages(double scale) this->setEquation("recharge", "charge", (1.0 - ((1.0 - scl) * (1.0 - this->lam()))) * this->final()); } +/** Prepend a stage called 'name' which uses the passed 'equation' + * to the start of this schedule. The equation will be the default + * equation that scales all parameters (levers) that don't have + * a custom lever for this stage. + */ void LambdaSchedule::prependStage(const QString &name, const SireCAS::Expression &equation) { @@ -380,6 +494,11 @@ void LambdaSchedule::prependStage(const QString &name, this->stage_equations.prepend(QHash()); } +/** Append a stage called 'name' which uses the passed 'equation' + * to the end of this schedule. The equation will be the default + * equation that scales all parameters (levers) that don't have + * a custom lever for this stage. + */ void LambdaSchedule::appendStage(const QString &name, const SireCAS::Expression &equation) { @@ -394,6 +513,11 @@ void LambdaSchedule::appendStage(const QString &name, this->stage_equations.append(QHash()); } +/** Insert a stage called 'name' at position `i` which uses the passed + * 'equation'. The equation will be the default + * equation that scales all parameters (levers) that don't have + * a custom lever for this stage. + */ void LambdaSchedule::insertStage(int i, const QString &name, const SireCAS::Expression &equation) @@ -420,12 +544,21 @@ void LambdaSchedule::insertStage(int i, this->stage_equations.insert(i, QHash()); } +/** Append a stage called 'name' which uses the passed 'equation' + * to the end of this schedule. The equation will be the default + * equation that scales all parameters (levers) that don't have + * a custom lever for this stage. + */ void LambdaSchedule::addStage(const QString &name, const Expression &equation) { this->appendStage(name, equation); } +/** Find the index of the stage called 'stage'. This returns + * the order in which this stage will take place in the + * schedule. + */ int LambdaSchedule::find_stage(const QString &stage) const { int idx = this->stage_names.indexOf(stage); @@ -440,12 +573,22 @@ int LambdaSchedule::find_stage(const QString &stage) const return idx; } +/** Set the default equation used to control levers for the + * stage 'stage' to 'equation'. This equation will be used + * to control any levers in this stage that don't have + * their own custom equation. + */ void LambdaSchedule::setDefaultEquation(const QString &stage, const Expression &equation) { this->default_equations[this->find_stage(stage)] = equation; } +/** Set the custom equation used to control the specified + * `lever` at the stage `stage` to `equation`. This equation + * will only be used to control the parameters for the + * specified lever at the specified stage. + */ void LambdaSchedule::setEquation(const QString &stage, const QString &lever, const Expression &equation) @@ -458,6 +601,10 @@ void LambdaSchedule::setEquation(const QString &stage, lever_expressions[lever] = equation; } +/** Remove the custom equation for the specified `lever` at the + * specified `stage`. The lever will now use the default + * equation at this stage. + */ void LambdaSchedule::removeEquation(const QString &stage, const QString &lever) { @@ -469,6 +616,9 @@ void LambdaSchedule::removeEquation(const QString &stage, this->stage_equations[idx].remove(lever); } +/** Return the default equation used to control the parameters for + * the stage `stage`. + */ Expression LambdaSchedule::getEquation(const QString &stage) const { const int idx = this->find_stage(stage); @@ -476,6 +626,11 @@ Expression LambdaSchedule::getEquation(const QString &stage) const return this->default_equations[idx]; } +/** Return the equation used to control the specified `lever` + * at the specified `stage`. This will be a custom equation + * if that has been set for this lever, or else the + * default equation for this stage. + */ Expression LambdaSchedule::getEquation(const QString &stage, const QString &lever) const { @@ -515,6 +670,10 @@ QVector generate_lambdas(int num_values) return lambda_values; } +/** Return the list of lever stages that are used for the passed list + * of lambda values. The lever names will be returned in the matching + * order of the lambda values. + */ QStringList LambdaSchedule::getLeverStages(const QVector &lambda_values) const { QStringList stages; @@ -537,11 +696,22 @@ QStringList LambdaSchedule::getLeverStages(const QVector &lambda_values) return stages; } +/** Return the lever stages used for the list of `nvalue` lambda values + * generated for the global lambda value between 0 and 1 inclusive. + */ QStringList LambdaSchedule::getLeverStages(int nvalues) const { return this->getLeverStages(generate_lambdas(nvalues)); } +/** Return the lever name and parameter values for that lever + * for the specified list of lambda values, assuming that a + * parameter for that lever has an initial value of + * `initial_value` and a final value of `final_value`. This + * is mostly useful for testing and graphing how this + * schedule would change some hyperthetical forcefield + * parameters for the specified lambda values. + */ QHash> LambdaSchedule::getLeverValues( const QVector &lambda_values, double initial_value, double final_value) const @@ -593,6 +763,15 @@ QHash> LambdaSchedule::getLeverValues( return lever_values; } +/** Return the lever name and parameter values for that lever + * for the specified number of lambda values generated + * evenly between 0 and 1, assuming that a + * parameter for that lever has an initial value of + * `initial_value` and a final value of `final_value`. This + * is mostly useful for testing and graphing how this + * schedule would change some hyperthetical forcefield + * parameters for the specified lambda values. + */ QHash> LambdaSchedule::getLeverValues( int nvalues, double initial_value, double final_value) const @@ -601,6 +780,51 @@ QHash> LambdaSchedule::getLeverValues( initial_value, final_value); } +/** Return the parameters for the specified lever called `lever_name` + * that have been morphed from the passed list of initial values + * (in `initial`) to the passed list of final values (in `final`) + * for the specified global value of :lambda: (in `lambda_value`). + * + * The morphed parameters will be returned in the matching + * order to `initial` and `final`. + * + * This morphs a single floating point parameters. + */ +double LambdaSchedule::morph(const QString &lever_name, + double initial, double final, + double lambda_value) const +{ + if (this->nStages() == 0) + // just return the initial parameters as we don't know how to morph + return initial; + + const auto resolved = this->resolve_lambda(lambda_value); + const int stage = std::get<0>(resolved); + + const auto equation = this->stage_equations[stage].value( + lever_name, this->default_equations[stage]); + + Values input_values = this->constant_values; + input_values.set(this->lam(), std::get<1>(resolved)); + + input_values.set(this->initial(), initial); + input_values.set(this->final(), final); + + return equation(input_values); +} + +/** Return the parameters for the specified lever called `lever_name` + * that have been morphed from the passed list of initial values + * (in `initial`) to the passed list of final values (in `final`) + * for the specified global value of :lambda: (in `lambda_value`). + * + * The morphed parameters will be returned in the matching + * order to `initial` and `final`. + * + * This morphs floating point parameters. There is an overload + * of this function that morphs integer parameters, in which + * case the result would be rounded to the nearest integer. + */ QVector LambdaSchedule::morph(const QString &lever_name, const QVector &initial, const QVector &final, @@ -647,6 +871,17 @@ QVector LambdaSchedule::morph(const QString &lever_name, return morphed; } +/** Return the parameters for the specified lever called `lever_name` + * that have been morphed from the passed list of initial values + * (in `initial`) to the passed list of final values (in `final`) + * for the specified global value of :lambda: (in `lambda_value`). + * + * The morphed parameters will be returned in the matching + * order to `initial` and `final`. + * + * This function morphs integer parameters. In this case, + * the result will be the rounded to the nearest integer. + */ QVector LambdaSchedule::morph(const QString &lever_name, const QVector &initial, const QVector &final, diff --git a/corelib/src/libs/SireCAS/lambdaschedule.h b/corelib/src/libs/SireCAS/lambdaschedule.h index 773f45d26..fc432e0d2 100644 --- a/corelib/src/libs/SireCAS/lambdaschedule.h +++ b/corelib/src/libs/SireCAS/lambdaschedule.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ @@ -76,6 +76,9 @@ namespace SireCAS bool isNull() const; + static LambdaSchedule standard_morph(); + static LambdaSchedule charge_scaled_morph(double scale = 0.2); + static SireCAS::Symbol lam(); static SireCAS::Symbol initial(); static SireCAS::Symbol final(); @@ -114,7 +117,7 @@ namespace SireCAS const SireCAS::Expression &equation); void addMorphStage(); - void addChargeScaleStages(double scale); + void addChargeScaleStages(double scale = 0.2); void setEquation(const QString &stage, const QString &lever, @@ -152,6 +155,9 @@ namespace SireCAS SireCAS::Symbol getConstantSymbol(const QString &constant) const; + double morph(const QString &lever, + double initial, double final, double lambda_value) const; + QVector morph(const QString &lever, const QVector &initial, const QVector &final, @@ -162,9 +168,10 @@ namespace SireCAS const QVector &final, double lambda_value) const; + double clamp(double lambda_value) const; + protected: int find_stage(const QString &stage) const; - double clamp(double lambda_value) const; std::tuple resolve_lambda(double lambda) const; diff --git a/corelib/src/libs/SireIO/amberrst.cpp b/corelib/src/libs/SireIO/amberrst.cpp index 184378417..01ba8887a 100644 --- a/corelib/src/libs/SireIO/amberrst.cpp +++ b/corelib/src/libs/SireIO/amberrst.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireIO/amberrst.h b/corelib/src/libs/SireIO/amberrst.h index 57822378d..e277a7318 100644 --- a/corelib/src/libs/SireIO/amberrst.h +++ b/corelib/src/libs/SireIO/amberrst.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireIO/ambertraj.cpp b/corelib/src/libs/SireIO/ambertraj.cpp index e5d51abbd..e2fcdd005 100644 --- a/corelib/src/libs/SireIO/ambertraj.cpp +++ b/corelib/src/libs/SireIO/ambertraj.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireIO/ambertraj.h b/corelib/src/libs/SireIO/ambertraj.h index 68470aac7..70d7eca7a 100644 --- a/corelib/src/libs/SireIO/ambertraj.h +++ b/corelib/src/libs/SireIO/ambertraj.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireIO/filetrajectoryparser.cpp b/corelib/src/libs/SireIO/filetrajectoryparser.cpp index 17893982d..65da5066c 100644 --- a/corelib/src/libs/SireIO/filetrajectoryparser.cpp +++ b/corelib/src/libs/SireIO/filetrajectoryparser.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireIO/filetrajectoryparser.h b/corelib/src/libs/SireIO/filetrajectoryparser.h index 20de6abbc..deb65b0b9 100644 --- a/corelib/src/libs/SireIO/filetrajectoryparser.h +++ b/corelib/src/libs/SireIO/filetrajectoryparser.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireIO/textfile.cpp b/corelib/src/libs/SireIO/textfile.cpp index 1cdf237ee..3c3f7286a 100644 --- a/corelib/src/libs/SireIO/textfile.cpp +++ b/corelib/src/libs/SireIO/textfile.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireIO/textfile.h b/corelib/src/libs/SireIO/textfile.h index a11d14ac7..dd676a3c1 100644 --- a/corelib/src/libs/SireIO/textfile.h +++ b/corelib/src/libs/SireIO/textfile.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireIO/trr.cpp b/corelib/src/libs/SireIO/trr.cpp index 5f8bd94eb..27ed8bfec 100644 --- a/corelib/src/libs/SireIO/trr.cpp +++ b/corelib/src/libs/SireIO/trr.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireIO/trr.h b/corelib/src/libs/SireIO/trr.h index f17715ad7..fb30738b2 100644 --- a/corelib/src/libs/SireIO/trr.h +++ b/corelib/src/libs/SireIO/trr.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireIO/xdrfile.cpp b/corelib/src/libs/SireIO/xdrfile.cpp index 79fd7e3cd..94046ebb5 100644 --- a/corelib/src/libs/SireIO/xdrfile.cpp +++ b/corelib/src/libs/SireIO/xdrfile.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireIO/xdrfile.h b/corelib/src/libs/SireIO/xdrfile.h index b49328917..418f3766c 100644 --- a/corelib/src/libs/SireIO/xdrfile.h +++ b/corelib/src/libs/SireIO/xdrfile.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireIO/xtc.cpp b/corelib/src/libs/SireIO/xtc.cpp index 9f0e800b5..74edd60ad 100644 --- a/corelib/src/libs/SireIO/xtc.cpp +++ b/corelib/src/libs/SireIO/xtc.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireIO/xtc.h b/corelib/src/libs/SireIO/xtc.h index 869a8518f..0c79a6307 100644 --- a/corelib/src/libs/SireIO/xtc.h +++ b/corelib/src/libs/SireIO/xtc.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMM/CMakeLists.txt b/corelib/src/libs/SireMM/CMakeLists.txt index 4a0bb4cc3..7580cc4af 100644 --- a/corelib/src/libs/SireMM/CMakeLists.txt +++ b/corelib/src/libs/SireMM/CMakeLists.txt @@ -22,6 +22,8 @@ set ( SIREMM_HEADERS atomljs.h atompairs.hpp bond.h + bondrestraints.h + boreschrestraints.h clj14group.h cljatoms.h cljboxes.h @@ -75,7 +77,9 @@ set ( SIREMM_HEADERS ljpotential.h mmdetail.h multicljcomponent.h + positionalrestraints.h restraint.h + restraints.h restraintcomponent.h restraintff.h selectorangle.h @@ -109,6 +113,8 @@ set ( SIREMM_SOURCES atomfunctions.cpp atomljs.cpp bond.cpp + bondrestraints.cpp + boreschrestraints.cpp clj14group.cpp cljatoms.cpp cljboxes.cpp @@ -161,7 +167,9 @@ set ( SIREMM_SOURCES ljpotential.cpp mmdetail.cpp multicljcomponent.cpp + positionalrestraints.cpp restraint.cpp + restraints.cpp restraintcomponent.cpp restraintff.cpp selectorangle.cpp diff --git a/corelib/src/libs/SireMM/bondrestraints.cpp b/corelib/src/libs/SireMM/bondrestraints.cpp new file mode 100644 index 000000000..11e91057e --- /dev/null +++ b/corelib/src/libs/SireMM/bondrestraints.cpp @@ -0,0 +1,680 @@ +/********************************************\ + * + * Sire - Molecular Simulation Framework + * + * Copyright (C) 2023 Christopher Woods + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * For full details of the license please see the COPYING file + * that should have come with this distribution. + * + * You can contact the authors via the website + * at https://sire.openbiosim.org + * +\*********************************************/ + +#include "bondrestraints.h" + +#include "SireUnits/units.h" + +#include "SireID/index.h" + +#include "SireError/errors.h" + +#include "SireStream/datastream.h" +#include "SireStream/shareddatastream.h" + +#include + +using namespace SireMM; +using namespace SireMaths; +using namespace SireBase; +using namespace SireUnits; +using namespace SireUnits::Dimension; +using namespace SireStream; + +/////// +/////// Implementation of BondRestraint +/////// + +static const RegisterMetaType r_bndrest; + +QDataStream &operator<<(QDataStream &ds, const BondRestraint &bndrest) +{ + writeHeader(ds, r_bndrest, 1); + + SharedDataStream sds(ds); + + sds << bndrest.atms0 << bndrest.atms1 << bndrest._k << bndrest._r0 + << static_cast(bndrest); + + return ds; +} + +QDataStream &operator>>(QDataStream &ds, BondRestraint &bndrest) +{ + VersionID v = readHeader(ds, r_bndrest); + + if (v == 1) + { + SharedDataStream sds(ds); + + sds >> bndrest.atms0 >> bndrest.atms1 >> bndrest._k >> bndrest._r0 >> static_cast(bndrest); + } + else + throw version_error(v, "1", r_bndrest, CODELOC); + + return ds; +} + +/** Null constructor */ +BondRestraint::BondRestraint() + : ConcreteProperty(), + _k(0), _r0(0) +{ +} + +/** Construct to restrain the atom at index 'atom' to the specified position + * using the specified force constant and flat-bottom well-width + */ +BondRestraint::BondRestraint(qint64 atom0, qint64 atom1, + const SireUnits::Dimension::HarmonicBondConstant &k, + const SireUnits::Dimension::Length &r0) + : ConcreteProperty(), + _k(k), _r0(r0) +{ + if (atom0 == atom1) + throw SireError::invalid_arg(QObject::tr( + "You cannot create a bond restraint between identical atoms! %1-%2") + .arg(atom0) + .arg(atom1), + CODELOC); + + atms0 = QVector(1, atom0); + atms0.squeeze(); + + atms1 = QVector(1, atom1); + atms1.squeeze(); +} + +/** Construct to restrain the centroid of the atoms whose indicies are + * in 'atoms' to the specified position using the specified force constant + * and flat-bottom well width + */ +BondRestraint::BondRestraint(const QList &atoms0, + const QList &atoms1, + const SireUnits::Dimension::HarmonicBondConstant &k, + const SireUnits::Dimension::Length &r0) + : ConcreteProperty(), + _k(k), _r0(r0) +{ + if (atoms0.isEmpty() or atoms1.isEmpty()) + return; + + // remove duplicates + atms0.reserve(atoms0.count()); + + auto sorted = atoms0; + std::sort(sorted.begin(), sorted.end()); + + atms0.append(sorted.at(0)); + + for (const auto &atom : sorted) + { + if (atom != atms0.last()) + atms0.append(atom); + } + + atms0.squeeze(); + + // now the same for atoms1 + atms1.reserve(atoms1.count()); + + sorted = atoms1; + std::sort(sorted.begin(), sorted.end()); + + if (atms0.indexOf(sorted.at(0)) != -1) + throw SireError::invalid_arg(QObject::tr( + "You cannot have an overlap in atoms between the two groups. Atom " + "%1 appears in both!") + .arg(sorted.at(0)), + CODELOC); + + atms1.append(sorted.at(0)); + + for (const auto &atom : sorted) + { + if (atom != atms1.last()) + { + if (atms0.indexOf(atom) != -1) + throw SireError::invalid_arg(QObject::tr( + "You cannot have an overlap in atoms between the two groups. Atom " + "%1 appears in both!") + .arg(sorted.at(0)), + CODELOC); + + atms1.append(atom); + } + + atms1.squeeze(); + } +} + +/** Copy constructor */ +BondRestraint::BondRestraint(const BondRestraint &other) + : ConcreteProperty(other), + atms0(other.atms0), atms1(other.atms1), _k(other._k), _r0(other._r0) +{ +} + +BondRestraint::~BondRestraint() +{ +} + +BondRestraint &BondRestraint::operator=(const BondRestraint &other) +{ + if (this != &other) + { + atms0 = other.atms0; + atms1 = other.atms1; + _k = other._k; + _r0 = other._r0; + } + + return *this; +} + +bool BondRestraint::operator==(const BondRestraint &other) const +{ + return atms0 == other.atms0 and atms1 == other.atms1 and + _k == other._k and _r0 == other._r0; +} + +bool BondRestraint::operator!=(const BondRestraint &other) const +{ + return not operator==(other); +} + +BondRestraints BondRestraint::operator+(const BondRestraint &other) const +{ + return BondRestraints(*this) + other; +} + +BondRestraints BondRestraint::operator+(const BondRestraints &other) const +{ + return BondRestraints(*this) + other; +} + +const char *BondRestraint::typeName() +{ + return QMetaType::typeName(qMetaTypeId()); +} + +const char *BondRestraint::what() const +{ + return BondRestraint::typeName(); +} + +BondRestraint *BondRestraint::clone() const +{ + return new BondRestraint(*this); +} + +bool BondRestraint::isNull() const +{ + return atms0.isEmpty() or atms1.isEmpty(); +} + +QString BondRestraint::toString() const +{ + if (this->isNull()) + return QObject::tr("BondRestraint::null"); + + else if (this->isAtomRestraint()) + { + return QString("BondRestraint( %1 <=> %2, k=%3 : r0=%4 )") + .arg(this->atom0()) + .arg(this->atom1()) + .arg(_k.toString()) + .arg(_r0.toString()); + } + else + { + QStringList a0, a1; + + for (const auto &atom : atms0) + { + a0.append(QString::number(atom)); + } + + for (const auto &atom : atms1) + { + a1.append(QString::number(atom)); + } + + return QString("BondRestraint( [%1] <=> [%2], k=%3 : r0=%4 )") + .arg(a0.join(", ")) + .arg(a1.join(", ")) + .arg(_k.toString()) + .arg(_r0.toString()); + } +} + +/** Return whether this is a single-atom restraint */ +bool BondRestraint::isAtomRestraint() const +{ + return atms0.count() == 1 and atms1.count() == 1; +} + +/** Return whether this restraint acts on the centroid of a group + * of atoms */ +bool BondRestraint::isCentroidRestraint() const +{ + return atms0.count() > 1 or atms1.count() > 1; +} + +/** Return the index of the atom if this is a single-atom restraint */ +qint64 BondRestraint::atom0() const +{ + if (not this->isAtomRestraint()) + throw SireError::incompatible_error(QObject::tr( + "You cannot get the atom when this isn't a single-atom restraint!"), + CODELOC); + + return this->atms0.at(0); +} + +/** Return the index of the atom if this is a single-atom restraint */ +qint64 BondRestraint::atom1() const +{ + if (not this->isAtomRestraint()) + throw SireError::incompatible_error(QObject::tr( + "You cannot get the atom when this isn't a single-atom restraint!"), + CODELOC); + + return this->atms1.at(0); +} + +/** Return the indexes of the atoms whose centroid is to be restrained */ +QVector BondRestraint::atoms0() const +{ + if (not this->isCentroidRestraint()) + throw SireError::incompatible_error(QObject::tr( + "You cannot get the atoms when this isn't a centroid restraint!"), + CODELOC); + + return this->atms0; +} + +/** Return the indexes of the atoms whose centroid is to be restrained */ +QVector BondRestraint::atoms1() const +{ + if (not this->isCentroidRestraint()) + throw SireError::incompatible_error(QObject::tr( + "You cannot get the atoms when this isn't a centroid restraint!"), + CODELOC); + + return this->atms1; +} + +/** Return the force constant for the restraint */ +SireUnits::Dimension::HarmonicBondConstant BondRestraint::k() const +{ + return this->_k; +} + +/** Return the width of the harmonic bond. */ +SireUnits::Dimension::Length BondRestraint::r0() const +{ + return this->_r0; +} + +/////// +/////// Implementation of BondRestraints +/////// + +static const RegisterMetaType r_bndrests; + +QDataStream &operator<<(QDataStream &ds, const BondRestraints &bndrests) +{ + writeHeader(ds, r_bndrests, 1); + + SharedDataStream sds(ds); + + sds << bndrests.r + << static_cast(bndrests); + + return ds; +} + +QDataStream &operator>>(QDataStream &ds, BondRestraints &bndrests) +{ + VersionID v = readHeader(ds, r_bndrests); + + if (v == 1) + { + SharedDataStream sds(ds); + + sds >> bndrests.r >> static_cast(bndrests); + } + else + throw version_error(v, "1", r_bndrests, CODELOC); + + return ds; +} + +/** Null constructor */ +BondRestraints::BondRestraints() + : ConcreteProperty() +{ +} + +BondRestraints::BondRestraints(const QString &name) + : ConcreteProperty(name) +{ +} + +BondRestraints::BondRestraints(const BondRestraint &restraint) + : ConcreteProperty() +{ + if (not restraint.isNull()) + r.append(restraint); +} + +BondRestraints::BondRestraints(const QList &restraints) + : ConcreteProperty() +{ + for (const auto &restraint : restraints) + { + if (not restraint.isNull()) + r.append(restraint); + } +} + +BondRestraints::BondRestraints(const QString &name, + const BondRestraint &restraint) + : ConcreteProperty(name) +{ + if (not restraint.isNull()) + r.append(restraint); +} + +BondRestraints::BondRestraints(const QString &name, + const QList &restraints) + : ConcreteProperty(name) +{ + for (const auto &restraint : restraints) + { + if (not restraint.isNull()) + r.append(restraint); + } +} + +BondRestraints::BondRestraints(const BondRestraints &other) + : ConcreteProperty(other), r(other.r) +{ +} + +BondRestraints::~BondRestraints() +{ +} + +BondRestraints &BondRestraints::operator=(const BondRestraints &other) +{ + r = other.r; + Restraints::operator=(other); + return *this; +} + +bool BondRestraints::operator==(const BondRestraints &other) const +{ + return r == other.r and Restraints::operator==(other); +} + +bool BondRestraints::operator!=(const BondRestraints &other) const +{ + return not operator==(other); +} + +const char *BondRestraints::typeName() +{ + return QMetaType::typeName(qMetaTypeId()); +} + +const char *BondRestraints::what() const +{ + return BondRestraints::typeName(); +} + +BondRestraints *BondRestraints::clone() const +{ + return new BondRestraints(*this); +} + +QString BondRestraints::toString() const +{ + if (this->isEmpty()) + return QObject::tr("BondRestraints::null"); + + QStringList parts; + + const auto n = this->count(); + + if (n <= 10) + { + for (int i = 0; i < n; ++i) + { + parts.append(QObject::tr("%1: %2").arg(i).arg(this->r.at(i).toString())); + } + } + else + { + for (int i = 0; i < 5; ++i) + { + parts.append(QObject::tr("%1: %2").arg(i).arg(this->r.at(i).toString())); + } + + parts.append("..."); + + for (int i = n - 5; i < n; ++i) + { + parts.append(QObject::tr("%1: %2").arg(i).arg(this->r.at(i).toString())); + } + } + + return QObject::tr("BondRestraints( name=%1, size=%2\n%3\n)").arg(this->name()).arg(n).arg(parts.join("\n")); +} + +/** Return whether or not this is empty */ +bool BondRestraints::isEmpty() const +{ + return this->r.isEmpty(); +} + +/** Return whether or not this is empty */ +bool BondRestraints::isNull() const +{ + return this->isEmpty(); +} + +/** Return the number of restraints */ +int BondRestraints::nRestraints() const +{ + return this->r.count(); +} + +/** Return the number of restraints */ +int BondRestraints::count() const +{ + return this->nRestraints(); +} + +/** Return the number of restraints */ +int BondRestraints::size() const +{ + return this->nRestraints(); +} + +/** Return the number of atom restraints */ +int BondRestraints::nAtomRestraints() const +{ + int n = 0; + + for (const auto &restraint : this->r) + { + n += int(restraint.isAtomRestraint()); + } + + return n; +} + +/** Return the number of centroid restraints */ +int BondRestraints::nCentroidRestraints() const +{ + int n = 0; + + for (const auto &restraint : this->r) + { + n += int(restraint.isCentroidRestraint()); + } + + return n; +} + +/** Return whether or not there are any atom restraints */ +bool BondRestraints::hasAtomRestraints() const +{ + for (const auto &restraint : this->r) + { + if (restraint.isAtomRestraint()) + return true; + } + + return false; +} + +/** Return whether or not there are any centroid restraints */ +bool BondRestraints::hasCentroidRestraints() const +{ + for (const auto &restraint : this->r) + { + if (restraint.isCentroidRestraint()) + return true; + } + + return false; +} + +/** Return the ith restraint */ +const BondRestraint &BondRestraints::at(int i) const +{ + i = SireID::Index(i).map(this->r.count()); + + return this->r.at(i); +} + +/** Return the ith restraint */ +const BondRestraint &BondRestraints::operator[](int i) const +{ + return this->at(i); +} + +/** Return all of the restraints */ +QList BondRestraints::restraints() const +{ + return this->r; +} + +/** Return all of the atom restraints */ +QList BondRestraints::atomRestraints() const +{ + if (this->hasCentroidRestraints()) + { + QList ar; + + for (const auto &restraint : this->r) + { + if (restraint.isAtomRestraint()) + ar.append(restraint); + } + + return ar; + } + else + return this->restraints(); +} + +/** Return all of the centroid restraints */ +QList BondRestraints::centroidRestraints() const +{ + if (this->hasAtomRestraints()) + { + QList cr; + + for (const auto &restraint : this->r) + { + if (restraint.isCentroidRestraint()) + cr.append(restraint); + } + + return cr; + } + else + return this->restraints(); +} + +/** Add a restraint onto the list */ +void BondRestraints::add(const BondRestraint &restraint) +{ + if (not restraint.isNull()) + this->r.append(restraint); +} + +/** Add a restraint onto the list */ +void BondRestraints::add(const BondRestraints &restraints) +{ + this->r += restraints.r; +} + +/** Add a restraint onto the list */ +BondRestraints &BondRestraints::operator+=(const BondRestraint &restraint) +{ + this->add(restraint); + return *this; +} + +/** Add a restraint onto the list */ +BondRestraints BondRestraints::operator+(const BondRestraint &restraint) const +{ + BondRestraints ret(*this); + ret += restraint; + return *this; +} + +/** Add restraints onto the list */ +BondRestraints &BondRestraints::operator+=(const BondRestraints &restraints) +{ + this->add(restraints); + return *this; +} + +/** Add restraints onto the list */ +BondRestraints BondRestraints::operator+(const BondRestraints &restraints) const +{ + BondRestraints ret(*this); + ret += restraints; + return *this; +} diff --git a/corelib/src/libs/SireMM/bondrestraints.h b/corelib/src/libs/SireMM/bondrestraints.h new file mode 100644 index 000000000..ccf534de9 --- /dev/null +++ b/corelib/src/libs/SireMM/bondrestraints.h @@ -0,0 +1,209 @@ +/********************************************\ + * + * Sire - Molecular Simulation Framework + * + * Copyright (C) 2023 Christopher Woods + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * For full details of the license please see the COPYING file + * that should have come with this distribution. + * + * You can contact the authors via the website + * at https://sire.openbiosim.org + * +\*********************************************/ + +#ifndef SIREMM_BONDRESTRAINTS_H +#define SIREMM_BONDRESTRAINTS_H + +#include "restraints.h" + +#include "SireUnits/dimensions.h" +#include "SireUnits/generalunit.h" + +SIRE_BEGIN_HEADER + +namespace SireMM +{ + class BondRestraint; + class BondRestraints; +} + +SIREMM_EXPORT QDataStream &operator<<(QDataStream &, const SireMM::BondRestraint &); +SIREMM_EXPORT QDataStream &operator>>(QDataStream &, SireMM::BondRestraint &); + +SIREMM_EXPORT QDataStream &operator<<(QDataStream &, const SireMM::BondRestraints &); +SIREMM_EXPORT QDataStream &operator>>(QDataStream &, SireMM::BondRestraints &); + +namespace SireMM +{ + + /** This class represents a single bond restraint between any two + * atoms in a system (or between the centroids of any two groups + * of atoms in a system) + */ + class SIREMM_EXPORT BondRestraint + : public SireBase::ConcreteProperty + { + friend QDataStream & ::operator<<(QDataStream &, const SireMM::BondRestraint &); + friend QDataStream & ::operator>>(QDataStream &, SireMM::BondRestraint &); + + public: + BondRestraint(); + BondRestraint(qint64 atom0, qint64 atom1, + const SireUnits::Dimension::HarmonicBondConstant &k, + const SireUnits::Dimension::Length &r0); + + BondRestraint(const QList &atoms0, + const QList &atoms1, + const SireUnits::Dimension::HarmonicBondConstant &k, + const SireUnits::Dimension::Length &r0); + + BondRestraint(const BondRestraint &other); + + ~BondRestraint(); + + BondRestraint &operator=(const BondRestraint &other); + + bool operator==(const BondRestraint &other) const; + bool operator!=(const BondRestraint &other) const; + + BondRestraints operator+(const BondRestraint &other) const; + BondRestraints operator+(const BondRestraints &other) const; + + static const char *typeName(); + const char *what() const; + + BondRestraint *clone() const; + + QString toString() const; + + bool isNull() const; + + bool isAtomRestraint() const; + bool isCentroidRestraint() const; + + qint64 atom0() const; + qint64 atom1() const; + + QVector atoms0() const; + QVector atoms1() const; + + SireUnits::Dimension::HarmonicBondConstant k() const; + SireUnits::Dimension::Length r0() const; + + private: + /** The first set of atoms involved in the restraint */ + QVector atms0; + + /** The second set of atoms involved in the restraint */ + QVector atms1; + + /** The force constant */ + SireUnits::Dimension::HarmonicBondConstant _k; + + /** The equilibrium distance for the restraint */ + SireUnits::Dimension::Length _r0; + }; + + /** This class provides the information for a collection of bond + * restraints that can be added to a collection of molecues. Each + * restraint can act on a pair of particles or a pair of the + * centroids of two collections of particles. + * The restaints are spherically symmetric, and + * are simple harmonic potentials + */ + class SIREMM_EXPORT BondRestraints + : public SireBase::ConcreteProperty + { + friend QDataStream & ::operator<<(QDataStream &, const SireMM::BondRestraints &); + friend QDataStream & ::operator>>(QDataStream &, SireMM::BondRestraints &); + + public: + BondRestraints(); + BondRestraints(const QString &name); + + BondRestraints(const BondRestraint &restraint); + BondRestraints(const QList &restraints); + + BondRestraints(const QString &name, + const BondRestraint &restraint); + + BondRestraints(const QString &name, + const QList &restraints); + + BondRestraints(const BondRestraints &other); + + ~BondRestraints(); + + BondRestraints &operator=(const BondRestraints &other); + + bool operator==(const BondRestraints &other) const; + bool operator!=(const BondRestraints &other) const; + + static const char *typeName(); + const char *what() const; + + BondRestraints *clone() const; + + QString toString() const; + + bool isEmpty() const; + bool isNull() const; + + int count() const; + int size() const; + int nRestraints() const; + + int nAtomRestraints() const; + int nCentroidRestraints() const; + + bool hasAtomRestraints() const; + bool hasCentroidRestraints() const; + + const BondRestraint &at(int i) const; + const BondRestraint &operator[](int i) const; + + QList restraints() const; + + QList atomRestraints() const; + QList centroidRestraints() const; + + void add(const BondRestraint &restraint); + void add(const BondRestraints &restraints); + + BondRestraints &operator+=(const BondRestraint &restraint); + BondRestraints &operator+=(const BondRestraints &restraints); + + BondRestraints operator+(const BondRestraint &restraint) const; + BondRestraints operator+(const BondRestraints &restraints) const; + + private: + /** The actual list of restraints*/ + QList r; + }; + +} + +Q_DECLARE_METATYPE(SireMM::BondRestraint) +Q_DECLARE_METATYPE(SireMM::BondRestraints) + +SIRE_EXPOSE_CLASS(SireMM::BondRestraint) +SIRE_EXPOSE_CLASS(SireMM::BondRestraints) + +SIRE_END_HEADER + +#endif diff --git a/corelib/src/libs/SireMM/boreschrestraints.cpp b/corelib/src/libs/SireMM/boreschrestraints.cpp new file mode 100644 index 000000000..be5981a9e --- /dev/null +++ b/corelib/src/libs/SireMM/boreschrestraints.cpp @@ -0,0 +1,585 @@ +/********************************************\ + * + * Sire - Molecular Simulation Framework + * + * Copyright (C) 2023 Christopher Woods + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * For full details of the license please see the COPYING file + * that should have come with this distribution. + * + * You can contact the authors via the website + * at https://sire.openbiosim.org + * +\*********************************************/ + +#include "boreschrestraints.h" + +#include "SireUnits/units.h" + +#include "SireID/index.h" + +#include "SireError/errors.h" + +#include "SireStream/datastream.h" +#include "SireStream/shareddatastream.h" + +#include + +using namespace SireMM; +using namespace SireMaths; +using namespace SireBase; +using namespace SireUnits; +using namespace SireUnits::Dimension; +using namespace SireStream; + +/////// +/////// Implementation of BoreschRestraint +/////// + +static const RegisterMetaType r_borrest; + +QDataStream &operator<<(QDataStream &ds, const BoreschRestraint &borrest) +{ + writeHeader(ds, r_borrest, 1); + + SharedDataStream sds(ds); + + sds << borrest.receptor_atms << borrest.ligand_atms + << borrest._r0 << borrest._theta0 << borrest._phi0 + << borrest._kr << borrest._ktheta << borrest._kphi + << static_cast(borrest); + + return ds; +} + +QDataStream &operator>>(QDataStream &ds, BoreschRestraint &borrest) +{ + VersionID v = readHeader(ds, r_borrest); + + if (v == 1) + { + SharedDataStream sds(ds); + + sds >> borrest.receptor_atms >> borrest.ligand_atms >> borrest._r0 >> borrest._theta0 >> borrest._phi0 >> borrest._kr >> borrest._ktheta >> borrest._kphi >> static_cast(borrest); + } + else + throw version_error(v, "1", r_borrest, CODELOC); + + return ds; +} + +/** Null constructor */ +BoreschRestraint::BoreschRestraint() + : ConcreteProperty(), + _r0(0), _kr(0) +{ +} + +/** Construct to restrain the atom at index 'atom' to the specified position + * using the specified force constant and flat-bottom well-width + */ +BoreschRestraint::BoreschRestraint(const QList &receptor_atoms, + const QList &ligand_atoms, + const SireUnits::Dimension::Length &r0, + const QVector &theta0, + const QVector &phi0, + const SireUnits::Dimension::HarmonicBondConstant &kr, + const QVector &ktheta, + const QVector &kphi) + : ConcreteProperty(), + _r0(r0), _theta0(theta0), _phi0(phi0), + _kr(kr), _ktheta(ktheta), _kphi(kphi) +{ + if (receptor_atoms.count() != 3 or ligand_atoms.count() != 3 or + theta0.count() != 2 or phi0.count() != 3 or + ktheta.count() != 2 or kphi.count() != 3) + { + throw SireError::invalid_arg(QObject::tr( + "Wrong number of inputs for a Boresch restraint. You need to " + "provide 3 receptor atoms (%1), 3 ligand atoms (%2), 2 theta0 values (%3) " + "3 phi0 values (%4), 2 ktheta values (%5) and 3 kphi values (%6).") + .arg(receptor_atoms.count()) + .arg(ligand_atoms.count()) + .arg(theta0.count()) + .arg(phi0.count()) + .arg(ktheta.count()) + .arg(kphi.count()), + CODELOC); + } + + // make sure that we have 6 distinct atoms + QSet distinct; + distinct.reserve(6); + + for (const auto &atom : receptor_atoms) + { + if (atom >= 0) + distinct.insert(atom); + } + + for (const auto &atom : ligand_atoms) + { + if (atom >= 0) + distinct.insert(atom); + } + + if (distinct.count() != 6) + throw SireError::invalid_arg(QObject::tr( + "There is something wrong with the receptor or ligand atoms. " + "They should all be unique and all greater than or equal to 0."), + CODELOC); + + receptor_atms = receptor_atoms.toVector(); + receptor_atms.squeeze(); + + ligand_atms = ligand_atoms.toVector(); + ligand_atms.squeeze(); +} + +/** Copy constructor */ +BoreschRestraint::BoreschRestraint(const BoreschRestraint &other) + : ConcreteProperty(other), + receptor_atms(other.receptor_atms), ligand_atms(other.ligand_atms), + _r0(other._r0), _theta0(other._theta0), _phi0(other._phi0), + _kr(other._kr), _ktheta(other._ktheta), _kphi(other._kphi) +{ +} + +BoreschRestraint::~BoreschRestraint() +{ +} + +BoreschRestraint &BoreschRestraint::operator=(const BoreschRestraint &other) +{ + if (this != &other) + { + receptor_atms = other.receptor_atms; + ligand_atms = other.ligand_atms; + _r0 = other._r0; + _theta0 = other._theta0; + _phi0 = other._phi0; + _kr = other._kr; + _ktheta = other._ktheta; + _kphi = other._kphi; + Property::operator=(other); + } + + return *this; +} + +bool BoreschRestraint::operator==(const BoreschRestraint &other) const +{ + return receptor_atms == other.receptor_atms and + ligand_atms == other.ligand_atms and + _kr == other._kr and _ktheta == other._ktheta and + _kphi == other._kphi and _r0 == other._r0 and + _theta0 == other._theta0 and _phi0 == other._phi0; +} + +bool BoreschRestraint::operator!=(const BoreschRestraint &other) const +{ + return not operator==(other); +} + +BoreschRestraints BoreschRestraint::operator+(const BoreschRestraint &other) const +{ + return BoreschRestraints(*this) + other; +} + +BoreschRestraints BoreschRestraint::operator+(const BoreschRestraints &other) const +{ + return BoreschRestraints(*this) + other; +} + +const char *BoreschRestraint::typeName() +{ + return QMetaType::typeName(qMetaTypeId()); +} + +const char *BoreschRestraint::what() const +{ + return BoreschRestraint::typeName(); +} + +BoreschRestraint *BoreschRestraint::clone() const +{ + return new BoreschRestraint(*this); +} + +bool BoreschRestraint::isNull() const +{ + return receptor_atms.isEmpty() and ligand_atms.isEmpty(); +} + +QString BoreschRestraint::toString() const +{ + if (this->isNull()) + return QObject::tr("BoreschRestraint::null"); + + else + { + QStringList r; + + for (const auto &atom : receptor_atms) + { + r.append(QString::number(atom)); + } + + QStringList l; + + for (const auto &atom : ligand_atms) + { + l.append(QString::number(atom)); + } + + QStringList k; + + k.append(_kr.toString()); + + for (const auto &kt : _ktheta) + { + k.append(kt.toString()); + } + + for (const auto &kp : _kphi) + { + k.append(kp.toString()); + } + + QStringList t; + + for (const auto &t0 : _theta0) + { + t.append(t0.toString()); + } + + QStringList p; + + for (const auto &p0 : _phi0) + { + p.append(p0.toString()); + } + + return QString("BoreschRestraint( [%1] => [%2],\n" + " k=[%3]\n" + " r0=%4, theta0=[%5],\n" + " phi0=[%6] )") + .arg(r.join(", ")) + .arg(l.join(", ")) + .arg(k.join(", ")) + .arg(_r0.toString()) + .arg(t.join(", ")) + .arg(p.join(', ')); + } +} + +/** Return the indexes of the three receptor atoms */ +QVector BoreschRestraint::receptorAtoms() const +{ + return this->receptor_atms; +} + +/** Return the indexes of the three ligand atoms */ +QVector BoreschRestraint::ligandAtoms() const +{ + return this->ligand_atms; +} + +/** Return the equilibrium length of the bond restraint */ +SireUnits::Dimension::Length BoreschRestraint::r0() const +{ + return _r0; +} + +/** Return the equilibrium size of the two angle restraints */ +QVector BoreschRestraint::theta0() const +{ + return _theta0; +} + +/** Return the equilibium size of the three dihedral restraints */ +QVector BoreschRestraint::phi0() const +{ + return _phi0; +} + +/** Return the force constant for the bond restraint */ +SireUnits::Dimension::HarmonicBondConstant BoreschRestraint::kr() const +{ + return _kr; +} + +/** Return the force constant for the two angle restraints */ +QVector BoreschRestraint::ktheta() const +{ + return _ktheta; +} + +/** Return the force constant for the three dihedral restraints */ +QVector BoreschRestraint::kphi() const +{ + return _kphi; +} + +/////// +/////// Implementation of BoreschRestraints +/////// + +static const RegisterMetaType r_borrests; + +QDataStream &operator<<(QDataStream &ds, const BoreschRestraints &borrests) +{ + writeHeader(ds, r_borrests, 1); + + SharedDataStream sds(ds); + + sds << borrests.r + << static_cast(borrests); + + return ds; +} + +QDataStream &operator>>(QDataStream &ds, BoreschRestraints &borrests) +{ + VersionID v = readHeader(ds, r_borrests); + + if (v == 1) + { + SharedDataStream sds(ds); + + sds >> borrests.r >> static_cast(borrests); + } + else + throw version_error(v, "1", r_borrests, CODELOC); + + return ds; +} + +/** Null constructor */ +BoreschRestraints::BoreschRestraints() + : ConcreteProperty() +{ +} + +BoreschRestraints::BoreschRestraints(const QString &name) + : ConcreteProperty(name) +{ +} + +BoreschRestraints::BoreschRestraints(const BoreschRestraint &restraint) + : ConcreteProperty() +{ + if (not restraint.isNull()) + r.append(restraint); +} + +BoreschRestraints::BoreschRestraints(const QList &restraints) + : ConcreteProperty() +{ + for (const auto &restraint : restraints) + { + if (not restraint.isNull()) + r.append(restraint); + } +} + +BoreschRestraints::BoreschRestraints(const QString &name, + const BoreschRestraint &restraint) + : ConcreteProperty(name) +{ + if (not restraint.isNull()) + r.append(restraint); +} + +BoreschRestraints::BoreschRestraints(const QString &name, + const QList &restraints) + : ConcreteProperty(name) +{ + for (const auto &restraint : restraints) + { + if (not restraint.isNull()) + r.append(restraint); + } +} + +BoreschRestraints::BoreschRestraints(const BoreschRestraints &other) + : ConcreteProperty(other), r(other.r) +{ +} + +BoreschRestraints::~BoreschRestraints() +{ +} + +BoreschRestraints &BoreschRestraints::operator=(const BoreschRestraints &other) +{ + r = other.r; + Restraints::operator=(other); + return *this; +} + +bool BoreschRestraints::operator==(const BoreschRestraints &other) const +{ + return r == other.r and Restraints::operator==(other); +} + +bool BoreschRestraints::operator!=(const BoreschRestraints &other) const +{ + return not operator==(other); +} + +const char *BoreschRestraints::typeName() +{ + return QMetaType::typeName(qMetaTypeId()); +} + +const char *BoreschRestraints::what() const +{ + return BoreschRestraints::typeName(); +} + +BoreschRestraints *BoreschRestraints::clone() const +{ + return new BoreschRestraints(*this); +} + +QString BoreschRestraints::toString() const +{ + if (this->isEmpty()) + return QObject::tr("BoreschRestraints::null"); + + QStringList parts; + + const auto n = this->count(); + + if (n <= 10) + { + for (int i = 0; i < n; ++i) + { + parts.append(QObject::tr("%1: %2").arg(i).arg(this->r.at(i).toString())); + } + } + else + { + for (int i = 0; i < 5; ++i) + { + parts.append(QObject::tr("%1: %2").arg(i).arg(this->r.at(i).toString())); + } + + parts.append("..."); + + for (int i = n - 5; i < n; ++i) + { + parts.append(QObject::tr("%1: %2").arg(i).arg(this->r.at(i).toString())); + } + } + + return QObject::tr("BoreschRestraints( name=%1, size=%2\n%3\n)").arg(this->name()).arg(n).arg(parts.join("\n")); +} + +/** Return whether or not this is empty */ +bool BoreschRestraints::isEmpty() const +{ + return this->r.isEmpty(); +} + +/** Return whether or not this is empty */ +bool BoreschRestraints::isNull() const +{ + return this->isEmpty(); +} + +/** Return the number of restraints */ +int BoreschRestraints::nRestraints() const +{ + return this->r.count(); +} + +/** Return the number of restraints */ +int BoreschRestraints::count() const +{ + return this->nRestraints(); +} + +/** Return the number of restraints */ +int BoreschRestraints::size() const +{ + return this->nRestraints(); +} + +/** Return the ith restraint */ +const BoreschRestraint &BoreschRestraints::at(int i) const +{ + i = SireID::Index(i).map(this->r.count()); + + return this->r.at(i); +} + +/** Return the ith restraint */ +const BoreschRestraint &BoreschRestraints::operator[](int i) const +{ + return this->at(i); +} + +/** Return all of the restraints */ +QList BoreschRestraints::restraints() const +{ + return this->r; +} + +/** Add a restraint onto the list */ +void BoreschRestraints::add(const BoreschRestraint &restraint) +{ + if (not restraint.isNull()) + this->r.append(restraint); +} + +/** Add a restraint onto the list */ +void BoreschRestraints::add(const BoreschRestraints &restraints) +{ + this->r += restraints.r; +} + +/** Add a restraint onto the list */ +BoreschRestraints &BoreschRestraints::operator+=(const BoreschRestraint &restraint) +{ + this->add(restraint); + return *this; +} + +/** Add a restraint onto the list */ +BoreschRestraints BoreschRestraints::operator+(const BoreschRestraint &restraint) const +{ + BoreschRestraints ret(*this); + ret += restraint; + return *this; +} + +/** Add restraints onto the list */ +BoreschRestraints &BoreschRestraints::operator+=(const BoreschRestraints &restraints) +{ + this->add(restraints); + return *this; +} + +/** Add restraints onto the list */ +BoreschRestraints BoreschRestraints::operator+(const BoreschRestraints &restraints) const +{ + BoreschRestraints ret(*this); + ret += restraints; + return *this; +} diff --git a/corelib/src/libs/SireMM/boreschrestraints.h b/corelib/src/libs/SireMM/boreschrestraints.h new file mode 100644 index 000000000..963e9e423 --- /dev/null +++ b/corelib/src/libs/SireMM/boreschrestraints.h @@ -0,0 +1,212 @@ +/********************************************\ + * + * Sire - Molecular Simulation Framework + * + * Copyright (C) 2023 Christopher Woods + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * For full details of the license please see the COPYING file + * that should have come with this distribution. + * + * You can contact the authors via the website + * at https://sire.openbiosim.org + * +\*********************************************/ + +#ifndef SIREMM_BORESCH_RESTRAINTS_H +#define SIREMM_BORESCH_RESTRAINTS_H + +#include "restraints.h" + +#include "SireMaths/vector.h" + +#include "SireUnits/dimensions.h" +#include "SireUnits/generalunit.h" + +SIRE_BEGIN_HEADER + +namespace SireMM +{ + class BoreschRestraint; + class BoreschRestraints; +} + +SIREMM_EXPORT QDataStream &operator<<(QDataStream &, const SireMM::BoreschRestraints &); +SIREMM_EXPORT QDataStream &operator>>(QDataStream &, SireMM::BoreschRestraints &); + +SIREMM_EXPORT QDataStream &operator<<(QDataStream &, const SireMM::BoreschRestraint &); +SIREMM_EXPORT QDataStream &operator>>(QDataStream &, SireMM::BoreschRestraint &); + +namespace SireMM +{ + /** This class provides information about a single Boresch restaint. + * This is a collection of distance, angle and torsion restraints + * that hold a ligand in a binding pose relative to a receptor + */ + class SIREMM_EXPORT BoreschRestraint + : public SireBase::ConcreteProperty + { + friend QDataStream & ::operator<<(QDataStream &, const SireMM::BoreschRestraint &); + friend QDataStream & ::operator>>(QDataStream &, SireMM::BoreschRestraint &); + + public: + BoreschRestraint(); + + BoreschRestraint(const QList &receptor, + const QList &ligand, + const SireUnits::Dimension::Length &r0, + const QVector &theta0, + const QVector &phi0, + const SireUnits::Dimension::HarmonicBondConstant &kr, + const QVector &ktheta, + const QVector &kphi); + + BoreschRestraint(const BoreschRestraint &other); + + ~BoreschRestraint(); + + BoreschRestraint &operator=(const BoreschRestraint &other); + + bool operator==(const BoreschRestraint &other) const; + bool operator!=(const BoreschRestraint &other) const; + + BoreschRestraints operator+(const BoreschRestraint &other) const; + BoreschRestraints operator+(const BoreschRestraints &other) const; + + static const char *typeName(); + const char *what() const; + + BoreschRestraint *clone() const; + + QString toString() const; + + bool isNull() const; + + QVector receptorAtoms() const; + QVector ligandAtoms() const; + + SireUnits::Dimension::Length r0() const; + QVector theta0() const; + QVector phi0() const; + + SireUnits::Dimension::HarmonicBondConstant kr() const; + QVector ktheta() const; + QVector kphi() const; + + private: + /** The three receptor atoms involved in the restraint */ + QVector receptor_atms; + + /** The three ligand atoms involved in the restraint */ + QVector ligand_atms; + + /** The equilibium bond length */ + SireUnits::Dimension::Length _r0; + + /** The equilibrium angle sizes */ + QVector _theta0; + + /** The equilibrium torsion sizes */ + QVector _phi0; + + /** The bond force constant */ + SireUnits::Dimension::HarmonicBondConstant _kr; + + /** The angle force constants */ + QVector _ktheta; + + /** The dihedral force constants */ + QVector _kphi; + }; + + /** This class provides the information for a collection of positional + * restraints that can be added to a collection of molecues. Each + * restraint can act on a particle or the centroid of a collection + * of particles. The restaints are spherically symmetric, and + * are either flat-bottom harmonics or harmonic potentials + */ + class SIREMM_EXPORT BoreschRestraints + : public SireBase::ConcreteProperty + { + friend QDataStream & ::operator<<(QDataStream &, const SireMM::BoreschRestraints &); + friend QDataStream & ::operator>>(QDataStream &, SireMM::BoreschRestraints &); + + public: + BoreschRestraints(); + BoreschRestraints(const QString &name); + + BoreschRestraints(const BoreschRestraint &restraint); + BoreschRestraints(const QList &restraints); + + BoreschRestraints(const QString &name, + const BoreschRestraint &restraint); + + BoreschRestraints(const QString &name, + const QList &restraints); + + BoreschRestraints(const BoreschRestraints &other); + + ~BoreschRestraints(); + + BoreschRestraints &operator=(const BoreschRestraints &other); + + bool operator==(const BoreschRestraints &other) const; + bool operator!=(const BoreschRestraints &other) const; + + static const char *typeName(); + const char *what() const; + + BoreschRestraints *clone() const; + + QString toString() const; + + bool isEmpty() const; + bool isNull() const; + + int count() const; + int size() const; + int nRestraints() const; + + const BoreschRestraint &at(int i) const; + const BoreschRestraint &operator[](int i) const; + + QList restraints() const; + + void add(const BoreschRestraint &restraint); + void add(const BoreschRestraints &restraints); + + BoreschRestraints &operator+=(const BoreschRestraint &restraint); + BoreschRestraints &operator+=(const BoreschRestraints &restraints); + + BoreschRestraints operator+(const BoreschRestraint &restraint) const; + BoreschRestraints operator+(const BoreschRestraints &restraints) const; + + private: + /** The actual list of restraints*/ + QList r; + }; + +} + +Q_DECLARE_METATYPE(SireMM::BoreschRestraint) +Q_DECLARE_METATYPE(SireMM::BoreschRestraints) + +SIRE_EXPOSE_CLASS(SireMM::BoreschRestraint) +SIRE_EXPOSE_CLASS(SireMM::BoreschRestraints) + +SIRE_END_HEADER + +#endif diff --git a/corelib/src/libs/SireMM/distancerestraint.cpp b/corelib/src/libs/SireMM/distancerestraint.cpp index 2f74f36d2..6f3b450aa 100644 --- a/corelib/src/libs/SireMM/distancerestraint.cpp +++ b/corelib/src/libs/SireMM/distancerestraint.cpp @@ -560,7 +560,7 @@ static Expression diffHarmonicFunction(double force_constant) /** Return a distance restraint that applies a harmonic potential between the points 'point0' and 'point1' using a force constant 'force_constant' */ DistanceRestraint DistanceRestraint::harmonic(const PointRef &point0, const PointRef &point1, - const HarmonicDistanceForceConstant &force_constant) + const SireUnits::Dimension::HarmonicBondConstant &force_constant) { return DistanceRestraint(point0, point1, ::harmonicFunction(force_constant), ::diffHarmonicFunction(force_constant)); @@ -603,7 +603,7 @@ static Expression diffHalfHarmonicFunction(double force_constant, double distanc using a force constant 'force_constant' */ DistanceRestraint DistanceRestraint::halfHarmonic(const PointRef &point0, const PointRef &point1, const Length &distance, - const HarmonicDistanceForceConstant &force_constant) + const SireUnits::Dimension::HarmonicBondConstant &force_constant) { return DistanceRestraint(point0, point1, ::halfHarmonicFunction(force_constant, distance), ::diffHalfHarmonicFunction(force_constant, distance)); diff --git a/corelib/src/libs/SireMM/distancerestraint.h b/corelib/src/libs/SireMM/distancerestraint.h index 6c7a49927..f594f6a28 100644 --- a/corelib/src/libs/SireMM/distancerestraint.h +++ b/corelib/src/libs/SireMM/distancerestraint.h @@ -66,9 +66,6 @@ namespace SireMM using SireCAS::Symbol; using SireCAS::Symbols; - // typedef the unit of a harmonic force constant ( MolarEnergy / Length^2 ) - typedef SireUnits::Dimension::PhysUnit<1, 0, -2, 0, 0, -1, 0> HarmonicDistanceForceConstant; - /** This is a restraint that operates on the distance between two SireMM::Point objects (e.g. two atoms in a molecule, a point in space and the center of a molecule, the @@ -136,11 +133,11 @@ namespace SireMM bool usesMoleculesIn(const Molecules &molecules) const; static DistanceRestraint harmonic(const PointRef &point0, const PointRef &point1, - const HarmonicDistanceForceConstant &force_constant); + const SireUnits::Dimension::HarmonicBondConstant &force_constant); static DistanceRestraint halfHarmonic(const PointRef &point0, const PointRef &point1, const SireUnits::Dimension::Length &distance, - const HarmonicDistanceForceConstant &force_constant); + const SireUnits::Dimension::HarmonicBondConstant &force_constant); protected: DistanceRestraint(const PointRef &point0, const PointRef &point1, const Expression &nrg_restraint, @@ -359,8 +356,6 @@ Q_DECLARE_METATYPE(SireMM::DistanceRestraint) Q_DECLARE_METATYPE(SireMM::DoubleDistanceRestraint) Q_DECLARE_METATYPE(SireMM::TripleDistanceRestraint) -Q_DECLARE_METATYPE(SireMM::HarmonicDistanceForceConstant) - SIRE_EXPOSE_CLASS(SireMM::DistanceRestraint) SIRE_EXPOSE_CLASS(SireMM::DoubleDistanceRestraint) SIRE_EXPOSE_CLASS(SireMM::TripleDistanceRestraint) diff --git a/corelib/src/libs/SireMM/excludedpairs.cpp b/corelib/src/libs/SireMM/excludedpairs.cpp index 57c15b74d..581de9912 100644 --- a/corelib/src/libs/SireMM/excludedpairs.cpp +++ b/corelib/src/libs/SireMM/excludedpairs.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMM/excludedpairs.h b/corelib/src/libs/SireMM/excludedpairs.h index f2bb679c2..e227bed1d 100644 --- a/corelib/src/libs/SireMM/excludedpairs.h +++ b/corelib/src/libs/SireMM/excludedpairs.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMM/positionalrestraints.cpp b/corelib/src/libs/SireMM/positionalrestraints.cpp new file mode 100644 index 000000000..13a53f989 --- /dev/null +++ b/corelib/src/libs/SireMM/positionalrestraints.cpp @@ -0,0 +1,619 @@ +/********************************************\ + * + * Sire - Molecular Simulation Framework + * + * Copyright (C) 2023 Christopher Woods + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * For full details of the license please see the COPYING file + * that should have come with this distribution. + * + * You can contact the authors via the website + * at https://sire.openbiosim.org + * +\*********************************************/ + +#include "positionalrestraints.h" + +#include "SireUnits/units.h" + +#include "SireID/index.h" + +#include "SireError/errors.h" + +#include "SireStream/datastream.h" +#include "SireStream/shareddatastream.h" + +#include + +using namespace SireMM; +using namespace SireMaths; +using namespace SireBase; +using namespace SireUnits; +using namespace SireUnits::Dimension; +using namespace SireStream; + +/////// +/////// Implementation of PositionalRestraint +/////// + +static const RegisterMetaType r_posrest; + +QDataStream &operator<<(QDataStream &ds, const PositionalRestraint &posrest) +{ + writeHeader(ds, r_posrest, 1); + + SharedDataStream sds(ds); + + sds << posrest.atms << posrest.pos << posrest._k << posrest._r0 + << static_cast(posrest); + + return ds; +} + +QDataStream &operator>>(QDataStream &ds, PositionalRestraint &posrest) +{ + VersionID v = readHeader(ds, r_posrest); + + if (v == 1) + { + SharedDataStream sds(ds); + + sds >> posrest.atms >> posrest.pos >> posrest._k >> posrest._r0 >> static_cast(posrest); + } + else + throw version_error(v, "1", r_posrest, CODELOC); + + return ds; +} + +/** Null constructor */ +PositionalRestraint::PositionalRestraint() + : ConcreteProperty(), + _k(0), _r0(0) +{ +} + +/** Construct to restrain the atom at index 'atom' to the specified position + * using the specified force constant and flat-bottom well-width + */ +PositionalRestraint::PositionalRestraint(qint64 atom, const SireMaths::Vector &position, + const SireUnits::Dimension::HarmonicBondConstant &k, + const SireUnits::Dimension::Length &r0) + : ConcreteProperty(), + pos(position), _k(k), _r0(r0) +{ + atms = QVector(1, atom); + atms.squeeze(); +} + +/** Construct to restrain the centroid of the atoms whose indicies are + * in 'atoms' to the specified position using the specified force constant + * and flat-bottom well width + */ +PositionalRestraint::PositionalRestraint(const QList &atoms, + const SireMaths::Vector &position, + const SireUnits::Dimension::HarmonicBondConstant &k, + const SireUnits::Dimension::Length &r0) + : ConcreteProperty(), + pos(position), _k(k), _r0(r0) +{ + if (atoms.isEmpty()) + return; + + // remove duplicates + atms.reserve(atoms.count()); + + auto sorted = atoms; + std::sort(sorted.begin(), sorted.end()); + + atms.append(sorted.at(0)); + + for (const auto &atom : sorted) + { + if (atom != atms.last()) + atms.append(atom); + } + + atms.squeeze(); +} + +/** Copy constructor */ +PositionalRestraint::PositionalRestraint(const PositionalRestraint &other) + : ConcreteProperty(other), + atms(other.atms), pos(other.pos), _k(other._k), _r0(other._r0) +{ +} + +PositionalRestraint::~PositionalRestraint() +{ +} + +PositionalRestraint &PositionalRestraint::operator=(const PositionalRestraint &other) +{ + if (this != &other) + { + atms = other.atms; + pos = other.pos; + _k = other._k; + _r0 = other._r0; + } + + return *this; +} + +bool PositionalRestraint::operator==(const PositionalRestraint &other) const +{ + return atms == other.atms and pos == other.pos and + _k == other._k and _r0 == other._r0; +} + +bool PositionalRestraint::operator!=(const PositionalRestraint &other) const +{ + return not operator==(other); +} + +PositionalRestraints PositionalRestraint::operator+(const PositionalRestraint &other) const +{ + return PositionalRestraints(*this) + other; +} + +PositionalRestraints PositionalRestraint::operator+(const PositionalRestraints &other) const +{ + return PositionalRestraints(*this) + other; +} + +const char *PositionalRestraint::typeName() +{ + return QMetaType::typeName(qMetaTypeId()); +} + +const char *PositionalRestraint::what() const +{ + return PositionalRestraint::typeName(); +} + +PositionalRestraint *PositionalRestraint::clone() const +{ + return new PositionalRestraint(*this); +} + +bool PositionalRestraint::isNull() const +{ + return atms.isEmpty(); +} + +QString PositionalRestraint::toString() const +{ + if (this->isNull()) + return QObject::tr("PositionalRestraint::null"); + + else if (this->isAtomRestraint()) + { + return QString("PositionalRestraint( %1 => %2, k=%3 : r0=%4 )") + .arg(this->atom()) + .arg(pos.toString()) + .arg(_k.toString()) + .arg(_r0.toString()); + } + else + { + QStringList a; + + for (const auto &atom : atms) + { + a.append(QString::number(atom)); + } + + return QString("PositionalRestraint( [%1] => %2, k=%3 : r0=%4 )") + .arg(a.join(", ")) + .arg(pos.toString()) + .arg(_k.toString()) + .arg(_r0.toString()); + } +} + +/** Return whether this is a single-atom restraint */ +bool PositionalRestraint::isAtomRestraint() const +{ + return atms.count() == 1; +} + +/** Return whether this restraint acts on the centroid of a group + * of atoms */ +bool PositionalRestraint::isCentroidRestraint() const +{ + return atms.count() > 1; +} + +/** Return the index of the atom if this is a single-atom restraint */ +qint64 PositionalRestraint::atom() const +{ + if (not this->isAtomRestraint()) + throw SireError::incompatible_error(QObject::tr( + "You cannot get the atom when this isn't a single-atom restraint!"), + CODELOC); + + return this->atms.at(0); +} + +/** Return the indexes of the atoms whose centroid is to be restrained */ +QVector PositionalRestraint::atoms() const +{ + if (not this->isCentroidRestraint()) + throw SireError::incompatible_error(QObject::tr( + "You cannot get the atoms when this isn't a centroid restraint!"), + CODELOC); + + return this->atms; +} + +/** Return the position in space where the restraint is placed */ +SireMaths::Vector PositionalRestraint::position() const +{ + return this->pos; +} + +/** Return the force constant for the restraint */ +SireUnits::Dimension::HarmonicBondConstant PositionalRestraint::k() const +{ + return this->_k; +} + +/** Return the width of the flat-bottom well. This is zero for a + * pure harmonic restraint + */ +SireUnits::Dimension::Length PositionalRestraint::r0() const +{ + return this->_r0; +} + +/////// +/////// Implementation of PositionalRestraints +/////// + +static const RegisterMetaType r_posrests; + +QDataStream &operator<<(QDataStream &ds, const PositionalRestraints &posrests) +{ + writeHeader(ds, r_posrests, 1); + + SharedDataStream sds(ds); + + sds << posrests.r + << static_cast(posrests); + + return ds; +} + +QDataStream &operator>>(QDataStream &ds, PositionalRestraints &posrests) +{ + VersionID v = readHeader(ds, r_posrests); + + if (v == 1) + { + SharedDataStream sds(ds); + + sds >> posrests.r >> static_cast(posrests); + } + else + throw version_error(v, "1", r_posrests, CODELOC); + + return ds; +} + +/** Null constructor */ +PositionalRestraints::PositionalRestraints() + : ConcreteProperty() +{ +} + +PositionalRestraints::PositionalRestraints(const QString &name) + : ConcreteProperty(name) +{ +} + +PositionalRestraints::PositionalRestraints(const PositionalRestraint &restraint) + : ConcreteProperty() +{ + if (not restraint.isNull()) + r.append(restraint); +} + +PositionalRestraints::PositionalRestraints(const QList &restraints) + : ConcreteProperty() +{ + for (const auto &restraint : restraints) + { + if (not restraint.isNull()) + r.append(restraint); + } +} + +PositionalRestraints::PositionalRestraints(const QString &name, + const PositionalRestraint &restraint) + : ConcreteProperty(name) +{ + if (not restraint.isNull()) + r.append(restraint); +} + +PositionalRestraints::PositionalRestraints(const QString &name, + const QList &restraints) + : ConcreteProperty(name) +{ + for (const auto &restraint : restraints) + { + if (not restraint.isNull()) + r.append(restraint); + } +} + +PositionalRestraints::PositionalRestraints(const PositionalRestraints &other) + : ConcreteProperty(other), r(other.r) +{ +} + +PositionalRestraints::~PositionalRestraints() +{ +} + +PositionalRestraints &PositionalRestraints::operator=(const PositionalRestraints &other) +{ + r = other.r; + Restraints::operator=(other); + return *this; +} + +bool PositionalRestraints::operator==(const PositionalRestraints &other) const +{ + return r == other.r and Restraints::operator==(other); +} + +bool PositionalRestraints::operator!=(const PositionalRestraints &other) const +{ + return not operator==(other); +} + +const char *PositionalRestraints::typeName() +{ + return QMetaType::typeName(qMetaTypeId()); +} + +const char *PositionalRestraints::what() const +{ + return PositionalRestraints::typeName(); +} + +PositionalRestraints *PositionalRestraints::clone() const +{ + return new PositionalRestraints(*this); +} + +QString PositionalRestraints::toString() const +{ + if (this->isEmpty()) + return QObject::tr("PositionalRestraints::null"); + + QStringList parts; + + const auto n = this->count(); + + if (n <= 10) + { + for (int i = 0; i < n; ++i) + { + parts.append(QObject::tr("%1: %2").arg(i).arg(this->r.at(i).toString())); + } + } + else + { + for (int i = 0; i < 5; ++i) + { + parts.append(QObject::tr("%1: %2").arg(i).arg(this->r.at(i).toString())); + } + + parts.append("..."); + + for (int i = n - 5; i < n; ++i) + { + parts.append(QObject::tr("%1: %2").arg(i).arg(this->r.at(i).toString())); + } + } + + return QObject::tr("PositionalRestraints( name=%1, size=%2\n%3\n)").arg(this->name()).arg(n).arg(parts.join("\n")); +} + +/** Return whether or not this is empty */ +bool PositionalRestraints::isEmpty() const +{ + return this->r.isEmpty(); +} + +/** Return whether or not this is empty */ +bool PositionalRestraints::isNull() const +{ + return this->isEmpty(); +} + +/** Return the number of restraints */ +int PositionalRestraints::nRestraints() const +{ + return this->r.count(); +} + +/** Return the number of restraints */ +int PositionalRestraints::count() const +{ + return this->nRestraints(); +} + +/** Return the number of restraints */ +int PositionalRestraints::size() const +{ + return this->nRestraints(); +} + +/** Return the number of atom restraints */ +int PositionalRestraints::nAtomRestraints() const +{ + int n = 0; + + for (const auto &restraint : this->r) + { + n += int(restraint.isAtomRestraint()); + } + + return n; +} + +/** Return the number of centroid restraints */ +int PositionalRestraints::nCentroidRestraints() const +{ + int n = 0; + + for (const auto &restraint : this->r) + { + n += int(restraint.isCentroidRestraint()); + } + + return n; +} + +/** Return whether or not there are any atom restraints */ +bool PositionalRestraints::hasAtomRestraints() const +{ + for (const auto &restraint : this->r) + { + if (restraint.isAtomRestraint()) + return true; + } + + return false; +} + +/** Return whether or not there are any centroid restraints */ +bool PositionalRestraints::hasCentroidRestraints() const +{ + for (const auto &restraint : this->r) + { + if (restraint.isCentroidRestraint()) + return true; + } + + return false; +} + +/** Return the ith restraint */ +const PositionalRestraint &PositionalRestraints::at(int i) const +{ + i = SireID::Index(i).map(this->r.count()); + + return this->r.at(i); +} + +/** Return the ith restraint */ +const PositionalRestraint &PositionalRestraints::operator[](int i) const +{ + return this->at(i); +} + +/** Return all of the restraints */ +QList PositionalRestraints::restraints() const +{ + return this->r; +} + +/** Return all of the atom restraints */ +QList PositionalRestraints::atomRestraints() const +{ + if (this->hasCentroidRestraints()) + { + QList ar; + + for (const auto &restraint : this->r) + { + if (restraint.isAtomRestraint()) + ar.append(restraint); + } + + return ar; + } + else + return this->restraints(); +} + +/** Return all of the centroid restraints */ +QList PositionalRestraints::centroidRestraints() const +{ + if (this->hasAtomRestraints()) + { + QList cr; + + for (const auto &restraint : this->r) + { + if (restraint.isCentroidRestraint()) + cr.append(restraint); + } + + return cr; + } + else + return this->restraints(); +} + +/** Add a restraint onto the list */ +void PositionalRestraints::add(const PositionalRestraint &restraint) +{ + if (not restraint.isNull()) + this->r.append(restraint); +} + +/** Add a restraint onto the list */ +void PositionalRestraints::add(const PositionalRestraints &restraints) +{ + this->r += restraints.r; +} + +/** Add a restraint onto the list */ +PositionalRestraints &PositionalRestraints::operator+=(const PositionalRestraint &restraint) +{ + this->add(restraint); + return *this; +} + +/** Add a restraint onto the list */ +PositionalRestraints PositionalRestraints::operator+(const PositionalRestraint &restraint) const +{ + PositionalRestraints ret(*this); + ret += restraint; + return *this; +} + +/** Add restraints onto the list */ +PositionalRestraints &PositionalRestraints::operator+=(const PositionalRestraints &restraints) +{ + this->add(restraints); + return *this; +} + +/** Add restraints onto the list */ +PositionalRestraints PositionalRestraints::operator+(const PositionalRestraints &restraints) const +{ + PositionalRestraints ret(*this); + ret += restraints; + return *this; +} diff --git a/corelib/src/libs/SireMM/positionalrestraints.h b/corelib/src/libs/SireMM/positionalrestraints.h new file mode 100644 index 000000000..abda2d9d7 --- /dev/null +++ b/corelib/src/libs/SireMM/positionalrestraints.h @@ -0,0 +1,209 @@ +/********************************************\ + * + * Sire - Molecular Simulation Framework + * + * Copyright (C) 2023 Christopher Woods + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * For full details of the license please see the COPYING file + * that should have come with this distribution. + * + * You can contact the authors via the website + * at https://sire.openbiosim.org + * +\*********************************************/ + +#ifndef SIREMM_POSITIONALRESTRAINTS_H +#define SIREMM_POSITIONALRESTRAINTS_H + +#include "restraints.h" + +#include "SireMaths/vector.h" + +#include "SireUnits/dimensions.h" +#include "SireUnits/generalunit.h" + +SIRE_BEGIN_HEADER + +namespace SireMM +{ + class PositionalRestraint; + class PositionalRestraints; +} + +SIREMM_EXPORT QDataStream &operator<<(QDataStream &, const SireMM::PositionalRestraints &); +SIREMM_EXPORT QDataStream &operator>>(QDataStream &, SireMM::PositionalRestraints &); + +SIREMM_EXPORT QDataStream &operator<<(QDataStream &, const SireMM::PositionalRestraint &); +SIREMM_EXPORT QDataStream &operator>>(QDataStream &, SireMM::PositionalRestraint &); + +namespace SireMM +{ + /** This class provides information about a single positional restraint. + * This is spherically symmetric and can act on either a single particle, + * or the centroid of a group of particles. The restraints are either + * flat-bottom harmonics or harmonic potentials + */ + class SIREMM_EXPORT PositionalRestraint + : public SireBase::ConcreteProperty + { + friend QDataStream & ::operator<<(QDataStream &, const SireMM::PositionalRestraint &); + friend QDataStream & ::operator>>(QDataStream &, SireMM::PositionalRestraint &); + + public: + PositionalRestraint(); + PositionalRestraint(qint64 atom, const SireMaths::Vector &position, + const SireUnits::Dimension::HarmonicBondConstant &k, + const SireUnits::Dimension::Length &r0); + + PositionalRestraint(const QList &atoms, + const SireMaths::Vector &position, + const SireUnits::Dimension::HarmonicBondConstant &k, + const SireUnits::Dimension::Length &r0); + + PositionalRestraint(const PositionalRestraint &other); + + ~PositionalRestraint(); + + PositionalRestraint &operator=(const PositionalRestraint &other); + + bool operator==(const PositionalRestraint &other) const; + bool operator!=(const PositionalRestraint &other) const; + + PositionalRestraints operator+(const PositionalRestraint &other) const; + PositionalRestraints operator+(const PositionalRestraints &other) const; + + static const char *typeName(); + const char *what() const; + + PositionalRestraint *clone() const; + + QString toString() const; + + bool isNull() const; + + bool isAtomRestraint() const; + bool isCentroidRestraint() const; + + qint64 atom() const; + QVector atoms() const; + + SireMaths::Vector position() const; + + SireUnits::Dimension::HarmonicBondConstant k() const; + SireUnits::Dimension::Length r0() const; + + private: + /** The atoms involved in the restraint */ + QVector atms; + + /** The location of the restraint */ + SireMaths::Vector pos; + + /** The force constant */ + SireUnits::Dimension::HarmonicBondConstant _k; + + /** The flat-bottom width (0 of harmonic restraints) */ + SireUnits::Dimension::Length _r0; + }; + + /** This class provides the information for a collection of positional + * restraints that can be added to a collection of molecues. Each + * restraint can act on a particle or the centroid of a collection + * of particles. The restaints are spherically symmetric, and + * are either flat-bottom harmonics or harmonic potentials + */ + class SIREMM_EXPORT PositionalRestraints + : public SireBase::ConcreteProperty + { + friend QDataStream & ::operator<<(QDataStream &, const SireMM::PositionalRestraints &); + friend QDataStream & ::operator>>(QDataStream &, SireMM::PositionalRestraints &); + + public: + PositionalRestraints(); + PositionalRestraints(const QString &name); + + PositionalRestraints(const PositionalRestraint &restraint); + PositionalRestraints(const QList &restraints); + + PositionalRestraints(const QString &name, + const PositionalRestraint &restraint); + + PositionalRestraints(const QString &name, + const QList &restraints); + + PositionalRestraints(const PositionalRestraints &other); + + ~PositionalRestraints(); + + PositionalRestraints &operator=(const PositionalRestraints &other); + + bool operator==(const PositionalRestraints &other) const; + bool operator!=(const PositionalRestraints &other) const; + + static const char *typeName(); + const char *what() const; + + PositionalRestraints *clone() const; + + QString toString() const; + + bool isEmpty() const; + bool isNull() const; + + int count() const; + int size() const; + int nRestraints() const; + + int nAtomRestraints() const; + int nCentroidRestraints() const; + + bool hasAtomRestraints() const; + bool hasCentroidRestraints() const; + + const PositionalRestraint &at(int i) const; + const PositionalRestraint &operator[](int i) const; + + QList restraints() const; + + QList atomRestraints() const; + QList centroidRestraints() const; + + void add(const PositionalRestraint &restraint); + void add(const PositionalRestraints &restraints); + + PositionalRestraints &operator+=(const PositionalRestraint &restraint); + PositionalRestraints &operator+=(const PositionalRestraints &restraints); + + PositionalRestraints operator+(const PositionalRestraint &restraint) const; + PositionalRestraints operator+(const PositionalRestraints &restraints) const; + + private: + /** The actual list of restraints*/ + QList r; + }; + +} + +Q_DECLARE_METATYPE(SireMM::PositionalRestraint) +Q_DECLARE_METATYPE(SireMM::PositionalRestraints) + +SIRE_EXPOSE_CLASS(SireMM::PositionalRestraint) +SIRE_EXPOSE_CLASS(SireMM::PositionalRestraints) + +SIRE_END_HEADER + +#endif diff --git a/corelib/src/libs/SireMM/restraints.cpp b/corelib/src/libs/SireMM/restraints.cpp new file mode 100644 index 000000000..0dcb12218 --- /dev/null +++ b/corelib/src/libs/SireMM/restraints.cpp @@ -0,0 +1,120 @@ +/********************************************\ + * + * Sire - Molecular Simulation Framework + * + * Copyright (C) 2023 Christopher Woods + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * For full details of the license please see the COPYING file + * that should have come with this distribution. + * + * You can contact the authors via the website + * at https://sire.openbiosim.org + * +\*********************************************/ + +#include "restraints.h" + +#include "SireStream/datastream.h" +#include "SireStream/shareddatastream.h" + +using namespace SireMM; +using namespace SireBase; +using namespace SireStream; + +static const RegisterMetaType r_restraints(MAGIC_ONLY, + Restraints::typeName()); + +QDataStream &operator<<(QDataStream &ds, const Restraints &restraints) +{ + writeHeader(ds, r_restraints, 1); + + ds << restraints.nme << static_cast(restraints); + + return ds; +} + +QDataStream &operator>>(QDataStream &ds, Restraints &restraints) +{ + VersionID v = readHeader(ds, r_restraints); + + if (v == 1) + { + ds >> restraints.nme >> static_cast(restraints); + } + else + throw version_error(v, "1", r_restraints, CODELOC); + + return ds; +} + +Restraints::Restraints() : Property(), nme("restraint") +{ +} + +Restraints::Restraints(const QString &name) + : Property() +{ + this->setName(name); +} + +Restraints::Restraints(const Restraints &other) + : Property(other), nme(other.nme) +{ +} + +Restraints::~Restraints() +{ +} + +/** Return whether or not this is a Restraints object (or derived + from this object) */ +bool Restraints::isRestraints() const +{ + return true; +} + +/** Return the name given to this group of restraints */ +QString Restraints::name() const +{ + return this->nme; +} + +/** Set the name for this group of restraints */ +void Restraints::setName(const QString &name) +{ + this->nme = name.simplified(); + + if (this->nme.isEmpty()) + this->nme = "restraints"; +} + +Restraints &Restraints::operator=(const Restraints &other) +{ + nme = other.nme; + Property::operator=(other); + return *this; +} + +bool Restraints::operator==(const Restraints &other) const +{ + return nme == other.nme; +} + +bool Restraints::operator!=(const Restraints &other) const +{ + return not this->operator==(other); +} diff --git a/corelib/src/libs/SireMM/restraints.h b/corelib/src/libs/SireMM/restraints.h new file mode 100644 index 000000000..aaac6f401 --- /dev/null +++ b/corelib/src/libs/SireMM/restraints.h @@ -0,0 +1,92 @@ +/********************************************\ + * + * Sire - Molecular Simulation Framework + * + * Copyright (C) 2023 Christopher Woods + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * For full details of the license please see the COPYING file + * that should have come with this distribution. + * + * You can contact the authors via the website + * at https://sire.openbiosim.org + * +\*********************************************/ + +#ifndef SIREMM_RESTRAINTS_H +#define SIREMM_RESTRAINTS_H + +#include "SireBase/property.h" + +namespace SireMM +{ + class Restraints; +} + +SIREMM_EXPORT QDataStream &operator<<(QDataStream &ds, const SireMM::Restraints &); +SIREMM_EXPORT QDataStream &operator>>(QDataStream &ds, SireMM::Restraints &); + +namespace SireMM +{ + /** This is the base class of all of the `Restraints` collections, + * e.g. PositionalRestraints, BoreschRestraints etc. + * + * These are not related to the legacy SireMM::Restraint classes, + * which calculate restraints directly. Instead, these classes + * provide information about the restraints that can be picked + * up and used by different engines (e.g. OpenMM) + */ + class SIREMM_EXPORT Restraints : public SireBase::Property + { + friend QDataStream & ::operator<<(QDataStream &, const Restraints &); + friend QDataStream & ::operator>>(QDataStream &, Restraints &); + + public: + Restraints(); + Restraints(const QString &name); + + Restraints(const Restraints &other); + + virtual ~Restraints(); + + virtual Restraints *clone() const = 0; + + static const char *typeName() + { + return "SireMM::Restraints"; + } + + QString name() const; + + void setName(const QString &name); + + bool isRestraints() const; + + protected: + Restraints &operator=(const Restraints &other); + + bool operator==(const Restraints &other) const; + bool operator!=(const Restraints &other) const; + + private: + /** The name of this group of restraints */ + QString nme; + }; +} + +SIRE_EXPOSE_CLASS(SireMM::Restraints) + +#endif diff --git a/corelib/src/libs/SireMaths/CMakeLists.txt b/corelib/src/libs/SireMaths/CMakeLists.txt index 19df255bb..1adae5e93 100644 --- a/corelib/src/libs/SireMaths/CMakeLists.txt +++ b/corelib/src/libs/SireMaths/CMakeLists.txt @@ -19,6 +19,7 @@ set ( SIREMATHS_HEADERS complex.h constants.h distvector.h + energytrajectory.h errors.h freeenergyaverage.h gamma.h @@ -66,6 +67,7 @@ set ( SIREMATHS_SOURCES boys.cpp complex.cpp distvector.cpp + energytrajectory.cpp errors.cpp freeenergyaverage.cpp gamma.cpp diff --git a/corelib/src/libs/SireMaths/energytrajectory.cpp b/corelib/src/libs/SireMaths/energytrajectory.cpp new file mode 100644 index 000000000..fa53fbdf8 --- /dev/null +++ b/corelib/src/libs/SireMaths/energytrajectory.cpp @@ -0,0 +1,436 @@ +/********************************************\ + * + * Sire - Molecular Simulation Framework + * + * Copyright (C) 2023 Christopher Woods + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * For full details of the license please see the COPYING file + * that should have come with this distribution. + * + * You can contact the authors via the website + * at https://sire.openbiosim.org + * +\*********************************************/ + +#include "energytrajectory.h" + +#include "SireUnits/units.h" + +#include "SireID/index.h" + +#include "SireError/errors.h" + +#include "SireStream/datastream.h" +#include "SireStream/shareddatastream.h" + +using namespace SireMaths; +using namespace SireBase; +using namespace SireStream; +using namespace SireUnits; +using namespace SireUnits::Dimension; + +static const RegisterMetaType r_etraj; + +QDataStream &operator<<(QDataStream &ds, const EnergyTrajectory &etraj) +{ + writeHeader(ds, r_etraj, 1); + + SharedDataStream sds(ds); + + sds << etraj.time_values << etraj.energy_values + << static_cast(etraj); + + return ds; +} + +QDataStream &operator>>(QDataStream &ds, EnergyTrajectory &etraj) +{ + VersionID v = readHeader(ds, r_etraj); + + if (v == 1) + { + SharedDataStream sds(ds); + + sds >> etraj.time_values >> etraj.energy_values >> static_cast(etraj); + } + else + throw version_error(v, "1", r_etraj, CODELOC); + + return ds; +} + +EnergyTrajectory::EnergyTrajectory() + : ConcreteProperty() +{ +} + +EnergyTrajectory::EnergyTrajectory(const EnergyTrajectory &other) + : ConcreteProperty(other), + time_values(other.time_values), energy_values(other.energy_values) +{ +} + +EnergyTrajectory::~EnergyTrajectory() +{ +} + +EnergyTrajectory &EnergyTrajectory::operator=(const EnergyTrajectory &other) +{ + if (this != &other) + { + time_values = other.time_values; + energy_values = other.energy_values; + Property::operator=(other); + } + + return *this; +} + +bool EnergyTrajectory::operator==(const EnergyTrajectory &other) const +{ + return time_values == other.time_values and + energy_values == other.energy_values; +} + +bool EnergyTrajectory::operator!=(const EnergyTrajectory &other) const +{ + return not this->operator==(other); +} + +EnergyTrajectory *EnergyTrajectory::clone() const +{ + return new EnergyTrajectory(*this); +} + +const char *EnergyTrajectory::typeName() +{ + return QMetaType::typeName(qMetaTypeId()); +} + +const char *EnergyTrajectory::what() const +{ + return EnergyTrajectory::typeName(); +} + +QString EnergyTrajectory::toString() const +{ + if (this->isEmpty()) + return QObject::tr("EnergyTrajectory::empty"); + + QStringList parts; + + const auto n = this->count(); + + auto keys = this->energy_values.keys(); + keys.sort(); + keys.insert(0, "time"); + + parts.append(keys.join("\t")); + + if (n <= 10) + { + for (int i = 0; i < n; ++i) + { + const auto vals = this->get(i, GeneralUnit(picosecond), GeneralUnit(kcal_per_mol)); + + QStringList v; + + for (const auto &key : keys) + { + v.append(QString::number(vals[key])); + } + + parts.append(v.join("\t")); + } + } + else + { + for (int i = 0; i < 5; ++i) + { + const auto vals = this->get(i, GeneralUnit(picosecond), GeneralUnit(kcal_per_mol)); + + QStringList v; + + for (const auto &key : keys) + { + v.append(QString::number(vals[key])); + } + + parts.append(v.join("\t")); + } + + parts.append("..."); + + for (int i = n - 5; i < n; ++i) + { + const auto vals = this->get(i, GeneralUnit(picosecond), GeneralUnit(kcal_per_mol)); + + QStringList v; + + for (const auto &key : keys) + { + v.append(QString::number(vals[key])); + } + + parts.append(v.join("\t")); + } + } + + return QObject::tr("SelectorMol( size=%1\n%2\n)").arg(n).arg(parts.join("\n")); +} + +/** Set the energies at time 'time' to the components contained + * in 'energies' + */ +void EnergyTrajectory::set(const GeneralUnit &time, + const QHash &energies) +{ + if (energies.isEmpty()) + return; + + auto t = Time(time); + + for (auto it = energies.constBegin(); it != energies.constEnd(); ++it) + { + // make sure all of the values are valid + if (it.key() == "time") + { + throw SireError::invalid_key(QObject::tr( + "You cannot call an energy component 'time'. This name " + "is reserved for the time component."), + CODELOC); + } + + auto nrg = MolarEnergy(it.value()); + } + + // make sure we have populated all of the columns - this + // populates with NaNs if the column doesn't exist + QVector null_column; + + for (const auto &key : energies.keys()) + { + if (not this->energy_values.contains(key)) + { + if (null_column.isEmpty() and not time_values.isEmpty()) + { + null_column = QVector(time_values.count(), NAN); + } + + this->energy_values.insert(key, null_column); + } + } + + // find the index of the time, and add it as needed. + // Times are sorted, so start from the end and work towards + // the beginning (as we will normally be adding energies onto + // the end) + int idx = time_values.count(); + bool must_create = true; + + while (idx > 0) + { + if (t > time_values[idx - 1]) + break; + + else if (t == time_values[idx - 1]) + { + must_create = false; + idx = idx - 1; + break; + } + + idx -= 1; + } + + if (idx >= time_values.count()) + { + time_values.append(t.value()); + + for (auto it = this->energy_values.begin(); + it != this->energy_values.end(); ++it) + { + it.value().append(NAN); + } + } + else if (must_create) + { + time_values.insert(idx, t.value()); + + for (auto it = this->energy_values.begin(); + it != this->energy_values.end(); ++it) + { + it.value().insert(idx, NAN); + } + } + + for (auto it = energies.constBegin(); + it != energies.constEnd(); ++it) + { + this->energy_values[it.key()][idx] = MolarEnergy(it.value()).value(); + } +} + +/** Return whether or not this is empty */ +bool EnergyTrajectory::isEmpty() const +{ + return this->time_values.isEmpty(); +} + +bool EnergyTrajectory::isNull() const +{ + return this->isEmpty(); +} + +/** Return the number of time values (number of rows) */ +int EnergyTrajectory::count() const +{ + return this->time_values.count(); +} + +/** Return the number of time values (number of rows) */ +int EnergyTrajectory::size() const +{ + return this->count(); +} + +/** Return the time and energy components at the ith row. + * Values are returned in internal units + */ +QHash EnergyTrajectory::operator[](int i) const +{ + i = SireID::Index(i).map(this->count()); + + QHash vals; + vals.reserve(1 + this->energy_values.count()); + + vals.insert("time", this->time_values[i]); + + for (auto it = this->energy_values.constBegin(); + it != this->energy_values.constEnd(); + ++it) + { + vals.insert(it.key(), it.value()[i]); + } + + return vals; +} + +/** Return the time and energy components at the ith row. + * Values are returned in internal units + */ +QHash EnergyTrajectory::get(int i) const +{ + return this->operator[](i); +} + +/** Return the time and energy components at the ith row. + * Values are returned in the specified units + */ +QHash EnergyTrajectory::get(int i, + const GeneralUnit &time_unit, + const GeneralUnit &energy_unit) const +{ + i = SireID::Index(i).map(this->count()); + + QHash vals; + vals.reserve(1 + this->energy_values.count()); + + vals.insert("time", Time(this->time_values[i]).to(time_unit)); + + for (auto it = this->energy_values.constBegin(); + it != this->energy_values.constEnd(); + ++it) + { + vals.insert(it.key(), MolarEnergy(it.value()[i]).to(energy_unit)); + } + + return vals; +} + +/** Return all of the energy keys */ +QStringList EnergyTrajectory::keys() const +{ + return this->energy_values.keys(); +} + +/** Return all of the time values (the time column). This is + * sorted from earliest to latest time, and is in the default internal + * unit + */ +QVector EnergyTrajectory::times() const +{ + return this->time_values; +} + +/** Return all of the energy values for the passed key (the energy-key column). + * This is in the same order as the times, and is in the default internal + * unit + */ +QVector EnergyTrajectory::energies(const QString &key) const +{ + auto it = this->energy_values.constFind(key); + + if (it == this->energy_values.constEnd()) + throw SireError::invalid_key(QObject::tr( + "There is no energy component with key '%1'. Valid " + "keys are [ %2 ].") + .arg(key) + .arg(this->energy_values.keys().join(", ")), + CODELOC); + + return it.value(); +} + +/** Return all of the times converted to the passed unit */ +QVector EnergyTrajectory::times(const SireUnits::Dimension::GeneralUnit &time_unit) const +{ + const double factor = 1.0 / Time(time_unit).value(); + + if (factor == 1) + return this->times(); + + auto t = this->times(); + + for (auto &val : t) + { + val *= factor; + } + + return t; +} + +/** Return all of the energies fro the passed key converted to the + * passed unit + */ +QVector EnergyTrajectory::energies(const QString &key, + const SireUnits::Dimension::GeneralUnit &energy_unit) const +{ + const double factor = 1.0 / MolarEnergy(energy_unit).value(); + + if (factor == 1) + return this->energies(key); + + auto e = this->energies(key); + + for (auto &val : e) + { + val *= factor; + } + + return e; +} diff --git a/corelib/src/libs/SireMaths/energytrajectory.h b/corelib/src/libs/SireMaths/energytrajectory.h new file mode 100644 index 000000000..2b7152206 --- /dev/null +++ b/corelib/src/libs/SireMaths/energytrajectory.h @@ -0,0 +1,117 @@ +/********************************************\ + * + * Sire - Molecular Simulation Framework + * + * Copyright (C) 2023 Christopher Woods + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * For full details of the license please see the COPYING file + * that should have come with this distribution. + * + * You can contact the authors via the website + * at https://sire.openbiosim.org + * +\*********************************************/ + +#ifndef SIREMATHS_ENERGYTRAJECTORY_H +#define SIREMATHS_ENERGYTRAJECTORY_H + +#include "SireUnits/generalunit.h" +#include "SireUnits/dimensions.h" + +#include "SireBase/property.h" + +SIRE_BEGIN_HEADER + +namespace SireMaths +{ + class EnergyTrajectory; +} + +SIREMATHS_EXPORT QDataStream &operator<<(QDataStream &ds, const SireMaths::EnergyTrajectory &); +SIREMATHS_EXPORT QDataStream &operator>>(QDataStream &ds, SireMaths::EnergyTrajectory &); + +namespace SireMaths +{ + /** This class holds the trajectory of energies, organised by + * timestep the energy was recorded and the types of energy + * (e.g. kinetic, potential, values at different lambda windows) + */ + class SIREMATHS_EXPORT EnergyTrajectory + : public SireBase::ConcreteProperty + { + friend QDataStream & ::operator<<(QDataStream &, const EnergyTrajectory &); + friend QDataStream & ::operator>>(QDataStream &, EnergyTrajectory &); + + public: + EnergyTrajectory(); + EnergyTrajectory(const EnergyTrajectory &other); + + ~EnergyTrajectory(); + + EnergyTrajectory &operator=(const EnergyTrajectory &other); + + bool operator==(const EnergyTrajectory &other) const; + bool operator!=(const EnergyTrajectory &other) const; + + virtual EnergyTrajectory *clone() const; + + static const char *typeName(); + const char *what() const; + + QString toString() const; + + bool isEmpty() const; + bool isNull() const; + + int count() const; + int size() const; + + QHash operator[](int i) const; + + QHash get(int i) const; + QHash get(int i, + const SireUnits::Dimension::GeneralUnit &time_unit, + const SireUnits::Dimension::GeneralUnit &energy_unit) const; + + void set(const SireUnits::Dimension::GeneralUnit &time, + const QHash &energies); + + QStringList keys() const; + + QVector times() const; + QVector energies(const QString &key) const; + + QVector times(const SireUnits::Dimension::GeneralUnit &time_unit) const; + QVector energies(const QString &key, + const SireUnits::Dimension::GeneralUnit &energy_unit) const; + + private: + /** All of the time values */ + QVector time_values; + + /** All of the energy values */ + QHash> energy_values; + }; +} + +Q_DECLARE_METATYPE(SireMaths::EnergyTrajectory) + +SIRE_EXPOSE_CLASS(SireMaths::EnergyTrajectory) + +SIRE_END_HEADER + +#endif diff --git a/corelib/src/libs/SireMol/atommapping.cpp b/corelib/src/libs/SireMol/atommapping.cpp index 7ea808c57..be33f0329 100644 --- a/corelib/src/libs/SireMol/atommapping.cpp +++ b/corelib/src/libs/SireMol/atommapping.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMol/atommapping.h b/corelib/src/libs/SireMol/atommapping.h index 210998457..033e6fe41 100644 --- a/corelib/src/libs/SireMol/atommapping.h +++ b/corelib/src/libs/SireMol/atommapping.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMol/atommatch.cpp b/corelib/src/libs/SireMol/atommatch.cpp index 88fa7b6cb..2aa0c1fe2 100644 --- a/corelib/src/libs/SireMol/atommatch.cpp +++ b/corelib/src/libs/SireMol/atommatch.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMol/atommatch.h b/corelib/src/libs/SireMol/atommatch.h index 0e1f3f475..4b4519ce4 100644 --- a/corelib/src/libs/SireMol/atommatch.h +++ b/corelib/src/libs/SireMol/atommatch.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMol/chirality.cpp b/corelib/src/libs/SireMol/chirality.cpp index 74b531007..b56ed9e1c 100644 --- a/corelib/src/libs/SireMol/chirality.cpp +++ b/corelib/src/libs/SireMol/chirality.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMol/chirality.h b/corelib/src/libs/SireMol/chirality.h index c4aa3583c..44011a114 100644 --- a/corelib/src/libs/SireMol/chirality.h +++ b/corelib/src/libs/SireMol/chirality.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMol/geometryperturbation.cpp b/corelib/src/libs/SireMol/geometryperturbation.cpp index 55f2d5cd0..497bda279 100644 --- a/corelib/src/libs/SireMol/geometryperturbation.cpp +++ b/corelib/src/libs/SireMol/geometryperturbation.cpp @@ -377,6 +377,15 @@ QList GeometryPerturbations::perturbations() const return perts; } +/** Append the passed perturbation onto the list */ +void GeometryPerturbations::append(const GeometryPerturbation &perturbation) +{ + if (perturbation.isA()) + perts += perturbation.asA().perts; + else + perts.append(perturbation); +} + /** Return a re-created version of this set of perturbations where all child perturbations are changed to use the passed mapping function */ PerturbationPtr GeometryPerturbations::recreate(const Expression &mapping_function) const diff --git a/corelib/src/libs/SireMol/geometryperturbation.h b/corelib/src/libs/SireMol/geometryperturbation.h index be392b437..6e46f8219 100644 --- a/corelib/src/libs/SireMol/geometryperturbation.h +++ b/corelib/src/libs/SireMol/geometryperturbation.h @@ -168,6 +168,8 @@ namespace SireMol QString toString() const; + void append(const GeometryPerturbation &perturbation); + QList perturbations() const; PerturbationPtr recreate(const SireCAS::Expression &mapping_function) const; diff --git a/corelib/src/libs/SireMol/getrmsd.cpp b/corelib/src/libs/SireMol/getrmsd.cpp index 05bdd1827..62c2122cc 100644 --- a/corelib/src/libs/SireMol/getrmsd.cpp +++ b/corelib/src/libs/SireMol/getrmsd.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMol/getrmsd.h b/corelib/src/libs/SireMol/getrmsd.h index c6a81ca9d..b7afdbce0 100644 --- a/corelib/src/libs/SireMol/getrmsd.h +++ b/corelib/src/libs/SireMol/getrmsd.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMol/hybridization.cpp b/corelib/src/libs/SireMol/hybridization.cpp index eb5ff9955..b3269e1c0 100644 --- a/corelib/src/libs/SireMol/hybridization.cpp +++ b/corelib/src/libs/SireMol/hybridization.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMol/hybridization.h b/corelib/src/libs/SireMol/hybridization.h index d1cc9b093..7799cb725 100644 --- a/corelib/src/libs/SireMol/hybridization.h +++ b/corelib/src/libs/SireMol/hybridization.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMol/iswater.cpp b/corelib/src/libs/SireMol/iswater.cpp index 4d4bbdcc1..5c8829b21 100644 --- a/corelib/src/libs/SireMol/iswater.cpp +++ b/corelib/src/libs/SireMol/iswater.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMol/iswater.h b/corelib/src/libs/SireMol/iswater.h index 029365170..14a1fd3f7 100644 --- a/corelib/src/libs/SireMol/iswater.h +++ b/corelib/src/libs/SireMol/iswater.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMol/moleculedata.cpp b/corelib/src/libs/SireMol/moleculedata.cpp index a006b7f29..3d28a19ff 100644 --- a/corelib/src/libs/SireMol/moleculedata.cpp +++ b/corelib/src/libs/SireMol/moleculedata.cpp @@ -522,6 +522,56 @@ const char *MoleculeData::metadataType(const PropertyName &key, const PropertyNa return props.metadataType(key, metakey); } +/** Add a link from the property 'key' to the property 'linked_property'. + * The linked_property will be returned if there is no property + * called 'key' in this set. + * + * Note that the linked property must already be contained in this set. + */ +void MoleculeData::addLink(const QString &key, const QString &linked_property) +{ + props.addLink(key, linked_property); + + // increment the global version number + vrsn = vrsns->increment(); +} + +/** Remove the link associated with the key 'key' */ +void MoleculeData::removeLink(const QString &key) +{ + if (this->getLinks().contains(key)) + { + props.removeLink(key); + + // increment the global version number + vrsn = vrsns->increment(); + } +} + +/** Remove all property links from this set */ +void MoleculeData::removeAllLinks() +{ + if (this->hasLinks()) + { + props.removeAllLinks(); + + // increment the global version number + vrsn = vrsns->increment(); + } +} + +/** Return whether or not there are any property links */ +bool MoleculeData::hasLinks() const +{ + return props.hasLinks(); +} + +/** Return all of the property links */ +QHash MoleculeData::getLinks() const +{ + return props.getLinks(); +} + /** Return the property at key 'key' \throw SireBase::missing_property diff --git a/corelib/src/libs/SireMol/moleculedata.h b/corelib/src/libs/SireMol/moleculedata.h index e374fdb42..e35e031a0 100644 --- a/corelib/src/libs/SireMol/moleculedata.h +++ b/corelib/src/libs/SireMol/moleculedata.h @@ -195,6 +195,13 @@ namespace SireMol QStringList metadataKeys() const; QStringList metadataKeys(const PropertyName &key) const; + void addLink(const QString &key, const QString &linked_property); + void removeLink(const QString &key); + void removeAllLinks(); + + bool hasLinks() const; + QHash getLinks() const; + const Property &property(const PropertyName &key) const; const Property &property(const PropertyName &key, const Property &default_value) const; @@ -298,7 +305,7 @@ namespace SireMol friend class MolNum; static MolNum createUniqueMolNum(); - class PropVersions + class SIREMOL_EXPORT PropVersions { public: PropVersions() : version(0) diff --git a/corelib/src/libs/SireMol/moleculeview.cpp b/corelib/src/libs/SireMol/moleculeview.cpp index 23dfc5d88..d988b6cc8 100644 --- a/corelib/src/libs/SireMol/moleculeview.cpp +++ b/corelib/src/libs/SireMol/moleculeview.cpp @@ -362,16 +362,19 @@ void MoleculeView::saveFrame(int frame, const SireBase::PropertyMap &map) if (d->hasProperty(traj_prop)) { - traj = d->property(traj_prop.source()).asA(); + traj = d->takeProperty(traj_prop.source()).read().asA(); } if (frame == traj.nFrames()) - this->saveFrame(); + { + traj.appendFrame(this->_toFrame(map)); + } else { traj.setFrame(frame, this->_toFrame(map)); - d->setProperty(traj_prop.source(), traj); } + + d->setProperty(traj_prop.source(), traj); } void MoleculeView::saveFrame(const SireBase::PropertyMap &map) @@ -385,7 +388,7 @@ void MoleculeView::saveFrame(const SireBase::PropertyMap &map) if (d->hasProperty(traj_prop)) { - traj = d->property(traj_prop.source()).asA(); + traj = d->takeProperty(traj_prop.source()).read().asA(); } traj.appendFrame(this->_toFrame(map)); @@ -399,15 +402,11 @@ void MoleculeView::deleteFrame(int frame, const SireBase::PropertyMap &map) if (not(traj_prop.hasSource() and d->hasProperty(traj_prop))) return; - auto traj = d->property(traj_prop.source()).asA(); + auto traj = d->takeProperty(traj_prop.source()).read().asA(); traj.deleteFrame(frame); - if (traj.isEmpty()) - { - d->removeProperty(traj_prop.source()); - } - else + if (not traj.isEmpty()) { d->setProperty(traj_prop.source(), traj); } @@ -2174,6 +2173,61 @@ MolViewPtr MoleculeView::at(const SireID::Index &idx) const return this->operator[](idx); } +/** + * Return whether or not this molecule has any property links + */ +bool MoleculeView::hasLinks() const +{ + return d->hasLinks(); +} + +/** + * Return the property links for this molecule. This returns an + * empty dictionary if there are no links. + */ +QHash MoleculeView::getLinks() const +{ + return d->getLinks(); +} + +/** + * Return whether or not this molecule has a link for the given + * property name + */ +bool MoleculeView::isLink(const PropertyName &key) const +{ + if (key.hasSource()) + { + return this->getLinks().contains(key.source()); + } + else + return false; +} + +/** + * Return the link for the given property name. This will throw + * an exception if there is no link for the given property name. + */ +QString MoleculeView::getLink(const PropertyName &key) const +{ + if (key.hasSource()) + { + const auto links = this->getLinks(); + + auto it = links.constFind(key.source()); + + if (it != links.constEnd()) + return it.value(); + } + + throw SireError::invalid_key(QObject::tr( + "No link found for '%1'") + .arg(key.toString()), + CODELOC); + + return QString(); +} + namespace SireBase { template class SireBase::PropPtr; diff --git a/corelib/src/libs/SireMol/moleculeview.h b/corelib/src/libs/SireMol/moleculeview.h index 8d32e3d9a..f7223a99a 100644 --- a/corelib/src/libs/SireMol/moleculeview.h +++ b/corelib/src/libs/SireMol/moleculeview.h @@ -285,6 +285,12 @@ namespace SireMol void update(const Molecules &molecules); void update(const SelectorMol &molecules); + bool hasLinks() const; + QHash getLinks() const; + + bool isLink(const PropertyName &key) const; + QString getLink(const PropertyName &key) const; + /** Return whether or not this view has the property at key 'key' - note that this returns true only if there is a property, *and* it fits the view (e.g. is an AtomProperty if this diff --git a/corelib/src/libs/SireMol/moleditor.cpp b/corelib/src/libs/SireMol/moleditor.cpp index 7b9b61ff4..dd0df85e3 100644 --- a/corelib/src/libs/SireMol/moleditor.cpp +++ b/corelib/src/libs/SireMol/moleditor.cpp @@ -335,6 +335,43 @@ MolStructureEditor MolEditor::removeAllSegments() const return editor.removeAllSegments(); } +/** Add a link from the property 'key' to the property 'linked_property'. + * The linked_property will be returned if there is no property + * called 'key' in this set. + * + * Note that the linked property must already be contained in this set. + */ +MolEditor &MolEditor::addLink(const QString &key, const QString &linked_property) +{ + d->addLink(key, linked_property); + return *this; +} + +/** Remove the link associated with the key 'key' */ +MolEditor &MolEditor::removeLink(const QString &key) +{ + d->removeLink(key); + return *this; +} + +/** Remove all property links from this set */ +MolEditor &MolEditor::removeAllLinks() +{ + d->removeAllLinks(); + return *this; +} + +/** Update the passed property to have the value 'value'. This does + * an in-place update on the existing property (which must have + * a compatible type). If 'auto-add' is true, then this will add + * the property if it doesn't exist. This returns whether or not + * a property was updated (or added) + */ +bool MolEditor::updateProperty(const QString &key, const Property &value, bool auto_add) +{ + return d->updateProperty(key, value, auto_add); +} + /** Commit these changes and return a copy of the edited molecule */ Molecule MolEditor::commit() const diff --git a/corelib/src/libs/SireMol/moleditor.h b/corelib/src/libs/SireMol/moleditor.h index 61ae18e91..ea148af4a 100644 --- a/corelib/src/libs/SireMol/moleditor.h +++ b/corelib/src/libs/SireMol/moleditor.h @@ -111,6 +111,15 @@ namespace SireMol MolEditor &renumber(const QHash &resnums); MolEditor &renumber(const QHash &atomnums, const QHash &resnums); + bool updateProperty(const QString &key, const Property &value, bool auto_add = true); + + template + bool updatePropertyFrom(const QString &key, const V &value, bool auto_add = true); + + MolEditor &addLink(const QString &key, const QString &linked_property); + MolEditor &removeLink(const QString &key); + MolEditor &removeAllLinks(); + AtomStructureEditor add(const AtomName &atom) const; AtomStructureEditor add(const AtomNum &atom) const; @@ -224,6 +233,24 @@ namespace SireMol operator Molecule() const; }; +#ifndef SIRE_SKIP_INLINE_FUNCTIONS + + /** Update the property at key 'key' (which must be of type T) with + * the value from 'values'. This called T::copyFrom(const V &values) + * on the property. If 'auto_add' is true then this + * default-constructs and adds the property if it doesn't exist. + * + * This returns whether or not a property was updated (or added) + */ + template + SIRE_OUTOFLINE_TEMPLATE bool MolEditor::updatePropertyFrom(const QString &key, + const V &value, bool auto_add) + { + return this->d->updatePropertyFrom(key, value, auto_add); + } + +#endif + } // namespace SireMol Q_DECLARE_METATYPE(SireMol::MolEditor); diff --git a/corelib/src/libs/SireMol/trajectory.cpp b/corelib/src/libs/SireMol/trajectory.cpp index e1b7c6786..dacd07e4c 100644 --- a/corelib/src/libs/SireMol/trajectory.cpp +++ b/corelib/src/libs/SireMol/trajectory.cpp @@ -1036,7 +1036,7 @@ SIREMOL_EXPORT QDataStream &operator>>(QDataStream &ds, Frame &frame) { SharedDataStream sds(ds); - sds >> frame.coords >> frame.vels >> frame.frcs >> frame.spc >> frame.props; + sds >> frame.coords >> frame.vels >> frame.frcs >> frame.spc; double time; sds >> time; @@ -1046,6 +1046,8 @@ SIREMOL_EXPORT QDataStream &operator>>(QDataStream &ds, Frame &frame) frame.num_atoms = std::max(frame.coords.count(), std::max(frame.vels.count(), frame.frcs.count())); + + sds >> frame.props; } else if (v == 1) { diff --git a/corelib/src/libs/SireMol/trajectoryaligner.cpp b/corelib/src/libs/SireMol/trajectoryaligner.cpp index aa374b443..b73af765b 100644 --- a/corelib/src/libs/SireMol/trajectoryaligner.cpp +++ b/corelib/src/libs/SireMol/trajectoryaligner.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMol/trajectoryaligner.h b/corelib/src/libs/SireMol/trajectoryaligner.h index 67497e096..feac79f19 100644 --- a/corelib/src/libs/SireMol/trajectoryaligner.h +++ b/corelib/src/libs/SireMol/trajectoryaligner.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMove/openmmfrenergyst.cpp b/corelib/src/libs/SireMove/openmmfrenergyst.cpp index 436fea5b3..26e07ec75 100644 --- a/corelib/src/libs/SireMove/openmmfrenergyst.cpp +++ b/corelib/src/libs/SireMove/openmmfrenergyst.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the developer's mailing list - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ @@ -1826,7 +1826,7 @@ void OpenMMFrEnergyST::initialise() int anchornum = restrainedAtoms.property(QString("Anchor(%1)").arg(i)).asA().toInt(); int atomnum = - restrainedAtoms.property(QString("Atom(%1)").arg(i)).asA().toInt(); + restrainedAtoms.property(QString("Atom(%1)").arg(i)).asA().toInt(); double k = restrainedAtoms.property(QString("k(%1)").arg(i)).asA().toDouble(); //double xref = restrainedAtoms.property(QString("x(%1)").arg(i)).asA().toDouble(); //double yref = restrainedAtoms.property(QString("y(%1)").arg(i)).asA().toDouble(); @@ -1842,7 +1842,7 @@ void OpenMMFrEnergyST::initialise() //qDebug() << "atomnum " << atomnum << " openmmindex " << openmmindex << " x " << xref << " y " // << yref << " z " << zref << " k " << k << " d " << d; qDebug() << "atomnum " << atomnum << " atopenmmindex " << atopenmmindex << " k " << k; - qDebug() << "anchornum " << anchornum << " anchoropenmmindex " << anchoropenmmindex << " k " << k; + qDebug() << "anchornum " << anchornum << " anchoropenmmindex " << anchoropenmmindex << " k " << k; } @@ -1860,8 +1860,8 @@ void OpenMMFrEnergyST::initialise() //std::vector custom_non_bonded_params{ 0., 0., 0., 0., 1., 1., 0., 0., 0., 1.}; //custom_force_field->addParticle(custom_non_bonded_params); custom_force_field->addExclusion(anchoropenmmindex, atopenmmindex); - positionalRestraints_openmm->addBond(anchoropenmmindex, atopenmmindex, - 0 * OpenMM::NmPerAngstrom, + positionalRestraints_openmm->addBond(anchoropenmmindex, atopenmmindex, + 0 * OpenMM::NmPerAngstrom, k * (OpenMM::KJPerKcal * OpenMM::AngstromsPerNm * OpenMM::AngstromsPerNm)); } } diff --git a/corelib/src/libs/SireMove/openmmfrenergyst.h b/corelib/src/libs/SireMove/openmmfrenergyst.h index 42a0c0765..6f5ea1cdf 100644 --- a/corelib/src/libs/SireMove/openmmfrenergyst.h +++ b/corelib/src/libs/SireMove/openmmfrenergyst.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the developer's mailing list - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMove/openmmmdintegrator.cpp b/corelib/src/libs/SireMove/openmmmdintegrator.cpp index 458c32398..d4f3b80c6 100644 --- a/corelib/src/libs/SireMove/openmmmdintegrator.cpp +++ b/corelib/src/libs/SireMove/openmmmdintegrator.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the developer's mailing list - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMove/openmmmdintegrator.h b/corelib/src/libs/SireMove/openmmmdintegrator.h index ea4ee50b7..0e026ca9f 100644 --- a/corelib/src/libs/SireMove/openmmmdintegrator.h +++ b/corelib/src/libs/SireMove/openmmmdintegrator.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the developer's mailing list - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireMove/openmmpmefep.cpp b/corelib/src/libs/SireMove/openmmpmefep.cpp index b86d05afb..6c2c8096d 100644 --- a/corelib/src/libs/SireMove/openmmpmefep.cpp +++ b/corelib/src/libs/SireMove/openmmpmefep.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the developer's mailing list - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \********************************************/ diff --git a/corelib/src/libs/SireMove/openmmpmefep.h b/corelib/src/libs/SireMove/openmmpmefep.h index c295b5196..a6c57ae3c 100644 --- a/corelib/src/libs/SireMove/openmmpmefep.h +++ b/corelib/src/libs/SireMove/openmmpmefep.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the developer's mailing list - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireSystem/forcefieldinfo.cpp b/corelib/src/libs/SireSystem/forcefieldinfo.cpp index 234ecca48..5f665031b 100644 --- a/corelib/src/libs/SireSystem/forcefieldinfo.cpp +++ b/corelib/src/libs/SireSystem/forcefieldinfo.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireSystem/forcefieldinfo.h b/corelib/src/libs/SireSystem/forcefieldinfo.h index aa36f8805..fa5e1cfa6 100644 --- a/corelib/src/libs/SireSystem/forcefieldinfo.h +++ b/corelib/src/libs/SireSystem/forcefieldinfo.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireUnits/ast.cpp b/corelib/src/libs/SireUnits/ast.cpp index affc913a2..fc98457aa 100644 --- a/corelib/src/libs/SireUnits/ast.cpp +++ b/corelib/src/libs/SireUnits/ast.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireUnits/ast.h b/corelib/src/libs/SireUnits/ast.h index 200bc80ba..fffec8003 100644 --- a/corelib/src/libs/SireUnits/ast.h +++ b/corelib/src/libs/SireUnits/ast.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireUnits/dimensions.h b/corelib/src/libs/SireUnits/dimensions.h index db0f85588..66d0ec08a 100644 --- a/corelib/src/libs/SireUnits/dimensions.h +++ b/corelib/src/libs/SireUnits/dimensions.h @@ -399,6 +399,10 @@ namespace SireUnits typedef PhysUnit<1, 2, -2, 0, 0, -1, 0> MolarEnergy; + typedef PhysUnit<1, 0, -2, 0, 0, -1, 0> HarmonicBondConstant; + + typedef PhysUnit<1, 2, -2, 0, 0, -1, -2> HarmonicAngleConstant; + typedef PhysUnit<1, 2, -3, 0, 0, 0, 0> Power; typedef PhysUnit<1, 2, -3, 0, 0, -1, 0> MolarPower; @@ -449,6 +453,8 @@ namespace SireUnits class Capacitance; class Current; class Potential; + class HarmonicBondConstant; + class HarmonicAngleConstant; class Constant1; class Constant2; @@ -506,6 +512,8 @@ Q_DECLARE_METATYPE(SireUnits::Dimension::Pressure); Q_DECLARE_METATYPE(SireUnits::Dimension::Capacitance); Q_DECLARE_METATYPE(SireUnits::Dimension::Current); Q_DECLARE_METATYPE(SireUnits::Dimension::Potential); +Q_DECLARE_METATYPE(SireUnits::Dimension::HarmonicBondConstant); +Q_DECLARE_METATYPE(SireUnits::Dimension::HarmonicAngleConstant); Q_DECLARE_METATYPE(SireUnits::Dimension::Constant1); Q_DECLARE_METATYPE(SireUnits::Dimension::Constant2); diff --git a/corelib/src/libs/SireUnits/grammar.h b/corelib/src/libs/SireUnits/grammar.h index ef4e980cf..0561a6239 100644 --- a/corelib/src/libs/SireUnits/grammar.h +++ b/corelib/src/libs/SireUnits/grammar.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireUnits/parser.cpp b/corelib/src/libs/SireUnits/parser.cpp index 0a40bd197..91048f190 100644 --- a/corelib/src/libs/SireUnits/parser.cpp +++ b/corelib/src/libs/SireUnits/parser.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireUnits/temperature.cpp b/corelib/src/libs/SireUnits/temperature.cpp index 3f1fea8fd..405e6fb40 100644 --- a/corelib/src/libs/SireUnits/temperature.cpp +++ b/corelib/src/libs/SireUnits/temperature.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireUnits/units.h b/corelib/src/libs/SireUnits/units.h index b05a27a76..1f6e9bf1f 100644 --- a/corelib/src/libs/SireUnits/units.h +++ b/corelib/src/libs/SireUnits/units.h @@ -72,6 +72,8 @@ namespace SireUnits const Dimension::Angle radians(1); const Dimension::Angle radian(1); + const auto radian2 = radian * radian; + const Dimension::Angle degrees = radians * pi / 180.0; const Dimension::Angle degree = degrees; @@ -220,6 +222,10 @@ namespace SireUnits const Dimension::Energy int_kcal(mole *int_kcal_per_mol); const Dimension::Energy int_cal(0.001 * int_kcal); + // Force constants + const Dimension::HarmonicBondConstant kcal_per_mol_per_A2(kcal_per_mol / angstrom2); + const Dimension::HarmonicAngleConstant kcal_per_mol_per_rad2(kcal_per_mol / radian2); + // http://physics.nist.gov/cgi-bin/cuu/Value?hr|search_for=hartree const Dimension::Energy hartree(4.35974394e-18 * joule); diff --git a/corelib/src/libs/SireVol/transformedspace.cpp b/corelib/src/libs/SireVol/transformedspace.cpp index 5c0019397..9e8f420e7 100644 --- a/corelib/src/libs/SireVol/transformedspace.cpp +++ b/corelib/src/libs/SireVol/transformedspace.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/corelib/src/libs/SireVol/transformedspace.h b/corelib/src/libs/SireVol/transformedspace.h index 753079afd..3887283ea 100644 --- a/corelib/src/libs/SireVol/transformedspace.h +++ b/corelib/src/libs/SireVol/transformedspace.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ diff --git a/doc/source/api/index_morph.rst b/doc/source/api/index_morph.rst new file mode 100644 index 000000000..aa64f03ab --- /dev/null +++ b/doc/source/api/index_morph.rst @@ -0,0 +1,8 @@ +========== +sire.morph +========== + +.. toctree:: + :maxdepth: 2 + + morph diff --git a/doc/source/api/index_restraints.rst b/doc/source/api/index_restraints.rst new file mode 100644 index 000000000..895f34ed2 --- /dev/null +++ b/doc/source/api/index_restraints.rst @@ -0,0 +1,8 @@ +=============== +sire.restraints +=============== + +.. toctree:: + :maxdepth: 2 + + restraints diff --git a/doc/source/api/morph.rst b/doc/source/api/morph.rst new file mode 100644 index 000000000..51bc4749e --- /dev/null +++ b/doc/source/api/morph.rst @@ -0,0 +1,8 @@ +========== +Public API +========== + +.. automodule:: sire.morph + :members: + + :doc:`View Module Index ` diff --git a/doc/source/api/restraints.rst b/doc/source/api/restraints.rst new file mode 100644 index 000000000..def7fdf42 --- /dev/null +++ b/doc/source/api/restraints.rst @@ -0,0 +1,8 @@ +========== +Public API +========== + +.. automodule:: sire.restraints + :members: + + :doc:`View Module Index ` diff --git a/doc/source/api/sire_modules.rst b/doc/source/api/sire_modules.rst index cb1d79859..f63c35775 100644 --- a/doc/source/api/sire_modules.rst +++ b/doc/source/api/sire_modules.rst @@ -12,6 +12,8 @@ Sub-modules index_maths index_mm index_mol + index_morph + index_restraints index_search index_stream index_system diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index aca95d01f..051ff054f 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -25,12 +25,23 @@ organisation on `GitHub `__. distance between atoms of two views. * Added support for perturbable molecules to the OpenMM converter. Have addded - `LambdaLever` and `LambdaSchedule` classes that can be used to control + ``LambdaLever`` and ``LambdaSchedule`` classes that can be used to control how forcefield parameters are changed with lambda. These levers change the parameters in the OpenMM context, enabling simulations at different values of lambda to be performed. This is initial functionality which will be documented and expanded by subsequent PRs. +* Added support for softening potentials used to smooth creation and + deletion of ghost atoms during alchemical free energy simulations. + Added a new ``sire.morph`` module that includes functions that should + make it easier to set up, view and control morphs (perturbations). + +* Added an ``EnergyTrajectory`` class that lets us record the energy + trajectory along a dynamics simulation. This includes recording energies + at different λ-windows to that being simulated, thereby providing + the raw data for free energy calculations. By default the + ``EnergyTrajectory`` is returned to the user as a pandas DataFrame. + * Forced all new-style modules to import when `sr.use_new_api()` is called. This will make it easier to use sire with multiprocessing. @@ -66,14 +77,31 @@ organisation on `GitHub `__. * Updated ``FreeEnergyAnalysis.py`` to be compatible with both the new pymbar 4 API and the old pymbar 3 API. -* Made sure that a title is written to an AmberRst file, even if the system - has no name (issue #99). +* Added support for restraints to the OpenMM dynamics layer. Initial tested + support for positional and distance/bond restraints is included, as well + as experimental support for Boresch restraints. The restraint are documented + in the :doc:`tutorial `. This also documents + new code to let you specify atoms that should be fixed in space. + +* Added support for alchemical restraints to the OpenMM dynamics layer. + This lets you scale restraints as part of a λ-coordinate. This is + documented in the :doc:`tutorial `__ - September 2023 +---------------------------------------------------------------------------------------------- + +* Made sure that a title is written to an AmberRst file, even if the system + has no name (issue #99). + * Modularise the :class:`~sire.vol.TriclinicBox` lattice rotation and reduction functionality - and make both optional. + and make both optional. (PR #102). + +* Updated default units so that units of pressure default to printing out in units of atmospheres `2023.3.1 `__ - July 2023 ----------------------------------------------------------------------------------------- diff --git a/doc/source/cheatsheet/openmm.rst b/doc/source/cheatsheet/openmm.rst index 5c59702cc..95bed8f23 100644 --- a/doc/source/cheatsheet/openmm.rst +++ b/doc/source/cheatsheet/openmm.rst @@ -2,7 +2,7 @@ OpenMM Integration ================== -The :doc:`dynamics functionality in sire <../tutorial/part05/04_dynamics>` +The :doc:`dynamics functionality in sire <../tutorial/part05/05_dynamics>` is based on tight integration with `OpenMM `__. This is achieved via code in :mod:`sire.convert` that can convert @@ -73,54 +73,86 @@ by setting values for the ``temperature`` and ``pressure`` keys. Available keys and allowable values are listed below. -+-----------------+----------------------------------------------------------+ -| Key | Valid values | -+=================+==========================================================+ -| constraint | Type of constraint to use for bonds and/or angles. | -| | Valid strings are ``none``, ``h-bonds``, ``bonds``, | -| | ``h-bonds-h-angles`` and ``bonds-h-angles``. | -+-----------------+----------------------------------------------------------+ -| cutoff | Size of the non-bonded cutoff, e.g. | -| | ``7.5*sr.units.angstrom`` | -+-----------------+----------------------------------------------------------+ -| cutoff_type | Type of cutoff, e.g. ``PARTICLE_MESH_EWALD``, ``PME``, | -| | ``NO_CUTOFF``, ``REACTION_FIELD``, ``RF``, ``EWALD`` | -+-----------------+----------------------------------------------------------+ -| cpu_pme | Boolean value, e.g. ``True`` or ``False`` as to whether | -| | or not PME should be evaluated on the CPU rather than | -| | on the GPU. | -+-----------------+----------------------------------------------------------+ -| device | Any valid OpenMM device number or device string, e.g. | -| | ``0`` would select device 0 | -+-----------------+----------------------------------------------------------+ -| dielectric | Dielectric value if a reaction field cutoff is used, | -| | e.g. ``78.0`` | -+-----------------+----------------------------------------------------------+ -| integrator | ``verlet``, ``leapfrog``, ``langevin``, | -| | ``langevin_middle``, ``nose_hoover``, | -| | ``brownian`` | -+-----------------+----------------------------------------------------------+ -| friction | Friction value for the integrator, in inverse time, e.g. | -| | ``5.0 / sr.units.picosecond`` | -+-----------------+----------------------------------------------------------+ -| platform | Any valid OpenMM platform string, e.g. ``CUDA``, | -| | ``OpenCL``, ``CPU``, ``Reference`` | -+-----------------+----------------------------------------------------------+ -| precision | Any valid OpenMM platform precision value, e.g. | -| | ``single``, ``mixed`` or ``double``. | -+-----------------+----------------------------------------------------------+ -| pressure | Any pressure value, e.g. ``1*sr.units.atm`` | -+-----------------+----------------------------------------------------------+ -| space | Space in which the simulation should be conducted, e.g. | -| | `sr.vol.Cartesian` | -+-----------------+----------------------------------------------------------+ -| temperature | Any temperature value, e.g. ``25*sr.units.celsius`` | -+-----------------+----------------------------------------------------------+ -| threads | The number of threads to use in the CPU platform | -+-----------------+----------------------------------------------------------+ -| timestep | Time between integration steps, e.g. | -| | ``2 * sr.units.femtosecond`` | -+-----------------+----------------------------------------------------------+ ++---------------------------+----------------------------------------------------------+ +| Key | Valid values | ++===========================+==========================================================+ +| constraint | Type of constraint to use for bonds and/or angles. | +| | Valid strings are ``none``, ``h-bonds``, ``bonds``, | +| | ``h-bonds-h-angles`` and ``bonds-h-angles``. | ++---------------------------+----------------------------------------------------------+ +| coulomb_power | The coulomb power parameter used by the softening | +| | potential used to soften interactions involving | +| | ghost atoms. | ++---------------------------+----------------------------------------------------------+ +| cutoff | Size of the non-bonded cutoff, e.g. | +| | ``7.5*sr.units.angstrom`` | ++---------------------------+----------------------------------------------------------+ +| cutoff_type | Type of cutoff, e.g. ``PARTICLE_MESH_EWALD``, ``PME``, | +| | ``NO_CUTOFF``, ``REACTION_FIELD``, ``RF``, ``EWALD`` | ++---------------------------+----------------------------------------------------------+ +| cpu_pme | Boolean value, e.g. ``True`` or ``False`` as to whether | +| | or not PME should be evaluated on the CPU rather than | +| | on the GPU. | ++---------------------------+----------------------------------------------------------+ +| device | Any valid OpenMM device number or device string, e.g. | +| | ``0`` would select device 0 | ++---------------------------+----------------------------------------------------------+ +| dielectric | Dielectric value if a reaction field cutoff is used, | +| | e.g. ``78.0`` | ++---------------------------+----------------------------------------------------------+ +| fixed | The atoms in the system that should be fixed (not moved) | ++---------------------------+----------------------------------------------------------+ +| ignore_perturbations | Whether or not to ignore any perturbations and only set | +| | up a perturbable molecule as a non-perurbable molecule | +| | from only the reference state. | ++---------------------------+----------------------------------------------------------+ +| integrator | The MD integrator to use, e.g. | +| | ``verlet``, ``leapfrog``, ``langevin``, | +| | ``langevin_middle``, ``nose_hoover``, | +| | ``brownian`` | ++---------------------------+----------------------------------------------------------+ +| friction | Friction value for the integrator, in inverse time, e.g. | +| | ``5.0 / sr.units.picosecond`` | ++---------------------------+----------------------------------------------------------+ +| lambda | The λ-value at which to set up the system (assuming this | +| | contains any perturbable molecules or restraints) | ++---------------------------+----------------------------------------------------------+ +| platform | Any valid OpenMM platform string, e.g. ``CUDA``, | +| | ``OpenCL``, ``Metal``, ```CPU``, ``Reference`` | ++---------------------------+----------------------------------------------------------+ +| precision | Any valid OpenMM platform precision value, e.g. | +| | ``single``, ``mixed`` or ``double``. | ++---------------------------+----------------------------------------------------------+ +| pressure | Any pressure value, e.g. ``1*sr.units.atm`` | ++---------------------------+----------------------------------------------------------+ +| restraints | The :class:`~sire.mm.Restraints` object (or list of | +| | objects) that describe the restraints that should be | +| | added to the system. | ++---------------------------+----------------------------------------------------------+ +| schedule | The :class:`~sire.cas.LambdaSchedule` to use that | +| | controls how parameters are modified with λ | ++---------------------------+----------------------------------------------------------+ +| shift_delta | The shift_delta parameter to use for the softening | +| | potential used to soften interactions involving | +| | ghost atoms. | ++---------------------------+----------------------------------------------------------+ +| space | Space in which the simulation should be conducted, e.g. | +| | `sr.vol.Cartesian` | ++---------------------------+----------------------------------------------------------+ +| swap_end_states | Whether to swap the end states of a perturbable molecule | +| | (i.e. treat the perturbed state as the reference state | +| | and vice versa). | ++---------------------------+----------------------------------------------------------+ +| temperature | Any temperature value, e.g. ``25*sr.units.celsius`` | ++---------------------------+----------------------------------------------------------+ +| threads | The number of threads to use in the CPU platform | ++---------------------------+----------------------------------------------------------+ +| timestep | Time between integration steps, e.g. | +| | ``2 * sr.units.femtosecond`` | ++---------------------------+----------------------------------------------------------+ +| use_dispersion_correction | Whether or not to use the dispersion correction to | +| | deal with cutoff issues. This is very expensive. | ++---------------------------+----------------------------------------------------------+ Higher level API ---------------- @@ -142,10 +174,26 @@ You can use this object to query the options that were passed into OpenMM. >>> print(d.ensemble()) microcanonical (NVE) ensemble -You can set OpenMM options by passing the dictionary of key-value pairs +You can set most of the OpenMM options via arguments to the :func:`~sire.mol.SelectorMol.dynamics` +function, e.g. + +>>> d = mols.dynamics(temperature="25oC") +>>> print(d.ensemble()) +canonical (NVT) ensemble { temperature = 298.15 C } + +... note:: + + The function will automatically convert strings to units if these are + needed, e.g. ``25oC`` will automatically be converted to 25 Celsius. + +or + +>>> d = mols.dynamics(timestep="4fs", lambda_value=0.5) + +You can also set OpenMM options by passing the dictionary of key-value pairs as the ``map`` option. ->>> d = mols.dynamics(map={"temperature": 25*sr.units.celsius}) +>>> d = mols.dynamics(map={"temperature": "25oC"}) >>> print(d.ensemble()) canonical (NVT) ensemble { temperature = 298.15 C } @@ -157,16 +205,15 @@ canonical (NVT) ensemble { temperature = 298.15 C } It is a mistake to use an OpenMM Integrator that is not suited for the chosen ensemble. ->>> d = mols.dynamics(map={"temperature": 25*sr.units.celsius, -... "integrator": "verlet"}) +>>> d = mols.dynamics(temperature="25oC", integrator="verlet") ValueError: You cannot use a verlet integrator with the ensemble canonical (NVT) ensemble { temperature = 298.15 C } You can also query other parameters. ->>> d = mols.dynamics(map={"timestep": 1*sr.units.femtosecond}) +>>> d = mols.dynamics(timestep="1fs") >>> print(d.constraint()) none ->>> d = mols.dynamics(map={"timestep": 5*sr.units.femtosecond}) +>>> d = mols.dynamics(timestep="5fs") >>> print(d.constraint()) bonds-h-angles >>> print(d.timestep()) @@ -234,3 +281,54 @@ ForceFieldInfo( bond = harmonic, angle = harmonic, dihedral = cosine } ) + +Running dynamics and saving frames and energies +----------------------------------------------- + +You can run dynamics via the :func:`~sire.mol.Dynamics.run` function, e.g. + +>>> d = mols.dynamics(timestep="4fs", temperature="25oC") +>>> d.run("100ps") + +would run 100 picoseconds of dynamics. + +At the end, you can extract the final system using the +:func:`~sire.mol.Dynamics.commit` function, e.g. + +>>> mols = d.commit() + +You can set the frequency at which trajectory frames and energies are saved +via the ``save_frequency`` argument, e.g. + +>>> d.run("100ps", save_frequency="10ps") + +would save energies and trajectory frames every 10 picoseconds. You can +specifiy different frequencies for these via the +``energy_frequency`` and/or ``frame_frequency`` arguments, e.g. + +>>> d.run("1ns", energy_frequency="1ps", frame_frequency="100ps") + +would save energies every picosecond and frames every 100 picoseconds. + +By default, only coordinates are saved. You can choose to save velocities +as well by setting ``save_velocities=True``, e.g. + +>>> d.run("10ps", save_frequency="1ps", save_velocities=True) + +By default, energies are saved only for the simulated λ-value of the +system. You can request energies to be saved for other λ-values using +the ``lambda_windows`` argument, e.g. + +>>> d.run("100ps", energy_frequency="1ps", lambda_windows=[0.0, 0.5, 1.0]) + +would save the energies at λ-values 0.0, 0.5 and 1.0 for every picosecond +of the trajectory. You can pass in as many or few λ-windows as you wish. + +The coordinate/velocity frames are saved to the ``trajectory`` property of +the molecules, and are accessible identically to trajectories loaded +from files (e.g. via that property of the ``.trajectory()`` function). + +The energies are saved to the ``energy_trajectory`` property of the +returned molecules, and accessible via that property or the +:func:`~sire.system.System.energy_trajectory` function. + diff --git a/doc/source/tutorial/index.rst b/doc/source/tutorial/index.rst index 82f18e32c..232078d85 100644 --- a/doc/source/tutorial/index.rst +++ b/doc/source/tutorial/index.rst @@ -26,3 +26,4 @@ please :doc:`ask for support. <../support>` index_part03 index_part04 index_part05 + index_part06 diff --git a/doc/source/tutorial/index_part06.rst b/doc/source/tutorial/index_part06.rst new file mode 100644 index 000000000..6dc6e0dc1 --- /dev/null +++ b/doc/source/tutorial/index_part06.rst @@ -0,0 +1,28 @@ +============================================ +Part 6 - Morphs and Alchemical Free Energies +============================================ + +One of the original design goals of :mod:`sire` was to support the +prototyping, setup and running of alchemical free energy simulations. + +:mod:`sire` has a lot of in-built support for perturbing (morphing) +between different molecules, and to simulate and then calculate +alchemical free energy differences along those morphs. + +This is supported by integration with `OpenMM `__, +thereby letting you set up and run molecular dynamics simulations that +morph one molecular system into another. + +This chapter will teach you how to set up a morph, how to run +alchemical molecular dynamics simulations, and how to calculate +alchemical free energies along morph coordinates. This includes how +to add restraints to atoms, fix atoms in space, and change these +restraints as you perturb between different molecules. + +.. toctree:: + :maxdepth: 1 + + part06/01_merge + part06/02_alchemical_dynamics + part06/03_restraints + part06/04_alchemical_restraints diff --git a/doc/source/tutorial/part05/05_dynamics.rst b/doc/source/tutorial/part05/05_dynamics.rst index 6d2be972b..02600b635 100644 --- a/doc/source/tutorial/part05/05_dynamics.rst +++ b/doc/source/tutorial/part05/05_dynamics.rst @@ -29,8 +29,6 @@ You perform minimisation itself by calling the :func:`~sire.mol.Minimisation.run function. >>> m.run() -minimisation ✔ -Minimisation() You can extract the results of minimisation, converted back into the original view object by calling :func:`~sire.mol.Minimisation.commit` @@ -43,7 +41,6 @@ You could run all of these steps on a single line, e.g. >>> mol = mols[0].minimisation().run().commit() >>> print(mol.energy()) -minimisation ✔ 17.7799 kcal mol-1 In the above case we minimised just the first molecule that was loaded. @@ -54,7 +51,6 @@ collection. -5855.24 kcal mol-1 >>> mols = mols.minimisation().run().commit() >>> print(mols.energy()) -minimisation ✔ -7971.79 kcal mol-1 Molecular Dynamics diff --git a/doc/source/tutorial/part06/01_merge.rst b/doc/source/tutorial/part06/01_merge.rst new file mode 100644 index 000000000..21fcecb3d --- /dev/null +++ b/doc/source/tutorial/part06/01_merge.rst @@ -0,0 +1,45 @@ +================= +Merging Molecules +================= + +This page is a WORK IN PROGRESS. + +This page will talk about how we can create merge molecules. + +This uses BioSimSpace, so examples will start with + +>>> import BioSimSpace as BSS +>>> import sire as sr + +This page will then detail additional functionality that can be used +to control or edit the merge molecule. + +Examples include shrinking ghost atoms. + +>>> sr.morph.shrink_ghost_atoms(mols, length="0.6 A") + +Or doing extra work like adding restraints, or adding extra controls +or forcefield parameters that represent best practice for complex +morphs. + +Also show how to visualise the morph, e.g. + +>>> merged_mol.perturbation().view() + +Also show the interface for checking if a molecule is perturbable + +>>> merged_mol.is_perturbable() + +And to search for perturbable molecules in a collection. + +>>> perturbable_mols = mols.molecules("property is_perturbable") + +or + +>>> mol = mols["molecule property is_perturbable"] + +Also show how to use links to link to the 0 or 1 properties + +>>> mol = mol.edit().add_link("coordinates0", "coordinates").commit() + +(would be good to add this to the Cursor API) diff --git a/doc/source/tutorial/part06/02_alchemical_dynamics.rst b/doc/source/tutorial/part06/02_alchemical_dynamics.rst new file mode 100644 index 000000000..b03c63b8d --- /dev/null +++ b/doc/source/tutorial/part06/02_alchemical_dynamics.rst @@ -0,0 +1,176 @@ +=================== +Alchemical Dynamics +=================== + +You can create an alchemical molecular dynamics simulation in exactly the +same way as you would a normal molecular dynamics simulation. There +are two options: + +* Use the high-level interface based on the +:func:`~sire.system.System.minimisation` and +:func:`~sire.system.System.dynamics` functions. +* Use the low-level interface that works directly with native OpenMM objects. + +High level interface +-------------------- + +The simplest route is to use the high-level interface. Calling +:func:`~sire.system.System.minimisation` or +:func:`~sire.system.System.dynamics` on any collection of molecules (or +view) that contains any perturbable molecule will automatically create +an alchemical simulation. There are extra options that you can pass to +the simulation that will control how it is run: + +* ``lambda_value`` - this sets the global λ-value for the simulation. + λ is a parameter that controls the morph from the reference molecule + (at λ=0) to the perturbed molecule (at λ=1). + +* ``swap_end_states`` - if set to ``True``, this will swap the end states + of the perturbation. The morph will run from the perturbed molecule + (at λ=0) to the reference molecule (at λ=1). Note that the coordinates + of the perturbed molecule will be used in this case to start the + simulation. + +* ``schedule`` - set the λ-schedule which specifies how λ morphs between + the reference and perturbed molecules. + +* ``shift_delta`` - set the ``shift_delta`` parameter which is used to + control the electrostatic and van der Waals softening potential that + smooths the creation or deletion of ghost atoms. This is a floating + point number that defaults to ``1.0``, which should be good for + most perturbations. + +* ``coulomb_power`` - set the ``coulomb_power`` parameter which is used + to control the electrostatic softening potential that smooths the + creation and deletion of ghost atoms. This is an integer that defaults + to ``0``, which should be good for most perturbations. + +For example, we could minimise our alchemical system at λ=0.5 using + +>>> mols = mols.minimisation(lambda_value=0.5).run().commit() + +We can then run some dynamics at this λ-value using + +>>> d = mols.dynamics(lambda_value=0.5, timestep="4fs", temperature="25oC") +>>> d.run("10ps", save_frequency="1ps") +>>> mols = d.commit() + +The result of dynamics is a trajectory run at this λ-value. You can view the +trajectory as you would any other, e.g. + +>>> mols.view() + +You could view specifically the perturbable molecules using + +>>> mols["molecule property is_perturbable"].view() + +Energy Trajectories +------------------- + +In addition to a coordinates trajectory, dynamics also produces an +energy trajectory. This is the history of kinetic and potential energies +sampled by the molecules during the trajectory. You can access this +energy trajectory via the :func:`~sire.system.System.energy_trajectory` +function, e.g. + +>>> df = mols.energy_trajectory() +>>> print(df) +PANDAS DATAFRAME + +By default, this trajectory is returned as a +`pandas DataFrame `__. +You can get the underlying :class:`sire.maths.EnergyTrajectory` function +by passing ``to_pandas=False``, e.g. + +>>> t = mols.energy_trajectory(to_pandas=False) +>>> print(t) +EnergyTrajectory() + +You calculate free energies by evaluating the potential energy for different +values of λ during dynamics. You can control which values of λ are used +(the so-called "λ-windows") by setting the ``lambda_windows`` argument, e.g. + +>>> d = mols.dynamics(lambda_value=0.5, timestep="4fs", temperature="25oC") +>>> d.run("10ps", save_frequency="1ps", lambda_windows=[0.4, 0.6]) +>>> mols = d.commit() +>>> print(mols.get_energy_trajectory()) + +This has run a new trajectory, evaluating the potential energy at the +simulation λ-value (0.5) as well as at λ-windows 0.4 and 0.6. You can pass in +as many or as few λ-windows as you want. + +Controlling the trajectory frequency +------------------------------------ + +The ``save_frequency`` parameter controls the frequency at which both +coordinate frames and potential energies are saved to the trajectory. + +Typically you want to evaluate the energies at a much higher frequency than +you want to save frames to the coordinate trajectory. You can choose +a different frequency by either using the ``frame_frequency`` option to +choose a different coordinate frame frequency, and/or using the +``energy_frequency`` option to choose a different energy frequency. + +For example, here we will run dynamics saving coordinates every picosecond, +but saving energies every 20 femtoseconds. + +>>> d = mols.dynamics(lambda_value=0.5, timestep="4fs", temperature="25oC") +>>> d.run("10ps", frame_frequency="1ps", energy_frequency="20fs", +... lambda_windows=[0.4, 0.6], save_velocities=False) +>>> mols = d.commit() +>>> print(mols.get_energy_trajectory()) + +.. note:: + + The ``save_velocities`` option turns on or off the saving of atomic + velocities to the frame trajectory. Typically you don't need to + record velocities, so it is safe to switch them off. This can + reduce memory consumption and also slightly speed up your simulation. + +Setting up a λ-schedule +----------------------- + +A λ-schedule (represented using the :class:`sire.cas.LambdaSchedule` class) +specifies how the λ-parameter morphs from the reference to the perturbed +molecules. The λ-schedule achieves this... + +WRITE MORE ABOUT THE λ-schedule + +Low level interface +------------------- + +The high-level interface is just a set of convienient wrappers around the +OpenMM objects which are used to run the simulation. If you convert +any set of views (or view) that contains perturbable molecules, then an +alchemical OpenMM context will be returned. + +>>> context = sr.convert.to(mols, "openmm") +>>> print(context) +OUTPUT + +The context is held in a low-level class, +:class:`~sire.Convert.SireOpenMM.SOMMContext`, inherits from the +standard `OpenMM Context `__ +class. + +The class adds some additional metadata and control functions that are needed +to update the atomic parameters in the OpenMM Context to represent the +molecular system at different values of λ. + +The key additional functions provided by :class:`~sire.Convert.SireOpenMM.SOMMContext` +are; + +* :func:`~sire.Convert.SireOpenMM.SOMMContext.get_lambda` - return the + current value of λ for the context. +* :func:`~sire.Convert.SireOpenMM.SOMMContext.set_lambda` - set the + new value of λ for the context. Note that this should only really + be used to change λ to evaluate energies at different λ-windows. + It is better to re-create the context if you want to simulate + at a different λ-value. +* :func:`~sire.Convert.SireOpenMM.SOMMContext.get_lambda_schedule` - return the + λ-schedule used to control the morph. +* :func:`~sire.Convert.SireOpenMM.SOMMContext.set_lambda_schedule` - set the + λ-schedule used to control the morph. +* :func:`~sire.Convert.SireOpenMM.SOMMContext.get_energy` - return the + current potential energy of the context. This will be in :mod:`sire` + units if ``to_sire_units`` is ``True`` (the default). diff --git a/doc/source/tutorial/part06/03_restraints.rst b/doc/source/tutorial/part06/03_restraints.rst new file mode 100644 index 000000000..084e83401 --- /dev/null +++ b/doc/source/tutorial/part06/03_restraints.rst @@ -0,0 +1,347 @@ +========== +Restraints +========== + +It can be useful to add restraints to the system to hold things in place. For +example, you might want to hold the protein fixed while allowing the ligand to +move around. Or you may want to hold a ligand in place while it is being +decoupled from the simulation. Or you may want to use restraints to control +the distances between atoms or pull a molecule into a particular conformation. + +You can specify the restraints you want to add to a system via the functions +in the :mod:`sire.restraints` module. These functions return +:class:`~sire.mm.Restraints` objects that contain all of the information that +needs to be passed to OpenMM to add the restraints to the system. + +Positional Restraints +--------------------- + +The simplest type of restraint is a positional restraint, which holds a set of +atoms at a fixed position. You can create a positional restraint using the +:func:`~sire.restraints.positional` function. This function takes the +set of molecules or system you want to simulate, together with a search +string, list of atom indexes, or molecule views holding the atoms that +you want to restrain. For example, + +>>> import sire as sr +>>> mols = sr.load(sr.expand(sr.tutorial_url, "ala.top", "ala.crd")) +>>> restraints = sr.restraints.positional(mols, atoms="resname ALA and element C") +>>> print(restraints) +PositionalRestraints( size=3 +0: PositionalRestraint( 8 => ( 16.5371, 5.02707, 15.812 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +1: PositionalRestraint( 10 => ( 16.0464, 6.38937, 15.2588 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +2: PositionalRestraint( 14 => ( 15.3698, 4.19397, 16.434 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +) + +will create positional restraints for the three carbon atoms in the alanine +residue. These carbon atoms are at indexes 8, 10 and 14 in ``mols.atoms()``, +e.g. + +>>> print(mols.atoms([8, 10, 14])) +SireMol::SelectorM( size=3 +0: MolNum(6) Atom( CA:9 [ 16.54, 5.03, 15.81] ) +1: MolNum(6) Atom( CB:11 [ 16.05, 6.39, 15.26] ) +2: MolNum(6) Atom( C:15 [ 15.37, 4.19, 16.43] ) +) + +You could have specified these indicies directly, e.g. + +>>> restraints = sr.restraints.positional(mols, atoms=[8, 10, 14]) +>>> print(restraints) +PositionalRestraints( size=3 +0: PositionalRestraint( 8 => ( 16.5371, 5.02707, 15.812 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +1: PositionalRestraint( 10 => ( 16.0464, 6.38937, 15.2588 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +2: PositionalRestraint( 14 => ( 15.3698, 4.19397, 16.434 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +) + +or just passed in the atoms directly, e.g. + +>>> atoms = mols.atoms([8, 10, 14]) +>>> restraints = sr.restraints.positional(mols, atoms=atoms) +>>> print(restraints) +PositionalRestraints( size=3 +0: PositionalRestraint( 8 => ( 16.5371, 5.02707, 15.812 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +1: PositionalRestraint( 10 => ( 16.0464, 6.38937, 15.2588 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +2: PositionalRestraint( 14 => ( 15.3698, 4.19397, 16.434 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +) + +The default force constant is 150 kcal mol-1 Å-2. By default, the +atoms are restrained to their current positions, and are held exactly in +those positions (hence why the ``r0=0 Å`` in the output above). + +You can set the force constant and width of the half-harmonic restraint +used to hold the atoms in position using the ``k`` and ``r0`` arguments, e.g. + +>>> restraints = sr.restraints.positional(mols, atoms="resname ALA and element C", +... k="100 kcal mol-1 A-2", r0="1.5 A") +>>> print(restraints) +PositionalRestraints( size=3 +0: PositionalRestraint( 8 => ( 16.5371, 5.02707, 15.812 ), k=100 kcal mol-1 Å-2 : r0=1.5 Å ) +1: PositionalRestraint( 10 => ( 16.0464, 6.38937, 15.2588 ), k=100 kcal mol-1 Å-2 : r0=1.5 Å ) +2: PositionalRestraint( 14 => ( 15.3698, 4.19397, 16.434 ), k=100 kcal mol-1 Å-2 : r0=1.5 Å ) +) + +By default, the atoms are restrained to their current positions. You can +specify a different position using the ``position`` argument, e.g. + +>>> restraints = sr.restraints.positional(mols, atoms="resname ALA and element C", +... position=[(0,0,0), (1,1,1), (2,2,2)]) +>>> print(restraints) +PositionalRestraints( size=3 +0: PositionalRestraint( 8 => ( 0, 0, 0 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +1: PositionalRestraint( 10 => ( 1, 1, 1 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +2: PositionalRestraint( 14 => ( 2, 2, 2 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +) + +.. note:: + + The number of positions must equal the number of atoms to be restrained, + or equal to 1. If you only pass in a single position then this will be used + for all of the restraints. + +Sometimes it can be useful to use the same position for all of the restraints, +e.g. in the case of a spherical half-harmonic restraint that holds molecules +within a spherical bubble, e.g. + +>>> center = mols[0].coordinates() +>>> restraints = sr.restraints.positional(mols, +... atoms=mols[f"atoms within 10 of {center}"], +... position=center, +... r0="10 A") +>>> print(restraints) +PositionalRestraints( size=358 +0: PositionalRestraint( 0 => ( 16.5471, 4.50102, 15.6589 ), k=150 kcal mol-1 Å-2 : r0=10 Å ) +1: PositionalRestraint( 1 => ( 16.5471, 4.50102, 15.6589 ), k=150 kcal mol-1 Å-2 : r0=10 Å ) +2: PositionalRestraint( 2 => ( 16.5471, 4.50102, 15.6589 ), k=150 kcal mol-1 Å-2 : r0=10 Å ) +3: PositionalRestraint( 3 => ( 16.5471, 4.50102, 15.6589 ), k=150 kcal mol-1 Å-2 : r0=10 Å ) +4: PositionalRestraint( 4 => ( 16.5471, 4.50102, 15.6589 ), k=150 kcal mol-1 Å-2 : r0=10 Å ) +... +353: PositionalRestraint( 1892 => ( 16.5471, 4.50102, 15.6589 ), k=150 kcal mol-1 Å-2 : r0=10 Å ) +354: PositionalRestraint( 1893 => ( 16.5471, 4.50102, 15.6589 ), k=150 kcal mol-1 Å-2 : r0=10 Å ) +355: PositionalRestraint( 1906 => ( 16.5471, 4.50102, 15.6589 ), k=150 kcal mol-1 Å-2 : r0=10 Å ) +356: PositionalRestraint( 1907 => ( 16.5471, 4.50102, 15.6589 ), k=150 kcal mol-1 Å-2 : r0=10 Å ) +357: PositionalRestraint( 1908 => ( 16.5471, 4.50102, 15.6589 ), k=150 kcal mol-1 Å-2 : r0=10 Å ) +) + +has created half-harmonic restraints that affect all atoms within 10 Å of the +center of the first molecule, and that restrain those atoms to be within +a sphere of radius 10 Å centered on that molecule. + +Note that the restraints only contain the indicies of the atoms that are +restrained, not the atoms themselves. You can get the atoms by looking up +those indicies from the ``mols`` molecular container from which the +atoms were selected. For example, here we get all of the atoms that are +subject to that spherical half-harmonic restraint; + +>>> print(mols.atoms()[[restraint.atom() for restraint in restraints]]) +SireMol::SelectorM( size=358 +0: MolNum(6) Atom( HH31:1 [ 18.45, 3.49, 12.44] ) +1: MolNum(6) Atom( CH3:2 [ 18.98, 3.45, 13.39] ) +2: MolNum(6) Atom( HH32:3 [ 20.05, 3.63, 13.29] ) +3: MolNum(6) Atom( HH33:4 [ 18.80, 2.43, 13.73] ) +4: MolNum(6) Atom( C:5 [ 18.48, 4.55, 14.35] ) +... +353: MolNum(630) Atom( H1:1893 [ 14.92, 1.28, 17.05] ) +354: MolNum(630) Atom( H2:1894 [ 15.19, -0.21, 17.07] ) +355: MolNum(623) Atom( O:1907 [ 21.65, 7.88, 9.79] ) +356: MolNum(623) Atom( H1:1908 [ 22.33, 8.56, 9.83] ) +357: MolNum(623) Atom( H2:1909 [ 21.07, 8.08, 10.53] ) +) + +Distance or Bond Restraints +--------------------------- + +The :func:`sire.restraints.distance` or :func:`sire.restraints.bond` functions +are used to create bond or distance restraints. These are identical restraints, +so the functions are just synonyms of each other (they are the same function +with different names). + +These functions take the set of molecules or system you want to simulate, +plus two search strings, lists of atom indexes, or molecule views holding +the pairs of atoms that you want to restrain, e.g. + +>>> restraints = sr.restraints.distance(mols, atoms0=0, atoms1=1) +>>> print(restraints) +BondRestraints( size=1 +0: BondRestraint( 0 <=> 1, k=150 kcal mol-1 Å-2 : r0=1.09 Å ) +) + +or + +>>> restraints = sr.restraints.bond(mols, atoms0=0, atoms1=1) +>>> print(restraints) +BondRestraints( size=1 +0: BondRestraint( 0 <=> 1, k=150 kcal mol-1 Å-2 : r0=1.09 Å ) +) + + +creates a single harmonic distance (or bond) restraint that acts between +atoms 0 and 1. By default, the equilibrium distance (r0) is the current +distance between the atoms (1.09 Å), and the force constant (k) is +150 kcal mol-1 Å-2. + +You can set these via the ``k`` and ``r0`` arguments, e.g. + +>>> restraints = sr.restraints.bond(mols, atoms0=0, atoms1=1, +... k="100 kcal mol-1 A-2", r0="1.5 A") +>>> print(restraints) +BondRestraints( size=1 +0: BondRestraint( 0 <=> 1, k=100 kcal mol-1 Å-2 : r0=1.5 Å ) +) + +You can specify as many atoms pairs as you like, e.g. + +>>> restraints = sr.restraints.bond(mols, atoms0=[0, 1, 2], atoms1=[3, 4, 5]) +>>> print(restraints) +BondRestraints( size=3 +0: BondRestraint( 0 <=> 3, k=150 kcal mol-1 Å-2 : r0=1.71245 Å ) +1: BondRestraint( 1 <=> 4, k=150 kcal mol-1 Å-2 : r0=1.54643 Å ) +2: BondRestraint( 2 <=> 5, k=150 kcal mol-1 Å-2 : r0=2.48642 Å ) +) + +You can specify the atoms using a search string, passing the atoms themselves, +or using the atom index, just as you could for the positional restraints. + +>>> restraints = sr.restraints.bond(mols, +... atoms0=mols[0][0], +... atoms1=mols["water and element O"]) +>>> print(restraints) +BondRestraints( size=630 +0: BondRestraint( 0 <=> 22, k=150 kcal mol-1 Å-2 : r0=13.2847 Å ) +1: BondRestraint( 0 <=> 25, k=150 kcal mol-1 Å-2 : r0=10.8445 Å ) +2: BondRestraint( 0 <=> 28, k=150 kcal mol-1 Å-2 : r0=15.9183 Å ) +3: BondRestraint( 0 <=> 31, k=150 kcal mol-1 Å-2 : r0=13.5108 Å ) +4: BondRestraint( 0 <=> 34, k=150 kcal mol-1 Å-2 : r0=9.18423 Å ) +... +625: BondRestraint( 0 <=> 1897, k=150 kcal mol-1 Å-2 : r0=9.52934 Å ) +626: BondRestraint( 0 <=> 1900, k=150 kcal mol-1 Å-2 : r0=12.7207 Å ) +627: BondRestraint( 0 <=> 1903, k=150 kcal mol-1 Å-2 : r0=10.8053 Å ) +628: BondRestraint( 0 <=> 1906, k=150 kcal mol-1 Å-2 : r0=6.04142 Å ) +629: BondRestraint( 0 <=> 1909, k=150 kcal mol-1 Å-2 : r0=17.1035 Å ) +) + +.. note:: + + If the number of atoms in ``atoms0`` and ``atoms1`` are not equal, then + the smaller list will be extended to match by appending multiple copies + of the last atom. In this case, this duplicates the single atom in + ``atoms0``, meaning that this has created distance restraints between + the first atom in the first molecule and the oxygen atoms in all of + the water molecules. + +Using restraints in minimisation or dynamics +-------------------------------------------- + +You can use restraints in a minimisation or dynamics simulation by +passing them in via the ``restraints`` argument, e.g. + +>>> restraints = sr.restraints.positional(mols, atoms="resname ALA and element C") +>>> d = mols.dynamics(timestep="4fs", temperature="25oC", +... restraints=restraints) +>>> d.run("10ps") +>>> mols = d.commit() + +runs a dynamics simulation using positional restraints on the carbon atoms +of the ``ALA`` residue, while + +>>> restraints = sr.restraints.distance(mols, atoms0=mols[0][0], +... atoms1=mols[1][0], r0="5 A") +>>> mols = mols.minimisation(restraints=restraints).run().commit() + +performs a minimisation where a distance restraint is applied between the +first atoms of the first two molecules, pulling them to be 5 Å apart. + +You can pass in a list of restraints, meaning that you can use as +many as you wish during a simulation. + +>>> pos_rests = sr.restraints.positional(mols, atoms="resname ALA and element C") +>>> dst_rests = sr.restraints.distance(mols, atoms0=mols[0][0], atoms1=mols[1][0]) +>>> mols = mols.minimisation(restraints=[pos_rests, dst_rests]).run().commit() +>>> d = mols.dynamics(timestep="4fs", temperature="25oC", +... restraints=[pos_rests, dst_rests]) +>>> d.run("10ps") +>>> mols = d.commit() + +.. note:: + + It is a good idea to run minimisation before dynamics whenever you add + restraints to a system. This is because the restraints could put a lot + of energy into the system, causing blow-ups or ``NaN`` errors. + +Using restraints in the low-level API +------------------------------------- + +You can use restraints in the low-level API by passing them in as a +``map`` parameter using the key ``restraints``, e.g. + +>>> omm = sr.convert.to(mols, "openmm", +... map={"restraints": [pos_rests, dst_rests]}) +>>> print(omm) +openmm::Context( num_atoms=1915 integrator=VerletIntegrator timestep=1.0 fs platform=HIP ) + +More information about the mappable options for the low-level API can be +found in the :doc:`detailed guide <../../cheatsheet/openmm>`. + +Fixing atoms in place +--------------------- + +As well as restraining atoms, you also have the option of fixing atoms in +space during the simulation. Atoms which are fixed are not moved by +minimisation or the dynamics integrator. This can be useful if you, e.g. +want to freeze atoms outside a radius of a ligand binding site, or if you +want to minimise the solvent while keeping the solute fixed. + +You can fix atoms by passing in the set of atoms to fix as the +``fixed`` argument to the :meth:`~sire.mol.SelectorMol.minimisation` or +:meth:`~sire.mol.SelectorMol.dynamics` functions, e.g. + +>>> mols = sr.load(sr.expand(sr.tutorial_url, "ala.top", "ala.crd")) +>>> d = mols.dynamics(timestep="4fs", temperature="25oC", +... fixed=mols[0]) +>>> d.run("10ps") +>>> mols = d.commit() + +will run dynamics where all of the atoms in ``mols[0]`` (the solute) +are fixed. You can pass in a molecular container containing all of the +atoms that should be fixed, or a search string, or the atom indexes +of the atoms. Here, + +>>> d = mols.dynamics(timestep="1fs", temperature="25oC", +... fixed="element C") +>>> d.run("10ps") +>>> mols = d.commit() + +we have run dynamics where all of the carbon atoms are fixed. + +.. note:: + + Be careful using constraints with fixed atoms. If a constraint involves + a fixed atom and a mobile atom, then there is a high risk that the + constraint won't be able to be satisfied during dynamics, and + ``Particle coordinate is NaN`` error or similar will occur. + It it safest to use a small timestep (e.g. 1 fs) when constraining + only parts of molecules. + +while + +>>> d = mols.dynamics(timestep="1fs", temperature="25oC", +... fixed=[0, 2, 4, 6, 8]) +>>> d.run("10ps") +>>> mols = d.commit() + +would run dynamics where atoms at indicies 0, 2, 4, 6 and 8 are fixed. + +You could even use search strings to fix atoms by distances. If you do this, +it is best to also add half-harmonic positional restraints that hold nearby +molecules within the sphere of mobile atoms, e.g. + +>>> center = mols[0].coordinates() +>>> radius = "10 A" +>>> restraints = sr.restraints.positional(mols, +... atoms=mols[f"molecules within 10 of {center}"], +... position=center, +... r0=radius) +>>> d = mols.dynamics(timestep="1fs", temperature="25oC", +... fixed=f"not molecules within {radius} of {center}") +>>> d.run("10ps") +>>> mols = d.commit() diff --git a/doc/source/tutorial/part06/04_alchemical_restraints.rst b/doc/source/tutorial/part06/04_alchemical_restraints.rst new file mode 100644 index 000000000..cae1f5579 --- /dev/null +++ b/doc/source/tutorial/part06/04_alchemical_restraints.rst @@ -0,0 +1,159 @@ +===================== +Alchemical Restraints +===================== + +You can perturb restraints as part of an alchemical free energy simulation. +This is useful for computing free energy differences between two systems +where you want to turn on or off restraints that are used to keep part of +the molecules in place while you are performing the mutation. + +You can perturb restraints by adding them to the +:class:`~sire.cas.LambdaSchedule` used to control the perturbation. + +By default, restraints are called ``restraint``, and so are perturbed +using the ``restraint`` lever. + +>>> import sire as sr +>>> l = sr.cas.LambdaSchedule() +>>> l.add_stage("restraints_on", l.initial()) +>>> l.add_stage("morph", (1-l.lam()) * l.initial() + l.lam() * l.final()) +>>> l.add_stage("restraints_off", l.final()) +>>> print(l) +LambdaSchedule( + restraints_on: initial + morph: initial * (-λ + 1) + λ * final + restraints_off: final +) + +This has created a schedule with three stages, ``restraints_on``, ``morph`` +and ``restraints_off``. The ``morph`` stage is the one that will be used +to peturb the molecule from the initial state to the perturbed state. + +The ``restraints_on`` and ``restraints_off`` stages will be used to +switch on and off the restraints. To actually switch them on and off, +we need to set the equation that will be used to scale the restraints. + +>>> l.set_equation("restraints_on", "restraint", l.lam() * l.initial()) +>>> l.set_equation("restraints_off", "restraint", (1-l.lam()) * l.initial()) + +This will scale the restraints by λ in the ``restraints_on`` stage. This +means that the restraints start at λ=0 scaled to 0, and are then fully +switched on by λ=1. + +The restraints are scaled off by (1-λ) in the ``restraints_off`` stage. +This means that the restraints are fully switched on at λ=0, but then +scaled down so that they are scaled to 0 at λ=1. + +The value of ``initial`` and ``final`` for a restraint is the same, being +100% of the restraint. Thus ``(1-l.lam()) * l.initial() + l.lam() * l.final()`` +will always evaluate to 100% of the restraint for all values of λ during +the ``morph`` stage. You can make sure that the restraint is kept at +100% during this stage by setting this value explicitly; + +>>> l.set_equation("morph", "restraint", l.initial()) +>>> print(l) +LambdaSchedule( + restraints_on: initial + restraint: λ * initial + morph: initial * (-λ + 1) + λ * final + restraint: initial + restraints_off: final + restraint: final * (-λ + 1) +) + +You can view the effect of this schedule by plotting the perturbation +assuming an initial value of ``1.0`` and a final value of ``2.0`` using + +>>> l.get_lever_values(initial=1.0, final=2.0).plot() + +.. image:: images/06_04_01.jpg + :alt: Impact of the schedule on parameters from 1.0 to 2.0 + +Here we can see the three stages of the schedule. The ``restraints_on`` +stage keeps the ``default`` value of the restraint at the initial value +(``1.0``) while it scales the restraint from ``0`` to ``1.0``. The ``morph`` +stage keeps the restraint at ``1.0``, while scaling the parameter from +``1.0`` to ``2.0``. Finally, the ``restraints_off`` stage scales the +restraint from ``1.0`` to ``0`` while keeping the parameter at ``2.0``. + +We can now use this schedule to perturb the restraints during an alchemical +simulation e.g. + +>>> mols = sr.load(sr.expand(sr.tutorial_url, "merged_molecule.s3")) +>>> restraints = sr.restraints.positional(mols, "molidx 0") +>>> mols = mols.minimisation(restraints=restraints, +... lambda_value=0.0).run().commit() +>>> d = mols.dynamics(timestep="4fs", temperature="25oC", +... restraints=restraints, schedule=l, +... lambda_value=0.0) +>>> d.run("10ps") +>>> mols = d.commit() + +Using named restraints +---------------------- + +By default, all restraints in a system are called ``restraint``, and so are +perturbed using the ``restraint`` lever. However, you can also give restraints +their own name, and then perturb them using their name. For example, here +we create two restraints, named ``positional`` and ``distance``. + +>>> pos_rest = sr.restraints.positional(mols, "molidx 0", name="positional") +>>> dst_rest = sr.restraints.distance(mols, atoms0=0, atoms1=1, name="distance") +>>> print(pos_rest, dst_rest) +PositionalRestraints( name=positional, size=8 +0: PositionalRestraint( 0 => ( 25.7128, 24.9375, 25.2539 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +1: PositionalRestraint( 1 => ( 24.2872, 25.0626, 24.7461 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +2: PositionalRestraint( 2 => ( 25.9115, 23.8899, 25.5639 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +3: PositionalRestraint( 3 => ( 26.425, 25.2206, 24.4509 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +4: PositionalRestraint( 4 => ( 25.8616, 25.6094, 26.1259 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +5: PositionalRestraint( 5 => ( 24.1384, 24.3907, 23.8741 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +6: PositionalRestraint( 6 => ( 24.0888, 26.1101, 24.4351 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +7: PositionalRestraint( 7 => ( 23.575, 24.7795, 25.5491 ), k=150 kcal mol-1 Å-2 : r0=0 Å ) +) BondRestraints( name=distance, size=1 +0: BondRestraint( 0 <=> 1, k=150 kcal mol-1 Å-2 : r0=1.5185 Å ) +) + +We can now create a schedule that perturbs these restraints separately using +their names. We will first scale up the ``distance`` restraint in a +``distance_restraints`` stage... + +>>> l = sr.cas.LambdaSchedule() +>>> l.add_stage("distance_restraints", 0) +>>> l.set_equation("distance_restraints", "distance", l.lam() * l.initial()) + +and will then scale up the ``positional`` restraint in a +``positional_restraints`` stage, while keeping the ``distance`` restraint +fully on. + +>>> l.add_stage("positional_restraints", 1) +>>> l.set_equation("positional_restraints", "positional", l.lam() * l.initial()) +>>> print(l) +LambdaSchedule( + distance_restraints: 0 + distance: λ * initial + positional_restraints: 1 + positional: λ * initial +) +>>> l.get_lever_values(initial=1.0, final=1.0, +... levers=["positional", "distance"]).plot() + +.. image:: images/06_04_02.jpg + :alt: Impact of the schedule for scaling two restraints separately + +Here we can see that, in the first ``distance_restraints`` stage +(from λ=0 to λ=0.5), the ``distance`` restraint is scaled from ``0`` to ``1`` +while the ``positional`` restraint is kept at ``0``. In the second +``positional_restraints`` stage (from λ=0.5 to λ=1), the ``positional`` +restraint is scaled from ``0`` to ``1`` while the ``distance`` restraint is +kept at ``1``. + +We can now use this schedule in a simulation, e.g. + +>>> mols = sr.load(sr.expand(sr.tutorial_url, "merged_molecule.s3")) +>>> mols = mols.minimisation(restraints=[dst_rest, pos_rest], +... lambda_value=0.0).run().commit() +>>> d = mols.dynamics(timestep="4fs", temperature="25oC", +... restraints=[dst_rest, pos_rest], schedule=l, +... lambda_value=0.0) +>>> d.run("10ps") +>>> mols = d.commit() diff --git a/doc/source/tutorial/part06/images/06_04_01.jpg b/doc/source/tutorial/part06/images/06_04_01.jpg new file mode 100644 index 000000000..939c1b391 Binary files /dev/null and b/doc/source/tutorial/part06/images/06_04_01.jpg differ diff --git a/doc/source/tutorial/part06/images/06_04_02.jpg b/doc/source/tutorial/part06/images/06_04_02.jpg new file mode 100644 index 000000000..e6bcc11e2 Binary files /dev/null and b/doc/source/tutorial/part06/images/06_04_02.jpg differ diff --git a/src/sire/CMakeLists.txt b/src/sire/CMakeLists.txt index 737e746b8..d56e2c42f 100644 --- a/src/sire/CMakeLists.txt +++ b/src/sire/CMakeLists.txt @@ -101,7 +101,9 @@ add_subdirectory (io) add_subdirectory (maths) add_subdirectory (mm) add_subdirectory (mol) +add_subdirectory (morph) add_subdirectory (move) +add_subdirectory (restraints) add_subdirectory (search) add_subdirectory (stream) add_subdirectory (squire) diff --git a/src/sire/__init__.py b/src/sire/__init__.py index 758e15aaf..c598c3c05 100644 --- a/src/sire/__init__.py +++ b/src/sire/__init__.py @@ -695,8 +695,10 @@ def _convert(id): maths = _lazy_import.lazy_module("sire.maths") mm = _lazy_import.lazy_module("sire.mm") mol = _lazy_import.lazy_module("sire.mol") + morph = _lazy_import.lazy_module("sire.morph") move = _lazy_import.lazy_module("sire.move") qt = _lazy_import.lazy_module("sire.qt") + restraints = _lazy_import.lazy_module("sire.restraints") search = _lazy_import.lazy_module("sire.search") squire = _lazy_import.lazy_module("sire.squire") stream = _lazy_import.lazy_module("sire.stream") diff --git a/src/sire/_pythonize.py b/src/sire/_pythonize.py index d2c9eda9f..bbfbe2612 100644 --- a/src/sire/_pythonize.py +++ b/src/sire/_pythonize.py @@ -72,6 +72,8 @@ def _pythonize(C, delete_old: bool = True) -> None: new_attr = new_attr.replace("asA", "as") elif new_attr.startswith("isAn"): new_attr = new_attr.replace("isAn", "is") + elif new_attr.startswith("isAtom"): + new_attr = new_attr.replace("isAtom", "is_atom") elif new_attr.startswith("isA"): new_attr = new_attr.replace("isA", "is") @@ -154,115 +156,14 @@ def _pythonize_modules(modules, delete_old: bool = True): _is_using_new_api = None -def use_mixed_api(support_old_module_names: bool = False): - """Load Sire using both the new (python-style) and old APIs. This - is useful for migrating old scripts as a temporary porting option. - You can start writing functions using the new API, safe in the - knowledge that the old API functions will still work. - - Do aim to finish your port though, else you will forever have - a duplicated API (e.g. have both X.nAtoms() and X.num_atoms() etc.) +def _load_new_api_modules(delete_old: bool = True): """ - global _is_using_new_api, _is_using_old_api - - if _is_using_old_api and _is_using_new_api: - # don't need to do this twice - return - - if _is_using_old_api or _is_using_new_api: - msg = ( - "Cannot import sire using the mixed API as either the old " - "or new APIs have already been activated." - ) - print(msg) - - raise ImportError(msg) - - # First, bring in the old API - if support_old_module_names: - print("Loading Sire with support for old module names.") - print( - "Note that this can cause problems with classes importing twice." - ) - use_old_api() - else: - _is_using_old_api = True - - # Now, bring in the new API - _is_using_new_api = True - - # call Pythonize on all of the new modules - from . import ( - move, - io, - system, - squire, - mm, - ff, - mol, - analysis, - base, - cas, - cluster, - error, - id, - maths, - qt, - stream, - units, - vol, - ) - - _pythonize_modules( - [ - analysis._Analysis, - base._Base, - cas._CAS, - cluster._Cluster, - error._Error, - ff._FF, - id._ID, - io._IO, - maths._Maths, - mm._MM, - mol._Mol, - move._Move, - qt._Qt, - squire._Squire, - stream._Stream, - system._System, - units._Units, - vol._Vol, - ], - delete_old=False, - ) - - -def use_new_api(): - """Load Sire using the new (python-style) API. This will be called - automatically when you load any of the new Python modules, so you - shouldn't need to call this yourself. + Internal function to load the new API modules, pythonizing + the function names as we go. If `delete_old` is True, then + the old function names will be deleted. Otherwise, they will + be kept. Keeping the names is only needed for the mixed API. """ - global _is_using_new_api, _is_using_old_api - - # load up the new console - ensure this is done once - from .utils import Console as _Console - - _Console._get_console() - - if _is_using_new_api: - # already done - return - - if _is_using_old_api: - msg = ( - "Cannot import sire using the new API as the old API has " - "already been activated. Both APIs cannot be active at " - "the same time." - ) - print(msg) - - raise ImportError(msg) + global _is_using_new_api _is_using_new_api = True @@ -309,7 +210,8 @@ def use_new_api(): System._System, Units._Units, Vol._Vol, - ] + ], + delete_old=delete_old, ) try: @@ -337,6 +239,8 @@ def use_new_api(): error, id, maths, + morph, + restraints, qt, stream, units, @@ -359,6 +263,8 @@ def use_new_api(): error, id, maths, + morph, + restraints, qt, stream, units, @@ -369,6 +275,74 @@ def use_new_api(): dir(M) +def use_mixed_api(support_old_module_names: bool = False): + """Load Sire using both the new (python-style) and old APIs. This + is useful for migrating old scripts as a temporary porting option. + You can start writing functions using the new API, safe in the + knowledge that the old API functions will still work. + + Do aim to finish your port though, else you will forever have + a duplicated API (e.g. have both X.nAtoms() and X.num_atoms() etc.) + """ + global _is_using_new_api, _is_using_old_api + + if _is_using_old_api and _is_using_new_api: + # don't need to do this twice + return + + if _is_using_old_api or _is_using_new_api: + msg = ( + "Cannot import sire using the mixed API as either the old " + "or new APIs have already been activated." + ) + print(msg) + + raise ImportError(msg) + + # First, bring in the old API + if support_old_module_names: + print("Loading Sire with support for old module names.") + print( + "Note that this can cause problems with classes importing twice." + ) + use_old_api() + else: + _is_using_old_api = True + + # Now bring in the new API + _load_new_api_modules(delete_old=False) + + +def use_new_api(): + """Load Sire using the new (python-style) API. This will be called + automatically when you load any of the new Python modules, so you + shouldn't need to call this yourself. + """ + global _is_using_new_api, _is_using_old_api + + # load up the new console - ensure this is done once + from .utils import Console as _Console + + _Console._get_console() + + if _is_using_new_api: + # already done + return + + if _is_using_old_api: + msg = ( + "Cannot import sire using the new API as the old API has " + "already been activated. Both APIs cannot be active at " + "the same time." + ) + print(msg) + + raise ImportError(msg) + + # Now bring in the new API + _load_new_api_modules(delete_old=True) + + def use_old_api(): """Load Sire using the old (C++-style) API. This is for compatibility reasons for old code only. This should diff --git a/src/sire/cas/__init__.py b/src/sire/cas/__init__.py index 40c5b74fa..8de0212a0 100644 --- a/src/sire/cas/__init__.py +++ b/src/sire/cas/__init__.py @@ -30,6 +30,7 @@ def _fix_lambdaschedule(): def get_lever_values( obj, lambda_values=None, + levers=None, num_lambda=101, initial=1.0, final=2.0, @@ -45,6 +46,11 @@ def get_lever_values( A list of lambda values to evaluate. If this is not passed then the lambda values will be auto-generated + levers: str or list[str] + A list of the levers for which to retrieve the values. + If this is not given, then all of the levers will be + returned. + num_lambda: int The number of lambda values to auto-generate if a list of lambda values is not passed. This defaults to 101. @@ -70,6 +76,22 @@ def get_lever_values( lambda_values=lambda_values, initial=initial, final=final ) + if levers is not None: + if type(levers) is not list: + levers = [levers] + + results = {} + + results["λ"] = vals["λ"] + + for lever in levers: + if lever in vals: + results[lever] = vals[lever] + else: + results[lever] = vals["default"] + + vals = results + if not to_pandas: return vals diff --git a/src/sire/convert/__init__.py b/src/sire/convert/__init__.py index c32b4c4dd..533d909ea 100644 --- a/src/sire/convert/__init__.py +++ b/src/sire/convert/__init__.py @@ -15,16 +15,12 @@ from .. import use_new_api as _use_new_api -# Imported to ensure that these classes are properly wrapped -from ..maths import Vector as _Vector -from ..cas import LambdaSchedule as _LambdaSchedule - -from ..mol import SelectorMol as _SelectorMol - _use_new_api() def _to_selectormol(obj): + from ..mol import SelectorMol + if hasattr(obj, "molecules"): return obj.molecules() elif type(obj) is list: @@ -33,9 +29,9 @@ def _to_selectormol(obj): for o in obj: mols.append(_to_selectormol(o)) - return _SelectorMol(mols) + return SelectorMol(mols) else: - return _SelectorMol(obj) + return SelectorMol(obj) def supported_formats(): @@ -351,6 +347,19 @@ def sire_to_openmm(obj, map=None): of molecules) to an OpenMM equivalent """ # will eventually support System too... + from ..system import System + from ..base import create_map + + map = create_map(map) + + if System.is_system(obj): + # bring in system-level properties + if not map.specified("space"): + map.set("space", obj.space()) + + if not map.specified("time"): + map.set("time", obj.time()) + obj = _to_selectormol(obj) try: @@ -361,9 +370,7 @@ def sire_to_openmm(obj, map=None): "'mamba install -c conda-forge openmm'" ) - from ..base import create_map - - mols = _sire_to_openmm(obj, map=create_map(map)) + mols = _sire_to_openmm(obj, map=map) return mols diff --git a/src/sire/maths/__init__.py b/src/sire/maths/__init__.py index df5bd9337..cbec5949c 100644 --- a/src/sire/maths/__init__.py +++ b/src/sire/maths/__init__.py @@ -2,10 +2,12 @@ "align", "create_quaternion", "get_alignment", + "EnergyTrajectory", "kabasch", "kabasch_fit", "Matrix", "pi", + "RanGenerator", "Sphere", "Torsion", "Transform", @@ -14,7 +16,16 @@ ] from ..legacy import Maths as _Maths -from ..legacy.Maths import Matrix, Quaternion, Triangle, Transform, Torsion, pi +from ..legacy.Maths import ( + Matrix, + Quaternion, + RanGenerator, + Triangle, + Transform, + Torsion, + pi, + EnergyTrajectory, +) from ._vector import Vector from ._sphere import Sphere @@ -155,3 +166,27 @@ def create_quaternion(angle=None, axis=None, matrix=None, quaternion=None): ) return quaternion + + +if not hasattr(EnergyTrajectory, "to_pandas"): + + def _to_pandas(obj): + """ + Return the energy trajectory as a pandas DataFrame + """ + import pandas as pd + from ..units import picosecond, kcal_per_mol + + data = {} + + data["time"] = obj.times(picosecond.get_default()) + + keys = obj.keys() + keys.sort() + + for key in keys: + data[str(key)] = obj.energies(key, kcal_per_mol.get_default()) + + return pd.DataFrame(data).set_index("time") + + EnergyTrajectory.to_pandas = _to_pandas diff --git a/src/sire/mm/__init__.py b/src/sire/mm/__init__.py index 0bd0c3e08..d33dec90b 100644 --- a/src/sire/mm/__init__.py +++ b/src/sire/mm/__init__.py @@ -1,8 +1,16 @@ __all__ = [ + "AmberBond", + "AmberAngle", + "AmberDihPart", + "AmberDihedral", "Angle", "Bond", + "BondRestraint", + "BondRestraints", "Dihedral", "Improper", + "PositionRestraint", + "PositionRestraints", "SelectorAngle", "SelectorBond", "SelectorDihedral", @@ -37,6 +45,22 @@ _use_new_api() +# It would be better if these were called "DistanceRestraints", +# but there is already a legacy Sire.MM class with this name +BondRestraint = _MM.BondRestraint +BondRestraints = _MM.BondRestraints + +BoreschRestraint = _MM.BoreschRestraint +BoreschRestraints = _MM.BoreschRestraints + +PositionalRestraint = _MM.PositionalRestraint +PositionalRestraints = _MM.PositionalRestraints + +AmberBond = _MM.AmberBond +AmberAngle = _MM.AmberAngle +AmberDihPart = _MM.AmberDihPart +AmberDihedral = _MM.AmberDihedral + LJParameter = _MM.LJParameter Bond = _MM.Bond diff --git a/src/sire/mol/__init__.py b/src/sire/mol/__init__.py index 98d1e7f2e..29de1a105 100644 --- a/src/sire/mol/__init__.py +++ b/src/sire/mol/__init__.py @@ -1,6 +1,7 @@ __all__ = [ "get_alignment", "is_water", + "selection_to_atoms", "Atom", "AtomIdx", "AtomMapping", @@ -125,6 +126,85 @@ get_alignment = _Mol.get_alignment +def selection_to_atoms(mols, atoms): + """ + Convert the passed selection to a list of atoms. + + Parameters + ---------- + mols : A molecule view or collection + The molecule container from which to select the atoms. + atoms : str, int, list, molecule view/collection etc. + Any valid search string, atom index, list of atom indicies, + or molecule view/container that can be used to select + atoms from ``mols`` + + Returns + ------- + atoms : A SelectorM_Atoms_ or Selector_Atom_ containing the list + of atoms. + + Examples + -------- + + >>> import sire as sr + >>> mols = sr.load(sr.expand(sr.tutorial_url, "ala.top", "ala.crd")) + >>> sr.mol.selection_to_atoms(mols, "atomname CA") + Selector( size=1 + 0: Atom( CA:9 [ 16.54, 5.03, 15.81] ) + ) + + >>> sr.mol.selection_to_atoms(mols, "resname ALA") + Selector( size=10 + 0: Atom( N:7 [ 17.22, 4.31, 14.71] ) + 1: Atom( H:8 [ 16.68, 3.62, 14.22] ) + 2: Atom( CA:9 [ 16.54, 5.03, 15.81] ) + 3: Atom( HA:10 [ 17.29, 5.15, 16.59] ) + 4: Atom( CB:11 [ 16.05, 6.39, 15.26] ) + 5: Atom( HB1:12 [ 15.63, 6.98, 16.07] ) + 6: Atom( HB2:13 [ 16.90, 6.89, 14.80] ) + 7: Atom( HB3:14 [ 15.24, 6.18, 14.55] ) + 8: Atom( C:15 [ 15.37, 4.19, 16.43] ) + 9: Atom( O:16 [ 14.94, 3.17, 15.88] ) + ) + + >>> sr.mol.selection_to_atoms(mols, [0, 1, 2, 3]) + SireMol::SelectorM( size=4 + 0: MolNum(641) Atom( HH31:1 [ 18.45, 3.49, 12.44] ) + 1: MolNum(641) Atom( CH3:2 [ 18.98, 3.45, 13.39] ) + 2: MolNum(641) Atom( HH32:3 [ 20.05, 3.63, 13.29] ) + 3: MolNum(641) Atom( HH33:4 [ 18.80, 2.43, 13.73] ) + ) + + >>> sr.mol.selection_to_atoms(mols, mols[0]) + Selector( size=22 + 0: Atom( HH31:1 [ 18.45, 3.49, 12.44] ) + 1: Atom( CH3:2 [ 18.98, 3.45, 13.39] ) + 2: Atom( HH32:3 [ 20.05, 3.63, 13.29] ) + 3: Atom( HH33:4 [ 18.80, 2.43, 13.73] ) + 4: Atom( C:5 [ 18.48, 4.55, 14.35] ) + ... + 17: Atom( H:18 [ 15.34, 5.45, 17.96] ) + 18: Atom( CH3:19 [ 13.83, 3.94, 18.35] ) + 19: Atom( HH31:20 [ 14.35, 3.41, 19.15] ) + 20: Atom( HH32:21 [ 13.19, 4.59, 18.94] ) + 21: Atom( HH33:22 [ 13.21, 3.33, 17.69] ) + ) + """ + if hasattr(atoms, "atoms"): + return atoms.atoms() + + from ..legacy.Base import NumberProperty, IntegerArrayProperty + + if type(atoms) is int or type(atoms) is list: + return mols.atoms()[atoms] + elif type(atoms) is NumberProperty or type(atoms) is IntegerArrayProperty: + atoms = [x.as_integer() for x in atoms.as_array()] + return mols.atoms()[atoms] + else: + return mols[atoms].atoms() + + def is_water(mol, map=None): """ Return whether or not the passed molecule (or collection of molecules) @@ -1319,22 +1399,147 @@ def _cursor(view, map=None): def _dynamics( view, - map=None, cutoff=None, cutoff_type=None, timestep=None, save_frequency=None, + energy_frequency=None, + frame_frequency=None, constraint=None, + schedule=None, + lambda_value=None, + swap_end_states=None, + temperature=None, + pressure=None, + shift_delta=None, + coulomb_power=None, + restraints=None, + fixed=None, + device=None, + map=None, **kwargs, ): """ Return a Dynamics object that can be used to perform dynamics of the molecule(s) in this view + + cutoff: Length + The size of the non-bonded cutoff + + cutoff_type: str + The type of cutoff to use, e.g. "PME", "RF" etc. + See https://sire.openbiosim.org/cheatsheet/openmm.html#choosing-options + for the full list of options + + timestep: time + The size of the dynamics timestep + + save_frequency: time + The amount of simulation time between saving energies and frames. + This can be overridden using `energy_frequency` or `frame_frequency`, + or by these options in individual dynamics runs. Set this + to zero if you don't want any saves. + + energy_frequency: time + The amount of time between saving energies. This overrides the + value in `save_frequency`. Set this to zero if you don't want + to save energies during the trajectory. This can be overridden + by setting energy_frequency during an individual run. + + frame_frequency: time + The amount of time between saving frames. This overrides the + value in `save_frequency`. Set this to zero if you don't want + to save frames during the trajectory. This can be overridden + by setting frame_frequency during an individual run. + + constraint: str + The type of constraint to use for bonds and/or angles, e.g. + `h-bonds`, `bonds` etc. + See https://sire.openbiosim.org/cheatsheet/openmm.html#choosing-options + for the full list of options. This will be automatically + guessed from the timestep if it isn't set. + + schedule: sire.cas.LambdaSchedule + The schedule used to control how perturbable forcefield parameters + should be morphed as a function of lambda. If this is not set + then a sire.cas.LambdaSchedule.standard_morph() is used. + + lambda_value: float + The value of lambda at which to run dynamics. This only impacts + perturbable molecules, whose forcefield parameters will be + scaled according to the lambda schedule for the specified + value of lambda. + + swap_end_states: bool + Whether or not to swap the end states. If this is True, then + the perturbation will run from the perturbed back to the + reference molecule (the perturbed molecule will be at lambda=0, + while the reference molecule will be at lambda=1). This will + use the coordinates of the perturbed molecule as the + starting point. + + temperature: temperature + The temperature at which to run the simulation. A + microcanonical (NVE) simulation will be run if you don't + specify the temperature. + + pressure: pressure + The pressure at which to run the simulation. A + microcanonical (NVE) or canonical (NVT) simulation will be + run if the pressure is not set. + + shift_delta: length + The shift_delta parameter that controls the electrostatic + and van der Waals softening potential that smooths the + creation and deletion of ghost atoms during a potential. + This defaults to 2.0 A. + + coulomb_power: int + The coulomb power parmeter that controls the electrostatic + softening potential that smooths the creation and deletion + of ghost atoms during a potential. This defaults to 0. + + restraints: sire.mm.Restraints or list[sire.mm.Restraints] + A single set of restraints, or a list of sets of + restraints that will be applied to the atoms during + the simulation. + + fixed: molecule(s) view, search string, int, list[int] etc + Anything that can be used to identify the atom or atoms + that should be fixed in place during the simulation. These + atoms will not be moved by dynamics. + + device: str or int + The ID of the GPU (or accelerator) used to accelerate + the simulation. This would be CUDA_DEVICE_ID or similar + if CUDA was used. This can be any valid OpenMM device string + + map: dict + A dictionary of additional options. Note that any options + set in this dictionary that are also specified via one of + the arguments above will be overridden by the argument + value """ from ..base import create_map + from ..system import System + from .. import u map = create_map(map, kwargs) + if not map.specified("space"): + map = create_map(map, {"space": "space"}) + + if ( + System.is_system(view) + and map.specified("space") + and not map["space"].has_value() + and not view.shared_properties().has_property(map["space"]) + ): + # space is not a shared property, so may be lost when we + # convert to molecules. Make sure this doens't happen by + # adding the space directly to the property map + map.set("space", view.property(map["space"])) + # Set default values if these have not been set if cutoff is None and not map.specified("cutoff"): from ..units import angstrom @@ -1342,15 +1547,20 @@ def _dynamics( cutoff = 7.5 * angstrom if cutoff_type is None and not map.specified("cutoff_type"): - cutoff_type = "PME" + try: + if view.property(map["space"]).is_periodic(): + cutoff_type = "PME" + else: + cutoff_type = "RF" + except Exception: + # no space, use RF + cutoff_type = "RF" if timestep is None and not map.specified("timestep"): from ..units import femtosecond timestep = 1 * femtosecond else: - from .. import u - timestep = u(timestep) if save_frequency is None and not map.specified("save_frequency"): @@ -1358,10 +1568,14 @@ def _dynamics( save_frequency = 25 * picosecond else: - from .. import u - save_frequency = u(save_frequency) + if energy_frequency is not None: + map.set("energy_frequency", energy_frequency) + + if frame_frequency is not None: + map.set("frame_frequency", frame_frequency) + if constraint is None and not map.specified("constraint"): from ..units import femtosecond @@ -1381,23 +1595,176 @@ def _dynamics( # can get away with no constraints constraint = "none" + if temperature is not None: + temperature = u(temperature) + map.set("temperature", temperature) + + if pressure is not None: + pressure = u(pressure) + map.set("pressure", pressure) + + if device is not None: + map.set("device", str(device)) + return Dynamics( view, cutoff=cutoff, cutoff_type=cutoff_type, timestep=timestep, save_frequency=save_frequency, - constraint=constraint, + constraint=str(constraint), + schedule=schedule, + lambda_value=lambda_value, + shift_delta=shift_delta, + coulomb_power=coulomb_power, + swap_end_states=swap_end_states, + restraints=restraints, + fixed=fixed, map=map, ) -def _minimisation(view, map=None): +def _minimisation( + view, + cutoff=None, + cutoff_type=None, + constraint=None, + schedule=None, + lambda_value=None, + swap_end_states=None, + shift_delta=None, + coulomb_power=None, + device=None, + restraints=None, + fixed=None, + map=None, + **kwargs, +): """ - Return a Minimisation object that can be used to minimise the energy - of the molecule(s) in this view. + Return a Minimisation object that can be used to perform + minimisation of the molecule(s) in this view + + cutoff: Length + The size of the non-bonded cutoff + + cutoff_type: str + The type of cutoff to use, e.g. "PME", "RF" etc. + See https://sire.openbiosim.org/cheatsheet/openmm.html#choosing-options + for the full list of options + + constraint: str + The type of constraint to use for bonds and/or angles, e.g. + `h-bonds`, `bonds` etc. + See https://sire.openbiosim.org/cheatsheet/openmm.html#choosing-options + for the full list of options. This will be automatically + guessed from the timestep if it isn't set. + + schedule: sire.cas.LambdaSchedule + The schedule used to control how perturbable forcefield parameters + should be morphed as a function of lambda. If this is not set + then a sire.cas.LambdaSchedule.standard_morph() is used. + + lambda_value: float + The value of lambda at which to run minimisation. This only impacts + perturbable molecules, whose forcefield parameters will be + scaled according to the lambda schedule for the specified + value of lambda. + + swap_end_states: bool + Whether or not to swap the end states. If this is True, then + the perturbation will run from the perturbed back to the + reference molecule (the perturbed molecule will be at lambda=0, + while the reference molecule will be at lambda=1). This will + use the coordinates of the perturbed molecule as the + starting point. + + shift_delta: length + The shift_delta parameter that controls the electrostatic + and van der Waals softening potential that smooths the + creation and deletion of ghost atoms during a potential. + This defaults to 2.0 A. + + coulomb_power: int + The coulomb power parmeter that controls the electrostatic + softening potential that smooths the creation and deletion + of ghost atoms during a potential. This defaults to 0. + + restraints: sire.mm.Restraints or list[sire.mm.Restraints] + A single set of restraints, or a list of sets of + restraints that will be applied to the atoms during + the simulation. + + fixed: molecule(s) view, search string, int, list[int] etc + Anything that can be used to identify the atom or atoms + that should be fixed in place during the simulation. These + atoms will not be moved by minimisation. + + device: str or int + The ID of the GPU (or accelerator) used to accelerate + minimisation. This would be CUDA_DEVICE_ID or similar + if CUDA was used. This can be any valid OpenMM device string + + map: dict + A dictionary of additional options. Note that any options + set in this dictionary that are also specified via one of + the arguments above will be overridden by the argument + value """ - return Minimisation(view, map=map) + from ..base import create_map + from ..system import System + from .. import u + + map = create_map(map, kwargs) + + if not map.specified("space"): + map = create_map(map, {"space": "space"}) + + if ( + System.is_system(view) + and map.specified("space") + and not map["space"].has_value() + and not view.shared_properties().has_property(map["space"]) + ): + # space is not a shared property, so may be lost when we + # convert to molecules. Make sure this doens't happen by + # adding the space directly to the property map + map.set("space", view.property(map["space"])) + + # Set default values if these have not been set + if cutoff is None and not map.specified("cutoff"): + from ..units import angstrom + + cutoff = 7.5 * angstrom + + if cutoff_type is None and not map.specified("cutoff_type"): + try: + if view.property(map["space"]).is_periodic(): + cutoff_type = "PME" + else: + cutoff_type = "RF" + except Exception: + # no space, use RF + cutoff_type = "RF" + + if device is not None: + map.set("device", str(device)) + + if constraint is not None: + map.set("constraint", str(constraint)) + + return Minimisation( + view, + cutoff=cutoff, + cutoff_type=cutoff_type, + schedule=schedule, + lambda_value=lambda_value, + swap_end_states=swap_end_states, + shift_delta=shift_delta, + coulomb_power=coulomb_power, + restraints=restraints, + fixed=fixed, + map=map, + ) MoleculeView.dynamics = _dynamics @@ -1807,5 +2174,31 @@ def __mapping_map__(obj, atoms, container, match_all: bool = True): AtomMapping.map = __mapping_map__ +if not hasattr(Molecule, "perturbation"): + + def __molecule_perturbation(mol): + """ + Return an interface to the perturbable properties of + this molecule. Note that the molecule must be + perturbable to call this function + """ + from ..morph._perturbation import Perturbation + + return Perturbation(mol) + + def __molecule_is_perturbable(mol): + """ + Return whether or not this molecule is perturbable + (can be morphed with a lambda coordinate) + """ + if mol.has_property("is_perturbable"): + return mol.property("is_perturbable").as_boolean() + else: + return False + + Molecule.perturbation = __molecule_perturbation + Molecule.is_perturbable = __molecule_is_perturbable + + # Remove some temporary variables del C diff --git a/src/sire/mol/_dynamics.py b/src/sire/mol/_dynamics.py index 45bacf03c..3bd1c8e75 100644 --- a/src/sire/mol/_dynamics.py +++ b/src/sire/mol/_dynamics.py @@ -1,6 +1,9 @@ __all__ = ["Dynamics"] +from typing import Any + + class DynamicsData: """ Internal class that is designed to only be used by the Dynamics @@ -16,16 +19,29 @@ def __init__(self, mols=None, map=None, **kwargs): if mols is not None: self._map = map # this is already a PropertyMap + # see if there are any fixed atoms + if map.specified("fixed"): + if map["fixed"].has_value(): + fixed_atoms = map["fixed"].value() + else: + fixed_atoms = map["fixed"].source() + + from . import selection_to_atoms + + # turn the fixed atoms into a list of atoms + map.set( + "fixed", + mols.atoms().find(selection_to_atoms(mols, fixed_atoms)), + ) + # get the forcefield info from the passed parameters # and from whatever we can glean from the molecules - from ..system import ForceFieldInfo + from ..system import ForceFieldInfo, System self._ffinfo = ForceFieldInfo(mols, map=self._map) # We want to store the molecules as a System so that # we can more easily track the space and time properties - from ..system import System, ForceFieldInfo - if type(mols) is System: # work on our own copy of the system self._sire_mols = mols.clone() @@ -39,6 +55,11 @@ def __init__(self, mols=None, map=None, **kwargs): "space", self._ffinfo.space() ) + # find the existing energy trajectory - we will build on this + self._energy_trajectory = self._sire_mols.energy_trajectory( + to_pandas=False, map=self._map + ) + self._num_atoms = len(self._sire_mols.atoms()) from ..units import nanosecond @@ -50,20 +71,19 @@ def __init__(self, mols=None, map=None, **kwargs): ) except Exception: current_time = 0 * nanosecond - - self._sire_mols.set_property("time", current_time) + self._sire_mols.set_property("time", current_time) self._current_time = current_time self._current_step = 0 self._elapsed_time = 0 * nanosecond self._walltime = 0 * nanosecond - self._is_running = None + self._is_running = False from ..convert import to self._omm_mols = to(self._sire_mols, "openmm", map=self._map) self._omm_state = None - self._omm_state_has_cv = False + self._omm_state_has_cv = (False, False) if self._ffinfo.space().is_periodic(): self._enforce_periodic_box = True @@ -77,26 +97,44 @@ def __init__(self, mols=None, map=None, **kwargs): else: self._sire_mols = None + self._energy_trajectory = None def is_null(self): return self._sire_mols is None def _clear_state(self): self._omm_state = None - self._omm_state_has_cv = False + self._omm_state_has_cv = (False, False) - def _update_from(self, state, nsteps_completed): + def _update_from(self, state, state_has_cv, nsteps_completed): if self.is_null(): return + if not state_has_cv[0]: + # there is no information to update + return + from ..legacy.Convert import ( openmm_extract_coordinates_and_velocities, + openmm_extract_coordinates, openmm_extract_space, ) - mols = openmm_extract_coordinates_and_velocities( - state, self._sire_mols.molecules(), self._map - ) + if state_has_cv[1]: + # get velocities too + mols = openmm_extract_coordinates_and_velocities( + state, + self._sire_mols.molecules(), + perturbable_maps=self._omm_mols.get_lambda_lever().get_perturbable_molecule_maps(), + map=self._map, + ) + else: + mols = openmm_extract_coordinates( + state, + self._sire_mols.molecules(), + perturbable_maps=self._omm_mols.get_lambda_lever().get_perturbable_molecule_maps(), + map=self._map, + ) space = openmm_extract_space(state) @@ -107,41 +145,41 @@ def _update_from(self, state, nsteps_completed): self._sire_mols.set_property("time", self._current_time) self._ffinfo.set_space(space) - def _start_dynamics_block(self): - if self._is_running is not None: + def _enter_dynamics_block(self): + if self._is_running: raise SystemError( "Cannot start dynamics while it is already running!" ) - import datetime - - self._is_running = datetime.datetime.now().timestamp() + self._is_running = True self._omm_state = None - self._omm_state_has_cv = False + self._omm_state_has_cv = (False, False) - def _end_dynamics_block(self, coords_and_vels=False): - if self._is_running is None: + def _exit_dynamics_block( + self, + save_frame: bool = False, + save_energy: bool = False, + lambda_windows=[], + save_velocities: bool = False, + ): + if not self._is_running: raise SystemError("Cannot stop dynamics that is not running!") - import datetime import openmm - from ..units import second, nanosecond - - self._walltime += ( - datetime.datetime.now().timestamp() - self._is_running - ) * second + from ..units import nanosecond, kcal_per_mol - if coords_and_vels: + if save_frame: self._omm_state = self._omm_mols.getState( getEnergy=True, getPositions=True, - getVelocities=True, + getVelocities=save_velocities, enforcePeriodicBox=self._enforce_periodic_box, ) + + self._omm_state_has_cv = (True, save_velocities) else: self._omm_state = self._omm_mols.getState(getEnergy=True) - - self._omm_state_has_cv = coords_and_vels + self._omm_state_has_cv = (False, False) current_time = ( self._omm_state.getTime().value_in_unit(openmm.unit.nanosecond) @@ -153,28 +191,66 @@ def _end_dynamics_block(self, coords_and_vels=False): self._elapsed_time = current_time self._current_time += delta - self._is_running = None + if save_energy: + # should save energy here + nrgs = {} - return self._omm_state + nrgs["kinetic"] = ( + self._omm_state.getKineticEnergy().value_in_unit( + openmm.unit.kilocalorie_per_mole + ) + * kcal_per_mol + ) + + nrgs["potential"] = ( + self._omm_state.getPotentialEnergy().value_in_unit( + openmm.unit.kilocalorie_per_mole + ) + * kcal_per_mol + ) - def _get_current_state(self, coords_and_vels=False): + if lambda_windows is not None: + sim_lambda_value = self._omm_mols.get_lambda() + nrgs[str(sim_lambda_value)] = nrgs["potential"] + + for lambda_value in lambda_windows: + if lambda_value != sim_lambda_value: + self._omm_mols.set_lambda(lambda_value) + nrgs[str(lambda_value)] = ( + self._omm_mols.get_potential_energy( + to_sire_units=False + ).value_in_unit(openmm.unit.kilocalorie_per_mole) + * kcal_per_mol + ) + + self._omm_mols.set_lambda(sim_lambda_value) + + self._energy_trajectory.set(self._current_time, nrgs) + + self._is_running = False + + return (self._omm_state, self._omm_state_has_cv) + + def _get_current_state( + self, include_coords: bool = False, include_velocities: bool = False + ): if self._omm_state is not None: - if self._omm_state_has_cv or (coords_and_vels is False): + if self._omm_state_has_cv == (include_coords, include_velocities): return self._omm_state # we need to get this again as either it doesn't exist, # or we now want velocities as well - if coords_and_vels: + if include_coords or include_velocities: self._omm_state = self._omm_mols.getState( getEnergy=True, - getPositions=True, - getVelocities=True, + getPositions=include_coords, + getVelocities=include_velocities, enforcePeriodicBox=self._enforce_periodic_box, ) else: self._omm_state = self._omm_mols.getState(getEnergy=True) - self._omm_state_has_cv = coords_and_vels + self._omm_state_has_cv = (include_coords, include_velocities) return self._omm_state @@ -197,6 +273,36 @@ def ensemble(self): return Ensemble(map=self._map) + def set_ensemble(self, ensemble, rescale_velocities: bool = True): + """ + Set the ensemble for the dynamics. Note that this will + only let you change the temperature and/or pressure of the + ensemble. You can't change its fundemental nature. + + If rescalse_velocities is True, then the velocities will + be rescaled to the new temperature. + """ + if self.is_null(): + return + + if ensemble.name() != self.ensemble().name(): + raise ValueError( + "You cannot change the ensemble of the dynamics. " + f"Currently the ensemble is {self.ensemble().name()}, " + f"but you tried to set it to {ensemble.name()}." + ) + + if ensemble.temperature() != self.ensemble().temperature(): + self._map["temperature"] = ensemble.temperature() + self._omm_mols.set_temperature( + ensemble.temperature(), rescale_velocities=rescale_velocities + ) + + if ensemble.is_constant_pressure(): + if ensemble.pressure() != self.ensemble().pressure(): + self._map["pressure"] = ensemble.pressure() + self._omm_mols.set_pressure(ensemble.pressure()) + def constraint(self): if self.is_null(): return None @@ -206,6 +312,38 @@ def constraint(self): else: return "none" + def get_schedule(self): + if self.is_null(): + return None + else: + return self._omm_mols.get_lambda_schedule() + + def set_schedule(self, schedule): + if not self.is_null(): + self._omm_mols.set_lambda_schedule(schedule) + + def get_lambda(self): + if self.is_null(): + return None + else: + return self._omm_mols.get_lambda() + + def set_lambda(self, lambda_value: float): + if not self.is_null(): + s = self.get_schedule() + + if s is None: + return + + lambda_value = s.clamp(lambda_value) + + if lambda_value == self._omm_mols.get_lambda(): + # nothing to do + return + + self._omm_mols.set_lambda(lambda_value) + self._clear_state() + def info(self): if self.is_null(): return None @@ -293,18 +431,56 @@ def current_kinetic_energy(self): return nrg.value_in_unit(_omm_kcal_mol) * _sire_kcal_mol + def energy_trajectory(self): + return self._energy_trajectory.clone() + + def run_minimisation(self, max_iterations: int): + """ + Internal method that runs minimisation on the molecules. + + Parameters: + + - max_iterations (int): The maximum number of iterations to run + """ + from openmm import LocalEnergyMinimizer + from concurrent.futures import ThreadPoolExecutor + + if max_iterations <= 0: + max_iterations = 0 + + from ..base import ProgressBar + + def runfunc(max_its): + try: + LocalEnergyMinimizer.minimize( + self._omm_mols, maxIterations=max_its + ) + + return 0 + except Exception as e: + return e + + with ProgressBar(text="minimisation") as spinner: + spinner.set_speed_unit("checks / s") + + with ThreadPoolExecutor() as pool: + run_promise = pool.submit(runfunc, max_iterations) + + while not run_promise.done(): + try: + result = run_promise.result(timeout=0.2) + except Exception: + spinner.tick() + pass + + if result != 0: + raise result + def _rebuild_and_minimise(self): if self.is_null(): return - from concurrent.futures import ThreadPoolExecutor from ..utils import Console - import openmm - - def runfunc(max_its): - openmm.LocalEnergyMinimizer.minimize( - self._omm_mols, maxIterations=max_its - ) Console.warning( "Something went wrong when running dynamics. Since no steps " @@ -321,27 +497,31 @@ def runfunc(max_its): self._omm_mols = to(self._sire_mols, "openmm", map=self._map) - from ..base import ProgressBar - - with ProgressBar(text="minimisation") as spinner: - spinner.set_speed_unit("checks / s") - - with ThreadPoolExecutor() as pool: - run_promise = pool.submit(runfunc, 0) - - while not run_promise.done(): - try: - run_promise.result(timeout=0.2) - except Exception: - spinner.tick() - pass - - spinner.set_completed() + self.run_minimisation(max_iterations=10000) - def run(self, time, save_frequency, auto_fix_minimise: bool = True): + def run( + self, + time, + save_frequency=None, + frame_frequency=None, + energy_frequency=None, + lambda_windows=None, + save_velocities: bool = None, + auto_fix_minimise: bool = True, + ): if self.is_null(): return + orig_args = { + "time": time, + "save_frequency": save_frequency, + "frame_frequency": frame_frequency, + "energy_frequency": energy_frequency, + "lambda_windows": lambda_windows, + "save_velocities": save_velocities, + "auto_fix_minimise": auto_fix_minimise, + } + from concurrent.futures import ThreadPoolExecutor import openmm @@ -353,18 +533,68 @@ def run(self, time, save_frequency, auto_fix_minimise: bool = True): if save_frequency is not None: save_frequency = u(save_frequency) + if frame_frequency is not None: + frame_frequency = u(frame_frequency) + + if energy_frequency is not None: + energy_frequency = u(energy_frequency) + + if lambda_windows is not None: + if type(lambda_windows) is not list: + lambda_windows = [lambda_windows] + try: - steps = int(time.to(picosecond) / self.timestep().to(picosecond)) + steps_to_run = int( + time.to(picosecond) / self.timestep().to(picosecond) + ) except Exception: # passed in the number of steps instead - steps = int(time) + steps_to_run = int(time) - if steps < 1: - steps = 1 + if steps_to_run < 1: + steps_to_run = 1 - if steps <= 0: + if steps_to_run <= 0: return + # get the energy and frame save frequencies + if save_frequency != 0: + if save_frequency is None: + if self._map.specified("save_frequency"): + save_frequency = ( + self._map["save_frequency"].value().to(picosecond) + ) + else: + save_frequency = 25 + else: + save_frequency = save_frequency.to(picosecond) + + if energy_frequency != 0: + if energy_frequency is None: + if self._map.specified("energy_frequency"): + energy_frequency = ( + self._map["energy_frequency"].value().to(picosecond) + ) + else: + energy_frequency = save_frequency + else: + energy_frequency = energy_frequency.to(picosecond) + + if frame_frequency != 0: + if frame_frequency is None: + if self._map.specified("frame_frequency"): + frame_frequency = ( + self._map["frame_frequency"].value().to(picosecond) + ) + else: + frame_frequency = save_frequency + else: + frame_frequency = frame_frequency.to(picosecond) + + if lambda_windows is None: + if self._map.specified("lambda_windows"): + lambda_windows = self._map["lambda_windows"].value() + def runfunc(num_steps): try: integrator = self._omm_mols.getIntegrator() @@ -373,37 +603,73 @@ def runfunc(num_steps): except Exception as e: return e - def process_block(state, nsteps_completed): - self._update_from(state, nsteps_completed) - self._sire_mols.save_frame( - map={ - "space": self.current_space(), - "time": self.current_time(), - } - ) + def process_block(state, state_has_cv, nsteps_completed): + self._update_from(state, state_has_cv, nsteps_completed) + + if state_has_cv[0] or state_has_cv[1]: + self._sire_mols.save_frame( + map={ + "space": self.current_space(), + "time": self.current_time(), + } + ) completed = 0 - if save_frequency != 0: - if save_frequency is None: - if self._map.specified("save_frequency"): - save_frequency = ( - self._map["save_frequency"].value().to(picosecond) - ) - else: - save_frequency = 25 + frame_frequency_steps = int( + frame_frequency / self.timestep().to(picosecond) + ) + + energy_frequency_steps = int( + energy_frequency / self.timestep().to(picosecond) + ) + + def get_steps_till_save(completed: int, total: int): + """Internal function to calculate the number of steps + to run before the next save. This returns a tuple + of number of steps, and then if a frame should be + saved and if the energy should be saved + """ + if completed < 0: + completed = 0 + + if completed >= total: + return (0, True, True) + + elif frame_frequency_steps <= 0 and energy_frequency_steps <= 0: + return (total, True, True) + + n_to_end = total - completed + + if frame_frequency_steps > 0: + n_to_frame = min( + frame_frequency_steps + - (completed % frame_frequency_steps), + n_to_end, + ) else: - save_frequency = save_frequency.to(picosecond) + n_to_frame = total - completed - if save_frequency <= 0: - # don't save any intermediate frames - save_size = steps - else: - save_size = int(save_frequency / self.timestep().to(picosecond)) + if energy_frequency_steps > 0: + n_to_energy = min( + energy_frequency_steps + - (completed % energy_frequency_steps), + n_to_end, + ) + else: + n_to_energy = total - completed + + if n_to_frame == n_to_energy: + return (n_to_frame, True, True) + elif n_to_frame < n_to_energy: + return (n_to_frame, True, False) + else: + return (n_to_energy, False, True) block_size = 50 state = None + state_has_cv = (False, False) saved_last_frame = False class NeedsMinimiseError(Exception): @@ -412,25 +678,34 @@ class NeedsMinimiseError(Exception): nsteps_before_run = self._current_step from ..base import ProgressBar + from ..units import second + from datetime import datetime + from math import isnan try: - with ProgressBar(total=steps, text="dynamics") as progress: + with ProgressBar(total=steps_to_run, text="dynamics") as progress: progress.set_speed_unit("steps / s") + start_time = datetime.now() + with ThreadPoolExecutor() as pool: - while completed < steps: - if steps - completed < save_size: - nrun_till_save = steps - completed - else: - nrun_till_save = save_size + while completed < steps_to_run: + ( + nrun_till_save, + save_frame, + save_energy, + ) = get_steps_till_save(completed, steps_to_run) + + assert nrun_till_save > 0 - self._start_dynamics_block() + self._enter_dynamics_block() # process the last block in the foreground if state is not None: process_promise = pool.submit( process_block, state, + state_has_cv, nsteps_before_run + completed, ) else: @@ -477,7 +752,13 @@ class NeedsMinimiseError(Exception): saved_last_frame = True # get the state, including coordinates and velocities - state = self._end_dynamics_block(coords_and_vels=True) + state, state_has_cv = self._exit_dynamics_block( + save_frame=save_frame, + save_energy=save_energy, + lambda_windows=lambda_windows, + save_velocities=save_velocities, + ) + saved_last_frame = False kinetic_energy = ( @@ -488,7 +769,7 @@ class NeedsMinimiseError(Exception): ke_per_atom = kinetic_energy / self._num_atoms - if ke_per_atom > 1000: + if isnan(ke_per_atom) or ke_per_atom > 1000: # The system has blown up! state = None saved_last_frame = True @@ -505,22 +786,27 @@ class NeedsMinimiseError(Exception): "minimising the system and run again." ) + self._walltime += ( + datetime.now() - start_time + ).total_seconds() * second + if state is not None and not saved_last_frame: # we can process the last block in the main thread - process_block(state, nsteps_before_run + completed) + process_block( + state=state, + state_has_cv=state_has_cv, + nsteps_completed=nsteps_before_run + completed, + ) except NeedsMinimiseError: # try to fix this problem by minimising, # then running again - self._is_running = None + self._is_running = False self._omm_state = None - self._omm_state_has_cv = False + self._omm_state_has_cv = (False, False) self._rebuild_and_minimise() - self.run( - time=time, - save_frequency=save_frequency * picosecond, - auto_fix_minimise=False, - ) + orig_args["auto_fix_minimise"] = False + self.run(**orig_args) return def commit(self): @@ -528,7 +814,15 @@ def commit(self): return self._update_from( - self._get_current_state(coords_and_vels=True), self._current_step + state=self._get_current_state( + include_coords=True, include_velocities=True + ), + state_has_cv=(True, True), + nsteps_completed=self._current_step, + ) + + self._sire_mols.set_energy_trajectory( + self._energy_trajectory, map=self._map ) @@ -554,6 +848,13 @@ def __init__( timestep=None, save_frequency=None, constraint=None, + schedule=None, + lambda_value=None, + swap_end_states=None, + shift_delta=None, + coulomb_power=None, + restraints=None, + fixed=None, ): from ..base import create_map from .. import u @@ -562,9 +863,22 @@ def __init__( _add_extra(extras, "cutoff", cutoff) _add_extra(extras, "cutoff_type", cutoff_type) - _add_extra(extras, "timestep", u(timestep)) + + if timestep is not None: + _add_extra(extras, "timestep", u(timestep)) + _add_extra(extras, "save_frequency", save_frequency) _add_extra(extras, "constraint", constraint) + _add_extra(extras, "schedule", schedule) + _add_extra(extras, "lambda", lambda_value) + _add_extra(extras, "swap_end_states", swap_end_states) + + if shift_delta is not None: + _add_extra(extras, "shift_delta", u(shift_delta)) + + _add_extra(extras, "coulomb_power", coulomb_power) + _add_extra(extras, "restraints", restraints) + _add_extra(extras, "fixed", fixed) map = create_map(map, extras) @@ -584,7 +898,7 @@ def __str__(self): f"energy={self.current_energy()}, speed=FAST ns day-1)" ) else: - return f"Dynamics(completed=0)" + return "Dynamics(completed=0)" else: return ( f"Dynamics(completed={self.current_time()}, " @@ -594,21 +908,158 @@ def __str__(self): def __repr__(self): return self.__str__() - def run(self, time, save_frequency=None): + def minimise(self, max_iterations: int = 10000): """ - Perform dynamics on the molecules + Perform minimisation on the molecules, running a maximum + of max_iterations iterations. + + Parameters: + + - max_iterations (int): The maximum number of iterations to run """ if not self._d.is_null(): - self._d.run(time=time, save_frequency=save_frequency) + self._d.run_minimisation(max_iterations=max_iterations) return self + def run( + self, + time, + save_frequency=None, + frame_frequency=None, + energy_frequency=None, + lambda_windows=None, + save_velocities: bool = True, + auto_fix_minimise: bool = True, + ): + """ + Perform dynamics on the molecules. + + Parameters + + time: Time + The amount of simulation time to run, e.g. + dynamics.run(sr.u("5 ps")) would perform + 5 picoseconds of dynamics. The number of steps + is determined automatically based on the current + timestep (e.g. if the timestep was 1 femtosecond, + then 5 picoseconds would involve running 5000 steps) + + save_frequency: Time + The amount of simulation time between saving frames + (coordinates, velocities) and energies from the trajectory. The + number of timesteps between saves will depend on the + timestep. For example, if save_frequency was 0.1 picoseconds + and the timestep was 2 femtoseconds, then the coordinates + would be saved every 50 steps of dynamics. Note that + `frame_frequency` or `energy_frequency` can be used + to override the frequency of saving frames or energies, + if you want them to be saved with different frequencies. + Specifying both will mean that the value of + `save_frequency` will be ignored. + + frame_frequency: Time + The amount of simulation time between saving + frames (coordinates, velocities) from the trajectory. + The number of timesteps between saves will depend on the + timestep. For example, if save_frequency was 0.1 picoseconds + and the timestep was 2 femtoseconds, then the coordinates + would be saved every 50 steps of dynamics. The energies + will be saved into this object and are accessible via the + `energy_trajectory` function. + + energy_frequency: Time + The amount of simulation time between saving + energies (kinetic and potential) from the trajectory. + The number of timesteps between saves will depend on the + timestep. For example, if save_frequency was 0.1 picoseconds + and the timestep was 2 femtoseconds, then the coordinates + would be saved every 50 steps of dynamics. The energies + will be saved into this object and are accessible via the + `energy_trajectory` function. + + lambda_windows: list[float] + The values of lambda for which the potential energy will be + evaluated at every save. If this is None (the default) then + only the current energy will be saved every `energy_frequency` + time. If this is not None, then the potential energy for + each of the lambda values in this list will be saved. Note that + we always save the potential energy of the simulated lambda + value, even if it is not in the list of lambda windows. + + save_velocities: bool + Whether or not to save the velocities when running dynamics. + By default this is True. Set this to False if you aren't + interested in saving the velocities. + + auto_fix_minimise: bool + Whether or not to automatically run minimisation if the + trajectory exits with an error in the first few steps. + Such failures often indicate that the system needs + minimsing. This automatically runs the minimisation + in these cases, and then runs the requested dynamics. + """ + if not self._d.is_null(): + self._d.run( + time=time, + save_frequency=save_frequency, + frame_frequency=frame_frequency, + energy_frequency=energy_frequency, + lambda_windows=lambda_windows, + save_velocities=save_velocities, + auto_fix_minimise=auto_fix_minimise, + ) + + return self + + def get_schedule(self): + """ + Return the LambdaSchedule that shows how lambda changes the + underlying forcefield parameters in the system. + Returns None if this isn't a perturbable system. + """ + return self._d.get_schedule() + + def set_schedule(self, schedule): + """ + Set the LambdaSchedule that will be used to control how + lambda changes the underlying forcefield parameters + in the system. This does nothing if this isn't + a perturbable system + """ + self._d.set_schedule(schedule) + + def get_lambda(self): + """ + Return the current value of lambda for this system. This + does nothing if this isn't a perturbable system + """ + return self._d.get_lambda() + + def set_lambda(self, lambda_value: float): + """ + Set the current value of lambda for this system. This will + update the forcefield parameters in the context according + to the data in the LambdaSchedule. This does nothing if + this isn't a perturbable system + """ + self._d.set_lambda(lambda_value) + def ensemble(self): """ Return the ensemble in which the simulation is being performed """ return self._d.ensemble() + def set_ensemble(self, ensemble): + """ + Set the ensemble that should be used for this simulation. Note + that you can only use this function to change temperature and/or + pressure values. You can't change the fundemental ensemble + of the simulation. + """ + self._d.set_ensemble(ensemble) + def constraint(self): """ Return the constraint used for the dynamics (e.g. constraining @@ -715,11 +1166,37 @@ def current_energy(self): """ return self._d.current_energy() - def current_potential_energy(self): + def current_potential_energy(self, lambda_values=None): """ - Return the current potential energy + Return the current potential energy. + + If `lambda_values` is passed (which should be a list of + lambda values) then this will return the energies + (as a list) at the requested lambda values """ - return self._d.current_potential_energy() + if lambda_values is None: + return self._d.current_potential_energy() + else: + if not type(lambda_values) is list: + lambda_values = [lambda_values] + + # save the current value of lambda so we + # can restore it + old_lambda = self.get_lambda() + + nrgs = [] + + try: + for lambda_value in lambda_values: + self.set_lambda(lambda_value) + nrgs.append(self._d.current_potential_energy()) + except Exception: + self.set_lambda(old_lambda) + raise + + self.set_lambda(old_lambda) + + return nrgs def current_kinetic_energy(self): """ @@ -727,8 +1204,111 @@ def current_kinetic_energy(self): """ return self._d.current_kinetic_energy() + def energy_trajectory(self, to_pandas: bool = True): + """ + Return the energy trajectory. This is the trajectory of + energy values that have been captured during dynamics. + + If 'to_pandas' is True, (the default) then this will + be returned as a pandas dataframe, with times and energies + in the defined default units + """ + t = self._d.energy_trajectory() + + if to_pandas: + return t.to_pandas() + else: + return t + def commit(self): if not self._d.is_null(): self._d.commit() return self._d._sire_mols + + def __call__( + self, + time, + save_frequency=None, + frame_frequency=None, + energy_frequency=None, + lambda_windows=None, + save_velocities: bool = True, + auto_fix_minimise: bool = True, + ): + """ + Perform dynamics on the molecules. + + Parameters + + time: Time + The amount of simulation time to run, e.g. + dynamics.run(sr.u("5 ps")) would perform + 5 picoseconds of dynamics. The number of steps + is determined automatically based on the current + timestep (e.g. if the timestep was 1 femtosecond, + then 5 picoseconds would involve running 5000 steps) + + save_frequency: Time + The amount of simulation time between saving frames + (coordinates, velocities) and energies from the trajectory. The + number of timesteps between saves will depend on the + timestep. For example, if save_frequency was 0.1 picoseconds + and the timestep was 2 femtoseconds, then the coordinates + would be saved every 50 steps of dynamics. Note that + `frame_frequency` or `energy_frequency` can be used + to override the frequency of saving frames or energies, + if you want them to be saved with different frequencies. + Specifying both will mean that the value of + `save_frequency` will be ignored. + + frame_frequency: Time + The amount of simulation time between saving + frames (coordinates, velocities) from the trajectory. + The number of timesteps between saves will depend on the + timestep. For example, if save_frequency was 0.1 picoseconds + and the timestep was 2 femtoseconds, then the coordinates + would be saved every 50 steps of dynamics. The energies + will be saved into this object and are accessible via the + `energy_trajectory` function. + + energy_frequency: Time + The amount of simulation time between saving + energies (kinetic and potential) from the trajectory. + The number of timesteps between saves will depend on the + timestep. For example, if save_frequency was 0.1 picoseconds + and the timestep was 2 femtoseconds, then the coordinates + would be saved every 50 steps of dynamics. The energies + will be saved into this object and are accessible via the + `energy_trajectory` function. + + lambda_windows: list[float] + The values of lambda for which the potential energy will be + evaluated at every save. If this is None (the default) then + only the current energy will be saved every `energy_frequency` + time. If this is not None, then the potential energy for + each of the lambda values in this list will be saved. Note that + we always save the potential energy of the simulated lambda + value, even if it is not in the list of lambda windows. + + save_velocities: bool + Whether or not to save the velocities when running dynamics. + By default this is True. Set this to False if you aren't + interested in saving the velocities. + + auto_fix_minimise: bool + Whether or not to automatically run minimisation if the + trajectory exits with an error in the first few steps. + Such failures often indicate that the system needs + minimsing. This automatically runs the minimisation + in these cases, and then runs the requested dynamics. + """ + return self.run( + time=time, + save_frequency=save_frequency, + frame_frequency=frame_frequency, + energy_frequency=energy_frequency, + lambda_windows=lambda_windows, + save_velocities=save_velocities, + auto_fix_minimise=auto_fix_minimise, + ).commit() diff --git a/src/sire/mol/_minimisation.py b/src/sire/mol/_minimisation.py index ab50bb334..2d7ae2eca 100644 --- a/src/sire/mol/_minimisation.py +++ b/src/sire/mol/_minimisation.py @@ -1,73 +1,6 @@ __all__ = ["Minimisation"] -class MinimisationData: - """ - Internal class that is designed to only be used by the Minimisation - class. This holds the shared state for minimisation on a set - of molecule(s). - """ - - def __init__(self, mols=None, map=None): - if mols is not None: - # eventually want to call 'extract' on this? - self._sire_mols = mols - - from ..base import create_map - - self._map = create_map(map) - - from ..convert import to - - self._omm_mols = to(self._sire_mols, "openmm", map=self._map) - - else: - self._sire_mols = None - self._map = None - self._omm_mols = None - - def is_null(self): - return self._sire_mols is None - - def run(self, max_iterations: int): - from openmm import LocalEnergyMinimizer - from concurrent.futures import ThreadPoolExecutor - - if max_iterations <= 0: - max_iterations = 0 - - from ..base import ProgressBar - - def runfunc(max_its): - LocalEnergyMinimizer.minimize( - self._omm_mols, maxIterations=max_its - ) - - with ProgressBar(text="minimisation") as spinner: - spinner.set_speed_unit("checks / s") - - with ThreadPoolExecutor() as pool: - run_promise = pool.submit(runfunc, max_iterations) - - while not run_promise.done(): - try: - run_promise.result(timeout=0.2) - except Exception: - spinner.tick() - pass - - def commit(self): - from ..legacy.Convert import openmm_extract_coordinates - - state = self._omm_mols.getState(getPositions=True) - - mols = openmm_extract_coordinates( - state, self._sire_mols.molecules(), self._map - ) - - self._sire_mols.update(mols.to_molecules()) - - class Minimisation: """ Class that runs minimisation on the contained molecule(s). Note that @@ -76,27 +9,76 @@ class Minimisation: you want to minimise """ - def __init__(self, mols=None, map=None): - self._d = MinimisationData(mols=mols, map=map) + def __init__( + self, + mols=None, + map=None, + cutoff=None, + cutoff_type=None, + schedule=None, + lambda_value=None, + swap_end_states=None, + shift_delta=None, + coulomb_power=None, + restraints=None, + fixed=None, + ): + from ..base import create_map + from ._dynamics import DynamicsData, _add_extra + + extras = {} + + _add_extra(extras, "cutoff", cutoff) + _add_extra(extras, "cutoff_type", cutoff_type) + _add_extra(extras, "schedule", schedule) + _add_extra(extras, "lambda", lambda_value) + _add_extra(extras, "swap_end_states", swap_end_states) + _add_extra(extras, "shift_delta", shift_delta) + _add_extra(extras, "coulomb_power", coulomb_power) + _add_extra(extras, "restraints", restraints) + _add_extra(extras, "fixed", fixed) + + map = create_map(map, extras) + + self._d = DynamicsData(mols=mols, map=map) def __str__(self): - return f"Minimisation()" + return "Minimisation()" def __repr__(self): return self.__str__() - def run(self, max_iterations: int = 1000): + def run(self, max_iterations: int = 10000): """ Perform minimisation on the molecules, running a maximum of max_iterations iterations. + + Parameters: + + - max_iterations (int): The maximum number of iterations to run """ if not self._d.is_null(): - self._d.run(max_iterations=max_iterations) + self._d.run_minimisation(max_iterations=max_iterations) return self def commit(self): + """ + Commit the minimisation to the molecules, returning the + minimised molecules. + """ if not self._d.is_null(): self._d.commit() return self._d._sire_mols + + def __call__(self, max_iterations: int = 10000): + """ + Perform minimisation on the molecules, running a maximum + of max_iterations iterations. + + Parameters: + + - max_iterations (int): The maximum number of iterations to run + """ + return self.run(max_iterations=max_iterations).commit() diff --git a/src/sire/morph/CMakeLists.txt b/src/sire/morph/CMakeLists.txt new file mode 100644 index 000000000..ea5a9f23e --- /dev/null +++ b/src/sire/morph/CMakeLists.txt @@ -0,0 +1,16 @@ +######################################## +# +# sire.morph +# +######################################## + +# Add your script to this list +set ( SCRIPTS + __init__.py + _ghost_atoms.py + _perturbation.py + _repex.py + ) + +# installation +install( FILES ${SCRIPTS} DESTINATION ${SIRE_PYTHON}/sire/morph ) diff --git a/src/sire/morph/__init__.py b/src/sire/morph/__init__.py new file mode 100644 index 000000000..f39200ac5 --- /dev/null +++ b/src/sire/morph/__init__.py @@ -0,0 +1,7 @@ +__all__ = ["shrink_ghost_atoms", "replica_exchange", "Perturbation"] + +from ._perturbation import * + +from ._ghost_atoms import * + +from ._repex import * diff --git a/src/sire/morph/_ghost_atoms.py b/src/sire/morph/_ghost_atoms.py new file mode 100644 index 000000000..4a71728ab --- /dev/null +++ b/src/sire/morph/_ghost_atoms.py @@ -0,0 +1,110 @@ +__all__ = ["shrink_ghost_atoms"] + + +def shrink_ghost_atoms(mols, length=None, map=None): + """ + Update all of the molecules (or single molecule) in 'mols' + so that the bond lengths involving ghost atoms at one or other + end state (but not both) are shrunk to `length`. This is + 0.6 A by default (this seems to be a value that causes + fewest NaNs). + """ + from ..base import create_map + from ..cas import Symbol + from ..mm import AmberBond + + if length is None: + length = 0.6 + else: + from ..units import angstrom, u + + length = u(length).to(angstrom) + + map = create_map(map) + + perturbable_mols = mols.molecules("property is_perturbable") + + props = [ + "LJ", + "bond", + "charge", + ] + + map0 = map.add_suffix("0", props) + map1 = map.add_suffix("1", props) + + # identify all of the ghost atoms + from_ghosts = [] + to_ghosts = [] + + for mol in perturbable_mols: + for atom in mol.atoms(): + chg0 = atom.property(map0["charge"]) + chg1 = atom.property(map1["charge"]) + + lj0 = atom.property(map0["LJ0"]) + lj1 = atom.property(map1["LJ1"]) + + is_ghost0 = chg0.is_zero() and lj0.is_dummy() + is_ghost1 = chg1.is_zero() and lj1.is_dummy() + + if is_ghost0 and not is_ghost1: + from_ghosts.append(atom.index()) + elif is_ghost1: + to_ghosts.append(atom.index()) + + r = Symbol("r") + + bonds0 = mol.property(map0["bond"]) + bonds1 = mol.property(map1["bond"]) + + changed0 = False + changed1 = False + + for bond in mol.bonds(): + # are either of the atoms in the bond in from_ghosts or to_ghosts + is_from_ghost = ( + bond[0].index() in from_ghosts + or bond[1].index() in from_ghosts + ) + + is_to_ghost = ( + bond[0].index() in to_ghosts or bond[1].index() in to_ghosts + ) + + if is_from_ghost and not is_to_ghost: + # this is a bond that is turning into a ghost + r0_0 = length + pot0 = AmberBond(bond.potential(map0), r) + + if r0_0 < pot0.r0(): + # only change it if the bond is not already shorter + bonds0.set( + bond.id(), + AmberBond(pot0.k(), r0_0).to_expression(r), + ) + changed0 = True + elif is_to_ghost: + r0_1 = length + pot1 = AmberBond(bond.potential(map1), r) + + if r0_1 < pot1.r0(): + # only change it if the bond is not already shorter + bonds1.set( + bond.id(), + AmberBond(pot1.k(), r0_1).to_expression(r), + ) + changed1 = True + + if changed0: + mol = ( + mol.edit().set_property(map0["bond"].source(), bonds0).commit() + ) + + if changed1: + mol = ( + mol.edit().set_property(map1["bond"].source(), bonds1).commit() + ) + + if changed0 or changed1: + mols.update(mol) diff --git a/src/sire/morph/_perturbation.py b/src/sire/morph/_perturbation.py new file mode 100644 index 000000000..bfe3ec765 --- /dev/null +++ b/src/sire/morph/_perturbation.py @@ -0,0 +1,295 @@ +__all__ = ["Perturbation"] + + +class Perturbation: + """ + This class provides lots of convenience functions that make it + easier to work with an visualise perturbations + """ + + def __init__(self, mol, map=None): + """ + Construct the Perturbation object from the passed molecule. + Note that this molecule must be perturbable + """ + from ..base import create_map + + map = create_map(map) + + if not mol.has_property(map["is_perturbable"]): + raise ValueError( + "You can only create a `Perturbation` from a " + "perturbable molecule!" + ) + + if not mol.property(map["is_perturbable"]): + raise ValueError( + "You can only create a `Perturbation` from a " + "perturbable molecule!" + ) + + # construct the perturbation objects that can move the + # coordinates between the end states + from ..legacy.Mol import ( + BondPerturbation, + AnglePerturbation, + GeometryPerturbations, + ) + + from ..legacy.MM import AmberBond, AmberAngle + from ..cas import Symbol + from ..units import angstrom, radian + + self._perturbations = GeometryPerturbations() + + props = [ + "LJ", + "ambertype", + "angle", + "atomtype", + "bond", + "charge", + "coordinates", + "dihedral", + "element", + "forcefield", + "gb_radii", + "gb_screening", + "improper", + "intrascale", + "mass", + "name", + "parameters", + "treechain", + ] + + self._map0 = map.add_suffix("0", props) + self._map1 = map.add_suffix("1", props) + + # identify all of the ghost atoms + from_ghosts = [] + to_ghosts = [] + + for atom in mol.atoms(): + chg0 = atom.property(self._map0["charge"]) + chg1 = atom.property(self._map1["charge"]) + + lj0 = atom.property(self._map0["LJ0"]) + lj1 = atom.property(self._map1["LJ1"]) + + is_ghost0 = chg0.is_zero() and lj0.is_dummy() + is_ghost1 = chg1.is_zero() and lj1.is_dummy() + + if is_ghost0 and not is_ghost1: + from_ghosts.append(atom.index()) + elif is_ghost1: + to_ghosts.append(atom.index()) + + r = Symbol("r") + theta = Symbol("theta") + + connectivity = mol.property(self._map0["connectivity"]) + + for angle in mol.angles(): + # get the bond lengths desired by the forcefield at the + # two end states + in_ring0 = connectivity.in_ring(angle.atom0().index()) + in_ring1 = connectivity.in_ring(angle.atom1().index()) + in_ring2 = connectivity.in_ring(angle.atom2().index()) + + if not (in_ring0 and in_ring1 and in_ring2): + pot0 = AmberAngle(angle.potential(self._map0), theta) + pot1 = AmberAngle(angle.potential(self._map1), theta) + + theta0_0 = pot0.theta0() + theta0_1 = pot1.theta0() + + self._perturbations.append( + AnglePerturbation( + angle=angle.id(), + start=theta0_0 * radian, + end=theta0_1 * radian, + map=map, + ) + ) + + for bond in mol.bonds(): + # get the bond lengths desired by the forcefield at the + # two end states + pot0 = AmberBond(bond.potential(self._map0), r) + pot1 = AmberBond(bond.potential(self._map1), r) + + r0_0 = pot0.r0() + r0_1 = pot1.r0() + + self._perturbations.append( + BondPerturbation( + bond=bond.id(), + start=r0_0 * angstrom, + end=r0_1 * angstrom, + map=map, + ) + ) + + self._mol = mol.clone() + + def __str__(self): + return f"Perturbation( {self._mol} )" + + def __repr__(self): + return self.__str__() + + def link_to_reference(self, properties: list[str] = None): + """ + Link all of the properties of the molecule to their values in the + reference molecule (lambda=0). + + If a list of properties is passed then only those properties will + be linked to the reference molecule + + Parameters + ---------- + + properties: list[str] + The list of properties to link to the reference molecule + """ + if properties is None: + properties = [] + + for key in self._mol.property_keys(): + if key.endswith("0"): + properties.append(key[:-1]) + + elif type(properties) is str: + properties = [properties] + + mol = self._mol.molecule().edit() + + for key in properties: + if mol.has_property(key): + mol.remove_property(key) + + mol.add_link(key, f"{key}0") + + self._mol.update(mol.commit()) + return self + + def link_to_perturbed(self, properties: list[str] = None): + """ + Link all of the properties of the molecule to their values in the + perturbed molecule (lambda=1). + + If a list of properties is passed then only those properties will + be linked to the perturbed molecule + + Parameters + ---------- + + properties: list[str] + The list of properties to link to the perturbed molecule + """ + if properties is None: + properties = [] + + for key in self._mol.property_keys(): + if key.endswith("1"): + properties.append(key[:-1]) + + elif type(properties) is str: + properties = [properties] + + mol = self._mol.molecule().edit() + + for key in properties: + if mol.has_property(key): + mol.remove_property(key) + + mol.add_link(key, f"{key}1") + + self._mol.update(mol.commit()) + return self + + def set_lambda(self, lam_val: float): + """ + Set the lambda value to the passed value + """ + from ..cas import Symbol + from ..base import create_map + from ..legacy.CAS import Values + + mol = self._mol.molecule() + + map = create_map({}) + + if mol.is_link(map["coordinates"]): + # make sure we remove any link to the 'coordinates' property + # as we will be replacing it with the calculated perturbed + # coordinates + mol = mol.edit().remove_link(map["coordinates"].source()).commit() + + if not mol.has_property(map["coordinates"]): + mol = ( + mol.edit() + .set_property( + map["coordinates"].source(), + mol.property(self._map0["coordinates"]), + ) + .commit() + ) + + vals = Values({Symbol("lambda"): lam_val}) + self._mol.update(self._perturbations.perturb(mol, vals)) + return self + + def commit(self): + """ + Return the modified molecule + """ + return self._mol.clone() + + def view(self, *args, **kwargs): + """ + View the perturbation + """ + from ..cas import Symbol + from ..base import create_map + from ..legacy.CAS import Values + + map = create_map({}) + + mol = self._mol.clone() + mol.delete_all_frames(map=map) + + if mol.is_link(map["coordinates"]): + # make sure we remove any link to the 'coordinates' property + # as we will be replacing it with the calculated perturbed + # coordinates + mol.update( + mol.molecule() + .edit() + .remove_link(map["coordinates"].source()) + .commit() + ) + + if not mol.has_property(map["coordinates"]): + mol.update( + mol.molecule() + .edit() + .set_property( + map["coordinates"].source(), + mol.property(self._map0["coordinates"]), + ) + .commit() + ) + + for lam in range(0, 11): + vals = Values({Symbol("lambda"): 0.1 * lam}) + mol = self._perturbations.perturb(mol, vals) + mol.save_frame() + + for lam in range(9, 0, -1): + vals = Values({Symbol("lambda"): 0.1 * lam}) + mol = self._perturbations.perturb(mol, vals) + mol.save_frame() + + return mol.view(*args, **kwargs) diff --git a/src/sire/morph/_repex.py b/src/sire/morph/_repex.py new file mode 100644 index 000000000..ffd328063 --- /dev/null +++ b/src/sire/morph/_repex.py @@ -0,0 +1,130 @@ +__all__ = ["replica_exchange"] + + +_global_generator = None + + +def _get_global_generator(): + """ + Return the global random number generator for replica exchange moves + """ + global _global_generator + + if _global_generator is None: + from ..maths import RanGenerator + + _global_generator = RanGenerator() + + return _global_generator + + +def replica_exchange( + replica0, replica1, rangenerator=None, rescale_velocities: bool = True +): + """ + Perform a replica exchange move between the passed two replicas. + This will be a Hamiltonian Replica Exchange, where the energies + at the two λ-values of the replicas will be evaluated for both, + and a Monte Carlo test applied to the difference of the difference + of those energies to decide if the exchange should be accepted. + + Parameters + ---------- + + replica0 : sire.mol.Dynamics + The dynamics object holding the first replica to exchange + + replica1 : sire.mol.Dynamics + The dynamics object holding the second replica to exchange + + rangenerator : sire.maths.RanGenerator, optional + A random number generator to use for the Monte Carlo test. This + should return uniformly distributed random numbers between 0 and 1. + If this is not specified, then the global random number + generator will be used. + + rescale_velocities : bool, optional + If True, then the velocities of the replicas will be rescaled + to the new temperature after the exchange. + + Returns + ------- + + (replica0, replica1, bool) + The replicas, either swapped if the move was accepted, + or in the same order if the move was rejected. Plus, + a boolean to say if the move was or was not accepted. + + Examples + -------- + + >>> from sire.morph import replica_exchange + >>> replica0 = mols.dynamics(lambda_value=0.0, ...) + >>> replica1 = mols.dynamics(lambda_value=0.2, ...) + >>> replica0, replica1, accepted = replica_exchange(replica0, replica1) + """ + lam0 = replica0.get_lambda() + lam1 = replica1.get_lambda() + + ensemble0 = replica0.ensemble() + ensemble1 = replica1.ensemble() + + if ensemble0.name() != ensemble1.name(): + raise ValueError( + "You cannot attempt a replica exchange move between repliacas " + f"that have different ensembles ({ensemble0.name()} and " + f"{ensemble1.name()})" + ) + + temperature0 = ensemble0.temperature() + temperature1 = ensemble1.temperature() + + # Get the energies of both replicas at both lambda values + nrgs0 = replica0.current_potential_energy(lambda_values=[lam0, lam1]) + nrgs1 = replica1.current_potential_energy(lambda_values=[lam0, lam1]) + + # now calculate delta needed for the Monte Carlo test + # delta = beta_b * [ H_b_i - H_b_j + P_b (V_b_i - V_b_j) ] + + # beta_a * [ H_a_i - H_a_j + P_a (V_a_i - V_a_j) ] + + from ..units import k_boltz + + beta0 = 1.0 / (k_boltz * temperature0) + beta1 = 1.0 / (k_boltz * temperature1) + + if not ensemble0.is_constant_pressure(): + delta = beta1 * (nrgs1[0] - nrgs1[1]) + beta0 * (nrgs0[0] - nrgs0[1]) + else: + volume0 = replica0.current_space().volume() + volume1 = replica1.current_space().volume() + + pressure0 = ensemble0.pressure() + pressure1 = ensemble1.pressure() + + delta = beta1 * ( + nrgs1[0] - nrgs1[1] + pressure1 * (volume1 - volume0) + ) + beta0 * (nrgs0[0] - nrgs0[1] + pressure0 * (volume0 - volume1)) + + from math import exp + + if rangenerator is None: + rangenerator = _get_global_generator() + + move_passed = delta > 0 or (exp(delta) >= rangenerator.rand()) + + if move_passed: + if lam0 != lam1: + replica0.set_lambda(lam1) + replica1.set_lambda(lam0) + + if ensemble0 != ensemble1: + replica0.set_ensemble( + ensemble1, rescale_velocities=rescale_velocities + ) + replica1.set_ensemble( + ensemble0, rescale_velocities=rescale_velocities + ) + + return (replica1, replica0, True) + else: + return (replica0, replica1, False) diff --git a/src/sire/restraints/CMakeLists.txt b/src/sire/restraints/CMakeLists.txt new file mode 100644 index 000000000..6411a8319 --- /dev/null +++ b/src/sire/restraints/CMakeLists.txt @@ -0,0 +1,14 @@ +######################################## +# +# sire.restraints +# +######################################## + +# Add your script to this list +set ( SCRIPTS + __init__.py + _restraints.py + ) + +# installation +install( FILES ${SCRIPTS} DESTINATION ${SIRE_PYTHON}/sire/restraints ) diff --git a/src/sire/restraints/__init__.py b/src/sire/restraints/__init__.py new file mode 100644 index 000000000..dfc8a3efb --- /dev/null +++ b/src/sire/restraints/__init__.py @@ -0,0 +1,3 @@ +__all__ = ["positional", "bond", "distance", "boresch"] + +from ._restraints import * diff --git a/src/sire/restraints/_restraints.py b/src/sire/restraints/_restraints.py new file mode 100644 index 000000000..8689e8b7f --- /dev/null +++ b/src/sire/restraints/_restraints.py @@ -0,0 +1,345 @@ +__all__ = [ + "boresch", + "bond", + "distance", + "positional", +] + + +def _to_atoms(mols, atoms): + """ + Internal function used to convert `mols[atoms]` into a list + of atoms + """ + from ..mol import selection_to_atoms + + return selection_to_atoms(mols, atoms) + + +def boresch( + mols, + receptor, + ligand, + kr=None, + ktheta=None, + kphi=None, + name: str = None, + map=None, +): + """ + Create a set of Boresch restraints that will hold the passed + ligand in a its relative binding mode relative to the + passed receptor. All of the atoms in both 'ligand' and + 'receptor' must be contained in 'mols'. + + The BoreschRestraint will be a set of six restraints between + three identified ligand atoms, and three identified receptor + atoms. + + 1. A single distance restraint, with specified kr and r0 parameters + 2. Two angle restraints, with the specified two ktheta and theta0 + parameters + 3. Three torsion restraints, with the specified three kphi and phi0 + parameters + + This will create a single BoreschRestraint, which will be passed + back in a BoreschRestraints object. + + If the force constants (kr, ktheta and kphi) are None, then they + will have default values of 150 kcal mol-1 A-2 and + 150 kcal mol-1 rad-2 + + The equilibium distances and angles are based on the current coordinates + of that atoms + """ + from .. import u + from ..base import create_map + from ..mm import BoreschRestraint, BoreschRestraints + + map = create_map(map) + + receptor = _to_atoms(mols, receptor) + ligand = _to_atoms(mols, ligand) + + if len(receptor) != 3 or len(ligand) != 3: + # Eventually will choose the best atoms from the receptor + # and ligand... + raise ValueError( + "You need to provide 3 receptor atoms and 3 ligand atoms" + ) + + if kr is None: + kr = u("150 kcal mol-1 A-2") + else: + kr = u(kr) + + _default_k = u("150 kcal mol-1 rad-2") + + if ktheta is None: + ktheta = [_default_k, _default_k] + elif type(ktheta) is not list: + ktheta = 2 * [u(ktheta)] + else: + if len(ktheta) == 0: + ktheta = [_default_k, _default_k] + if len(ktheta) < 2: + ktheta = 2 * [u(ktheta[0])] + else: + ktheta = [u(x) for x in ktheta[0:2]] + + if kphi is None: + kphi = [_default_k, _default_k, _default_k] + elif type(kphi) is not list: + kphi = 3 * [u(kphi)] + else: + if len(kphi) == 0: + kphi = [_default_k, _default_k, _default_k] + elif len(kphi) == 1: + kphi = 3 * [u(kphi[0])] + elif len(kphi) == 2: + kphi = [u(kphi[0]), u(kphi[1]), u(kphi[1])] + else: + kphi = [u(x) for x in kphi[0:3]] + + # r is | Ligand1 - Receptor1 | = distance(P1, P4) + # thetaA = angle(R2, R1, L1) = angle(P2, P1, P4) + # thetaB = angle(R1, L1, L2) = angle(P1, P4, P5) + # phiA = dihedral(R3, R2, R1, L1) = dihedral(P3, P2, P1, P4) + # phiB = dihedral(R2, R1, L1, L2) = dihedral(P2, P1, P4, P5) + # phiC = dihedral(R1, L1, L2, L3) = dihedral(P1, P4, P5, P6) + from .. import measure + + r0 = measure(ligand[0], receptor[0]) + theta0 = [ + measure(receptor[1], receptor[0], ligand[0]), + measure(receptor[0], ligand[0], ligand[1]), + ] + phi0 = [ + measure(receptor[2], receptor[1], receptor[0], ligand[0]), + measure(receptor[1], receptor[0], ligand[0], ligand[1]), + measure(receptor[0], ligand[0], ligand[1], ligand[2]), + ] + + mols = mols.atoms() + + b = BoreschRestraint( + receptor=mols.find(receptor), + ligand=mols.find(ligand), + r0=r0, + theta0=theta0, + phi0=phi0, + kr=kr, + ktheta=ktheta, + kphi=kphi, + ) + + if name is None: + return BoreschRestraints(b) + else: + return BoreschRestraint(name, b) + + +def distance( + mols, atoms0, atoms1, r0=None, k=None, name: str = None, map=None +): + """ + Create a set of distance restraints from all of the atoms in 'atoms0' + to all of the atoms in 'atoms1' where all atoms are + contained in the container 'mols', using the + passed values of the force constant 'k' and equilibrium + bond length r0. + + These restraints will be per atom-atom distance. If a list of k and/or r0 + values are passed, then different values could be used for + different atom-atom distances (assuming the same number as the number of + atom-atom distances). Otherwise, all atom-atom distances will use the + same parameters. + + If r0 is None, then the current atom-atom distance for + each atom-atom pair will be used as the equilibium value. + + If k is None, then a default value of 150 kcal mol-1 A-2 will be used + """ + from .. import u + from ..base import create_map + from ..mm import BondRestraint, BondRestraints + + map = create_map(map) + + if k is None: + k = [u("150 kcal mol-1 A-2")] + elif type(k) is list: + k = [u(x) for x in k] + else: + k = [u(k)] + + atoms0 = _to_atoms(mols, atoms0) + atoms1 = _to_atoms(mols, atoms1) + + if atoms0.is_empty() or atoms1.is_empty(): + raise ValueError("We need at least one atom in each group") + + while len(atoms0) < len(atoms1): + atoms0 += atoms0[-1] + + while len(atoms1) < len(atoms0): + atoms1 += atoms1[-1] + + if r0 is None: + # calculate all of the current distances + from .. import measure + + r0 = [] + for atom0, atom1 in zip(atoms0, atoms1): + r0.append(measure(atom0, atom1)) + elif type(r0) is list: + r0 = [u(x) for x in r0] + else: + r0 = [u(r0)] + + mols = mols.atoms() + + if name is None: + restraints = BondRestraints() + else: + restraints = BondRestraints(name=name) + + for i, (atom0, atom1) in enumerate(zip(atoms0, atoms1)): + idxs0 = mols.find(atom0) + idxs1 = mols.find(atom1) + + if type(idxs0) is int: + idxs0 = [idxs0] + + if type(idxs1) is int: + idxs1 = [idxs1] + + if len(idxs0) == 0: + raise KeyError( + f"Could not find atom {atom0} in the molecules. Please ensure " + "that 'mols' contains all of that atoms, or else we can't " + "add the positional restraints." + ) + + if len(idxs1) == 0: + raise KeyError( + f"Could not find atom {atom1} in the molecules. Please ensure " + "that 'mols' contains all of that atoms, or else we can't " + "add the positional restraints." + ) + + if i < len(k): + ik = k[i] + else: + ik = k[-1] + + if i < len(r0): + ir0 = r0[i] + else: + ir0 = r0[-1] + + restraints.add(BondRestraint(idxs0[0], idxs1[0], ik, ir0)) + + return restraints + + +def bond(*args, **kwargs): + """ + Synonym for distance(), as a bond restraint is treated the same + as a distance restraint + """ + return distance(*args, **kwargs) + + +def positional( + mols, atoms, k=None, r0=None, position=None, name: str = None, map=None +): + """ + Create a set of position restraints for the atoms specified in + 'atoms' that are contained in the container 'mols', using the + passed values of the force constant 'k' and flat-bottom potential + well-width 'r0' for the restraints. + + These restraints will be per atom. If a list of k and/or r0 + values are passed, then different values could be used for + different atoms (assuming the same number as the number of + atoms). Otherwise, all atoms will use the same parameters. + + If 'r0' is not specified, then a simple harmonic restraint + is used. + + If 'k' is not specified, then a default of 150 kcal mol-1 A-2 + will be used. + """ + from .. import u + from ..base import create_map + from ..mm import PositionalRestraint, PositionalRestraints + + map = create_map(map) + + if k is None: + k = [u("150 kcal mol-1 A-2")] + elif type(k) is list: + k = [u(x) for x in k] + else: + k = [u(k)] + + if r0 is None: + r0 = [u("0")] + elif type(r0) is list: + r0 = [u(x) for x in r0] + else: + r0 = [u(r0)] + + atoms = _to_atoms(mols, atoms) + + mols = mols.atoms() + + if name is None: + restraints = PositionalRestraints() + else: + restraints = PositionalRestraints(name=name) + + coords_prop = map["coordinates"] + + if position is not None: + from ..maths import Vector + + if type(position) is not list: + position = len(atoms) * [Vector.to_vector(position)] + else: + position = [Vector.to_vector(x) for x in position] + + for i, atom in enumerate(atoms.atoms()): + idxs = mols.find(atom) + + if type(idxs) is int: + idxs = [idxs] + + elif len(idxs) == 0: + raise KeyError( + f"Could not find atom {atom} in the molecules. Please ensure " + "that 'mols' contains all of that atoms, or else we can't " + "add the positional restraints." + ) + + if i < len(k): + ik = k[i] + else: + ik = k[-1] + + if i < len(r0): + ir0 = r0[i] + else: + ir0 = r0[-1] + + if position is None: + restraints.add( + PositionalRestraint( + idxs[0], atom.property(coords_prop), ik, ir0 + ) + ) + else: + restraints.add(PositionalRestraint(idxs[0], position[i], ik, ir0)) + + return restraints diff --git a/src/sire/system/_system.py b/src/sire/system/_system.py index 679bfe19a..56836417c 100644 --- a/src/sire/system/_system.py +++ b/src/sire/system/_system.py @@ -357,19 +357,182 @@ def trajectory(self, *args, **kwargs): return TrajectoryIterator(self, *args, **kwargs) - def minimisation(self, map=None): - """ - Return a Minimisation object that can be used to minimise the energy - of the molecule(s) in this view. - """ - from ..mol import Minimisation - - return Minimisation(self, map=map) + def minimisation(self, *args, **kwargs): + """ + Return a Minimisation object that can be used to perform + minimisation of the molecule(s) in this System + + cutoff: Length + The size of the non-bonded cutoff + + cutoff_type: str + The type of cutoff to use, e.g. "PME", "RF" etc. + See https://sire.openbiosim.org/cheatsheet/openmm.html#choosing-options + for the full list of options + + constraint: str + The type of constraint to use for bonds and/or angles, e.g. + `h-bonds`, `bonds` etc. + See https://sire.openbiosim.org/cheatsheet/openmm.html#choosing-options + for the full list of options. This will be automatically + guessed from the timestep if it isn't set. + + schedule: sire.cas.LambdaSchedule + The schedule used to control how perturbable forcefield parameters + should be morphed as a function of lambda. If this is not set + then a sire.cas.LambdaSchedule.standard_morph() is used. + + lambda_value: float + The value of lambda at which to run minimisation. This only impacts + perturbable molecules, whose forcefield parameters will be + scaled according to the lambda schedule for the specified + value of lambda. + + swap_end_states: bool + Whether or not to swap the end states. If this is True, then + the perturbation will run from the perturbed back to the + reference molecule (the perturbed molecule will be at lambda=0, + while the reference molecule will be at lambda=1). This will + use the coordinates of the perturbed molecule as the + starting point. + + shift_delta: length + The shift_delta parameter that controls the electrostatic + and van der Waals softening potential that smooths the + creation and deletion of ghost atoms during a potential. + This defaults to 2.0 A. + + coulomb_power: int + The coulomb power parmeter that controls the electrostatic + softening potential that smooths the creation and deletion + of ghost atoms during a potential. This defaults to 0. + + restraints: sire.mm.Restraints or list[sire.mm.Restraints] + A single set of restraints, or a list of sets of + restraints that will be applied to the atoms during + the simulation. + + fixed: molecule(s) view, search string, int, list[int] etc + Anything that can be used to identify the atom or atoms + that should be fixed in place during the simulation. These + atoms will not be moved by minimisation. + + device: str or int + The ID of the GPU (or accelerator) used to accelerate + minimisation. This would be CUDA_DEVICE_ID or similar + if CUDA was used. This can be any valid OpenMM device string + + map: dict + A dictionary of additional options. Note that any options + set in this dictionary that are also specified via one of + the arguments above will be overridden by the argument + value + """ + from ..mol import _minimisation + + return _minimisation(self, *args, **kwargs) def dynamics(self, *args, **kwargs): """ Return a Dynamics object that can be used to perform - dynamics of the molecule(s) in this view + dynamics on the molecule(s) in this System + + cutoff: Length + The size of the non-bonded cutoff + + cutoff_type: str + The type of cutoff to use, e.g. "PME", "RF" etc. + See https://sire.openbiosim.org/cheatsheet/openmm.html#choosing-options + for the full list of options + + timestep: time + The size of the dynamics timestep + + save_frequency: time + The amount of simulation time between saving energies and frames. + This can be overridden using `energy_frequency` or `frame_frequency`, + or by these options in individual dynamics runs. Set this + to zero if you don't want any saves. + + energy_frequency: time + The amount of time between saving energies. This overrides the + value in `save_frequency`. Set this to zero if you don't want + to save energies during the trajectory. This can be overridden + by setting energy_frequency during an individual run. + + frame_frequency: time + The amount of time between saving frames. This overrides the + value in `save_frequency`. Set this to zero if you don't want + to save frames during the trajectory. This can be overridden + by setting frame_frequency during an individual run. + + constraint: str + The type of constraint to use for bonds and/or angles, e.g. + `h-bonds`, `bonds` etc. + See https://sire.openbiosim.org/cheatsheet/openmm.html#choosing-options + for the full list of options. This will be automatically + guessed from the timestep if it isn't set. + + schedule: sire.cas.LambdaSchedule + The schedule used to control how perturbable forcefield parameters + should be morphed as a function of lambda. If this is not set + then a sire.cas.LambdaSchedule.standard_morph() is used. + + lambda_value: float + The value of lambda at which to run dynamics. This only impacts + perturbable molecules, whose forcefield parameters will be + scaled according to the lambda schedule for the specified + value of lambda. + + swap_end_states: bool + Whether or not to swap the end states. If this is True, then + the perturbation will run from the perturbed back to the + reference molecule (the perturbed molecule will be at lambda=0, + while the reference molecule will be at lambda=1). This will + use the coordinates of the perturbed molecule as the + starting point. + + temperature: temperature + The temperature at which to run the simulation. A + microcanonical (NVE) simulation will be run if you don't + specify the temperature. + + pressure: pressure + The pressure at which to run the simulation. A + microcanonical (NVE) or canonical (NVT) simulation will be + run if the pressure is not set. + + shift_delta: length + The shift_delta parameter that controls the electrostatic + and van der Waals softening potential that smooths the + creation and deletion of ghost atoms during a potential. + This defaults to 2.0 A. + + coulomb_power: int + The coulomb power parmeter that controls the electrostatic + softening potential that smooths the creation and deletion + of ghost atoms during a potential. This defaults to 0. + + restraints: sire.mm.Restraints or list[sire.mm.Restraints] + A single set of restraints, or a list of sets of + restraints that will be applied to the atoms during + the simulation. + + fixed: molecule(s) view, search string, int, list[int] etc + Anything that can be used to identify the atom or atoms + that should be fixed in place during the simulation. These + atoms will not be moved by dynamics. + + device: str or int + The ID of the GPU (or accelerator) used to accelerate + the simulation. This would be CUDA_DEVICE_ID or similar + if CUDA was used. This can be any valid OpenMM device string + + map: dict + A dictionary of additional options. Note that any options + set in this dictionary that are also specified via one of + the arguments above will be overridden by the argument + value """ from ..mol import _dynamics @@ -506,6 +669,86 @@ def set_time(self, time, map=None): self._molecules = None + def energy_trajectory(self, to_pandas=True, map=None): + """ + Return the energy trajectory for this System. This is the history + of energies evaluate during any dynamics runs. It could include + energies calculated at different values of lambda + """ + from ..base import create_map + + map = create_map(map) + + traj_propname = map["energy_trajectory"] + + try: + traj = self._system.property(traj_propname) + except Exception: + traj = None + + if traj is not None: + if traj.what() != "SireMaths::EnergyTrajectory": + if traj_propname.has_value(): + raise TypeError( + f"You cannot force the use of a {type(traj)} " + "as an EnergyTrajectory" + ) + + traj = None + + if traj is None: + # we need to create this trajectory + from ..maths import EnergyTrajectory + + self._system.set_property( + traj_propname.source(), EnergyTrajectory() + ) + + traj = self._system.property(traj_propname) + + if to_pandas: + return traj.to_pandas() + else: + return traj + + def set_energy_trajectory(self, trajectory, map=None): + """ + Set the energy trajectory to the passed value + """ + from ..base import create_map + + map = create_map(map) + + traj_propname = map["energy_trajectory"] + + if traj_propname.has_value(): + return + + if trajectory.what() != "SireMaths::EnergyTrajectory": + raise TypeError( + f"You cannot set a {type(trajectory)} as an " + "energy trajectory!" + ) + + self._system.set_property(traj_propname.source(), trajectory) + + def clear_energy_trajectory(self, map=None): + """ + Completely clear any existing energy trajectory + """ + from ..base import create_map + + map = create_map(map) + + traj_propname = map["energy_trajectory"] + + if traj_propname.has_value(): + return + + from ..maths import EnergyTrajectory + + self._system.set_property(traj_propname.source(), EnergyTrajectory()) + def evaluate(self, *args, **kwargs): """Return an evaluator for this Systme (or of the matching index/search subset of this System)""" diff --git a/src/sire/units/__init__.py b/src/sire/units/__init__.py index 9a4bf606b..d1d54eee6 100644 --- a/src/sire/units/__init__.py +++ b/src/sire/units/__init__.py @@ -375,9 +375,16 @@ def __generalunit__int__(obj): return int(obj.value()) + def __generalunit__abs__(obj): + if obj.value() < 0: + return obj * -1 + else: + return obj + GeneralUnit.__bool__ = __generalunit__bool__ GeneralUnit.__float__ = __generalunit__float__ GeneralUnit.__int__ = __generalunit__int__ + GeneralUnit.__abs__ = __generalunit__abs__ if not hasattr(GeneralUnit, "to_default"): @@ -622,6 +629,7 @@ def set_si_units(scaled: bool = True): "J mol-1 s", "W mol-1", "N", + "atm", ] ) else: @@ -648,6 +656,7 @@ def set_si_units(scaled: bool = True): "J mol-1 s", "W mol-1", "N", + "atm", ] ) @@ -694,6 +703,7 @@ def set_internal_units(): "kcal mol-1 s", "kcal mol-1 s-1", "kcal Å-1", + "atm", ] ) diff --git a/tests/conftest.py b/tests/conftest.py index bd7808ac0..62dda5fdd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -133,5 +133,10 @@ def triclinic_protein_rst7(): @pytest.fixture(scope="session") -def merged_molecule(): +def merged_ethane_methanol(): return sr.load_test_files("merged_molecule.s3") + + +@pytest.fixture(scope="session") +def merged_zan_ose(): + return sr.load_test_files("merged_ligand.s3") diff --git a/tests/convert/test_openmm_lambda.py b/tests/convert/test_openmm_lambda.py index 9ac3cf9b5..bc8e7bb31 100644 --- a/tests/convert/test_openmm_lambda.py +++ b/tests/convert/test_openmm_lambda.py @@ -2,7 +2,7 @@ import pytest -def _run_test(mols, is_slow=False): +def _run_test(mols, is_slow=False, use_taylor=False): c = mols.cursor() # can only get the same energies if they have the same coordinates @@ -40,41 +40,40 @@ def get_end_state(mol, state, remove_state): mols0 = get_end_state(merge, "0", "1") + water mols1 = get_end_state(merge, "1", "0") + water + # a very basic lambda schedule + l = sr.cas.LambdaSchedule() + l.add_stage("morph", (1 - l.lam()) * l.initial() + l.lam() * l.final()) + # need to use the reference platform on GH Actions - map = {"platform": "Reference"} + map = {"platform": "Reference", "schedule": l} + + if use_taylor: + map["use_taylor_softening"] = True # create the perturbable OpenMM system omm = sr.convert.to(mols, "openmm", map=map) - # a very basic lambda schedule - l = omm.get_lambda_schedule() - l.add_stage("morph", (1 - l.lam()) * l.initial() + l.lam() * l.final()) - omm.set_lambda_schedule(l) - - def e(nrg): - return nrg.value_in_unit(nrg.unit) - # now the lambda0 and lambda1 non-perturbable end states omm0 = sr.convert.to(mols0, "openmm", map=map) - nrg0 = e(omm0.get_energy()) + nrg0 = omm0.get_energy().value() omm1 = sr.convert.to(mols1, "openmm", map=map) - nrg1 = e(omm1.get_energy()) + nrg1 = omm1.get_energy().value() omm.set_lambda(0.0) - assert e(omm.get_energy()) == pytest.approx(nrg0) + assert omm.get_energy().value() == pytest.approx(nrg0) omm.set_lambda(0.5) - nrg0_5 = e(omm.get_energy()) + nrg0_5 = omm.get_energy().value() omm.set_lambda(1.0) - assert e(omm.get_energy()) == pytest.approx(nrg1) + assert omm.get_energy().value() == pytest.approx(nrg1) omm.set_lambda(0.5) - assert e(omm.get_energy()) == pytest.approx(nrg0_5) + assert omm.get_energy().value() == pytest.approx(nrg0_5) omm.set_lambda(0.0) - assert e(omm.get_energy()) == pytest.approx(nrg0) + assert omm.get_energy().value() == pytest.approx(nrg0) # now swap the end states - lambda 0 == 1 and lambda 1 == 0 map["swap_end_states"] = True @@ -83,27 +82,52 @@ def e(nrg): omm.set_lambda_schedule(l) omm.set_lambda(0.0) - assert e(omm.get_energy()) == pytest.approx(nrg1) + assert omm.get_energy().value() == pytest.approx(nrg1) omm.set_lambda(0.5) - assert e(omm.get_energy()) == pytest.approx(nrg0_5) + assert omm.get_energy().value() == pytest.approx(nrg0_5) omm.set_lambda(1.0) - assert e(omm.get_energy()) == pytest.approx(nrg0) + assert omm.get_energy().value() == pytest.approx(nrg0) omm.set_lambda(0.5) - assert e(omm.get_energy()) == pytest.approx(nrg0_5) + assert omm.get_energy().value() == pytest.approx(nrg0_5) omm.set_lambda(0.0) - assert e(omm.get_energy()) == pytest.approx(nrg1) + assert omm.get_energy().value() == pytest.approx(nrg1) + + +@pytest.mark.skipif( + "openmm" not in sr.convert.supported_formats(), + reason="openmm support is not available", +) +def test_openmm_scale_lambda_simple(merged_ethane_methanol): + _run_test(merged_ethane_methanol.clone(), False) + + +@pytest.mark.skipif( + "openmm" not in sr.convert.supported_formats(), + reason="openmm support is not available", +) +def test_openmm_scale_lambda_taylor_simple(merged_ethane_methanol): + _run_test(merged_ethane_methanol.clone(), False, True) + + +@pytest.mark.veryslow +@pytest.mark.skipif( + "openmm" not in sr.convert.supported_formats(), + reason="openmm support is not available", +) +def test_big_openmm_scale_lambda_simple(merged_ethane_methanol): + _run_test(merged_ethane_methanol.clone(), True) @pytest.mark.skipif( "openmm" not in sr.convert.supported_formats(), reason="openmm support is not available", ) -def test_openmm_scale_lambda(merged_molecule): - _run_test(merged_molecule.clone(), False) +def test_openmm_scale_lambda_ligand(merged_zan_ose): + _run_test(merged_zan_ose.clone(), False) @pytest.mark.veryslow @@ -111,5 +135,5 @@ def test_openmm_scale_lambda(merged_molecule): "openmm" not in sr.convert.supported_formats(), reason="openmm support is not available", ) -def test_big_openmm_scale_lambda(merged_molecule): - _run_test(merged_molecule.clone(), True) +def test_big_openmm_scale_lambda_ligand(merged_zan_ose): + _run_test(merged_zan_ose.clone(), True) diff --git a/tests/convert/test_openmm_restraints.py b/tests/convert/test_openmm_restraints.py new file mode 100644 index 000000000..9aed7f679 --- /dev/null +++ b/tests/convert/test_openmm_restraints.py @@ -0,0 +1,265 @@ +import sire as sr +import pytest + + +@pytest.mark.skipif( + "openmm" not in sr.convert.supported_formats(), + reason="openmm support is not available", +) +def test_openmm_positional_restraints(kigaki_mols): + mols = kigaki_mols + + mol = mols[0] + + map = {"space": sr.vol.Cartesian(), "platform": "Reference"} + + # test restraining all C atoms + restraints = sr.restraints.positional(mol, atoms="element C") + + assert len(restraints) == len(mol.atoms("element C")) + + coords = [] + + for restraint, atom in zip(restraints, mol.atoms("element C")): + assert restraint.is_atom_restraint() + assert restraint.position() == atom.coordinates() + assert restraint.r0().is_zero() + coords.append(atom.coordinates()) + + d = mol.dynamics(restraints=restraints, timestep="4fs", map=map) + + d.run("1ps") + + mol = d.commit() + + for atom, coords in zip(mol.atoms("element C"), coords): + assert (atom.coordinates() - coords).length() < 0.1 * sr.units.angstrom + + +@pytest.mark.skipif( + "openmm" not in sr.convert.supported_formats(), + reason="openmm support is not available", +) +def test_openmm_distance_restraints(ala_mols): + mols = ala_mols + + mols = mols[0:2] + + map = {"space": sr.vol.Cartesian(), "platform": "Reference"} + + # test restraining the distance between the first and last molecule + restraints = sr.restraints.distance( + mols, atoms0=mols[0][0], atoms1=mols[-1][0], r0="5A" + ) + + assert len(restraints) == 1 + + # check that we get the same result using bond restraints + restraints2 = sr.restraints.bond( + mols, atoms0=mols[0][0], atoms1=mols[-1][0], r0="5A" + ) + + assert len(restraints2) == 1 + + assert restraints == restraints2 + + assert restraints[0].is_atom_restraint() + assert restraints[0].r0() == sr.u("5A") + + # need to minimise the energy to get the restraint to work + mols = mols.minimisation(restraints=restraints, map=map)() + + d = mols.dynamics(restraints=restraints, timestep="4fs", map=map) + + d.run("10ps") + + mols = d.commit() + + new_coords = [mols[0][0].coordinates(), mols[-1][0].coordinates()] + + assert (new_coords[0] - new_coords[1]).length().value() == pytest.approx( + 5.0, 1e-2 + ) + + +@pytest.mark.skipif( + "openmm" not in sr.convert.supported_formats(), + reason="openmm support is not available", +) +def test_openmm_fixed_atoms(kigaki_mols): + mols = kigaki_mols + + mol = mols[0] + + map = {"space": sr.vol.Cartesian(), "platform": "Reference"} + + # test fixing all C atoms + coords = [] + + for atom in mol.atoms("element C"): + coords.append(atom.coordinates()) + + d = mol.dynamics(fixed="element C", timestep="1fs", map=map) + + d.run("1ps") + + mol = d.commit() + + for atom, coords in zip(mol.atoms("element C"), coords): + assert ( + atom.coordinates() - coords + ).length() < 0.001 * sr.units.angstrom + + +@pytest.mark.skipif( + "openmm" not in sr.convert.supported_formats(), + reason="openmm support is not available", +) +def test_openmm_alchemical_restraints(ala_mols): + mols = ala_mols + + mol = mols[0] + + map = {"space": sr.vol.Cartesian(), "platform": "Reference"} + + # test scaling a positional restraint + restraints = sr.restraints.positional(mol, atoms="element C") + + # move the molecule so that the restraints have some energy + mol = mol.move().translate(sr.maths.Vector(1, 1, 1)).commit() + + d = mol.dynamics(timestep="1fs", restraints=None, map=map) + + nrg_0 = d.current_potential_energy() + + d = mol.dynamics(timestep="1fs", restraints=restraints, map=map) + + nrg_1 = d.current_potential_energy() + + assert nrg_1 != nrg_0 + + l = sr.cas.LambdaSchedule() + + l.add_stage("restraints", l.lam() * l.initial()) + l.set_equation("restraints", "restraint", l.lam() * l.initial()) + + d = mol.dynamics( + timestep="1fs", restraints=restraints, schedule=l, map=map + ) + + d.set_lambda(0) + + assert d.current_potential_energy().value() == pytest.approx( + nrg_0.value(), 1e-6 + ) + + d.set_lambda(1) + + assert d.current_potential_energy().value() == pytest.approx( + nrg_1.value(), 1e-6 + ) + + d.set_lambda(0.3) + + assert d.current_potential_energy().value() == pytest.approx( + nrg_0.value() + 0.3 * (nrg_1.value() - nrg_0.value()), 1e-6 + ) + + +@pytest.mark.skipif( + "openmm" not in sr.convert.supported_formats(), + reason="openmm support is not available", +) +def test_openmm_named_restraints(ala_mols): + mols = ala_mols + + mol = mols[0] + + map = {"space": sr.vol.Cartesian(), "platform": "Reference"} + + # test using named restraints, that we can scale these independently + posrests = sr.restraints.positional( + mol, atoms="element C", name="positional" + ) + + dstrests = sr.restraints.distance( + mol, atoms0=mol[0], atoms1=mol[-1], name="distance", r0="5A" + ) + + restraints = [posrests, dstrests] + + # move the molecule so that the restraints have some energy + mol = mol.move().translate(sr.maths.Vector(1, 1, 1)).commit() + + d = mol.dynamics(timestep="1fs", restraints=None, map=map) + + nrg_0 = d.current_potential_energy() + + d = mol.dynamics(timestep="1fs", restraints=posrests, map=map) + + nrg_1_posrests = d.current_potential_energy() + + d = mol.dynamics(timestep="1fs", restraints=dstrests, map=map) + + nrg_1_dstrests = d.current_potential_energy() + + d = mol.dynamics(timestep="1fs", restraints=restraints, map=map) + + nrg_1_1 = d.current_potential_energy() + + assert nrg_0 != nrg_1_1 + assert nrg_0 != nrg_1_dstrests + assert nrg_0 != nrg_1_posrests + assert nrg_1_dstrests != nrg_1_posrests + + l = sr.cas.LambdaSchedule() + + l.add_stage("1", 0) + l.set_equation("1", "positional", l.lam() * l.initial()) + + l.add_stage("2", 0) + l.set_equation("2", "distance", l.lam() * l.initial()) + + l.add_stage("3", 0) + l.set_equation("3", "positional", l.lam() * l.initial()) + l.set_equation("3", "distance", l.lam() * l.initial()) + + d = mol.dynamics( + timestep="1fs", restraints=restraints, schedule=l, map=map + ) + + d.set_lambda(0) + + assert d.current_potential_energy().value() == pytest.approx( + nrg_0.value(), 1e-6 + ) + + d.set_lambda(1) + + assert d.current_potential_energy().value() == pytest.approx( + nrg_1_1.value(), 1e-6 + ) + + d.set_lambda(0.99999999999 / 3.0) + + assert d.current_potential_energy().value() == pytest.approx( + nrg_1_posrests.value(), 1e-6 + ) + + d.set_lambda(1.0 / 3.0) + + assert d.current_potential_energy().value() == pytest.approx( + nrg_0.value(), 1e-6 + ) + + d.set_lambda(1.99999999999 / 3.0) + + assert d.current_potential_energy().value() == pytest.approx( + nrg_1_dstrests.value(), 1e-6 + ) + + d.set_lambda(2.0 / 3.0) + + assert d.current_potential_energy().value() == pytest.approx( + nrg_0.value(), 1e-6 + ) diff --git a/wrapper/Base/LazyEvaluator.pypp.cpp b/wrapper/Base/LazyEvaluator.pypp.cpp index eb9512f47..c996070fc 100644 --- a/wrapper/Base/LazyEvaluator.pypp.cpp +++ b/wrapper/Base/LazyEvaluator.pypp.cpp @@ -19,6 +19,8 @@ namespace bp = boost::python; #include +#include + #include "lazyevaluator.h" SireBase::LazyEvaluator __copy__(const SireBase::LazyEvaluator &other){ return SireBase::LazyEvaluator(other); } diff --git a/wrapper/Base/Properties.pypp.cpp b/wrapper/Base/Properties.pypp.cpp index 47e33a41e..f8626eeb6 100644 --- a/wrapper/Base/Properties.pypp.cpp +++ b/wrapper/Base/Properties.pypp.cpp @@ -43,6 +43,19 @@ void register_Properties_class(){ Properties_exposer_t Properties_exposer = Properties_exposer_t( "Properties", "This class holds a collection of properties, indexed by name.\nEach property comes complete with a set of metadata.\nThe metadata is actually another Properties object,\nand indeed Properties can itself be a Property,\nso allowing Properties to be nested indefinitely.\n\nAuthor: Christopher Woods\n", bp::init< >("Null constructor - construct an empty set of properties") ); bp::scope Properties_scope( Properties_exposer ); Properties_exposer.def( bp::init< SireBase::Properties const & >(( bp::arg("other") ), "Copy constructor") ); + { //::SireBase::Properties::addLink + + typedef void ( ::SireBase::Properties::*addLink_function_type)( ::QString const &,::QString const & ) ; + addLink_function_type addLink_function_value( &::SireBase::Properties::addLink ); + + Properties_exposer.def( + "addLink" + , addLink_function_value + , ( bp::arg("key"), bp::arg("linked_property") ) + , bp::release_gil_policy() + , "" ); + + } { //::SireBase::Properties::allMetadata typedef ::SireBase::Properties const & ( ::SireBase::Properties::*allMetadata_function_type)( ) const; @@ -130,6 +143,30 @@ void register_Properties_class(){ , bp::release_gil_policy() , "Return the number of properties in this set" ); + } + { //::SireBase::Properties::getLinks + + typedef ::QHash< QString, QString > ( ::SireBase::Properties::*getLinks_function_type)( ) const; + getLinks_function_type getLinks_function_value( &::SireBase::Properties::getLinks ); + + Properties_exposer.def( + "getLinks" + , getLinks_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireBase::Properties::hasLinks + + typedef bool ( ::SireBase::Properties::*hasLinks_function_type)( ) const; + hasLinks_function_type hasLinks_function_value( &::SireBase::Properties::hasLinks ); + + Properties_exposer.def( + "hasLinks" + , hasLinks_function_value + , bp::release_gil_policy() + , "" ); + } { //::SireBase::Properties::hasMetadata @@ -375,6 +412,18 @@ void register_Properties_class(){ , bp::release_gil_policy() , "Return the type name of the property at key key\nThrow: SireBase::missing_property\n" ); + } + { //::SireBase::Properties::removeAllLinks + + typedef void ( ::SireBase::Properties::*removeAllLinks_function_type)( ) ; + removeAllLinks_function_type removeAllLinks_function_value( &::SireBase::Properties::removeAllLinks ); + + Properties_exposer.def( + "removeAllLinks" + , removeAllLinks_function_value + , bp::release_gil_policy() + , "" ); + } { //::SireBase::Properties::removeAllMetadata @@ -400,6 +449,19 @@ void register_Properties_class(){ , bp::release_gil_policy() , "Remove all of the metadata associated with the property at\nkey key" ); + } + { //::SireBase::Properties::removeLink + + typedef void ( ::SireBase::Properties::*removeLink_function_type)( ::QString const & ) ; + removeLink_function_type removeLink_function_value( &::SireBase::Properties::removeLink ); + + Properties_exposer.def( + "removeLink" + , removeLink_function_value + , ( bp::arg("key") ) + , bp::release_gil_policy() + , "" ); + } { //::SireBase::Properties::removeMetadata @@ -527,6 +589,18 @@ void register_Properties_class(){ , bp::release_gil_policy() , "" ); + } + { //::SireBase::Properties::updateProperty + + typedef bool ( ::SireBase::Properties::*updateProperty_function_type)( ::QString const &,::SireBase::Property const &,bool ) ; + updateProperty_function_type updateProperty_function_value( &::SireBase::Properties::updateProperty ); + + Properties_exposer.def( + "updateProperty" + , updateProperty_function_value + , ( bp::arg("key"), bp::arg("value"), bp::arg("auto_add")=(bool)(true) ) + , "Update the passed property to have the value value. This does\n an in-place update on the existing property (which must have\n a compatible type). If auto-add is true, then this will add\n the property if it doesnt exist. This returns whether or not\n a property was updated (or added)\n" ); + } Properties_exposer.staticmethod( "typeName" ); Properties_exposer.def( "__copy__", &__copy__); diff --git a/wrapper/CAS/LambdaSchedule.pypp.cpp b/wrapper/CAS/LambdaSchedule.pypp.cpp index 3ab075667..bff2a2557 100644 --- a/wrapper/CAS/LambdaSchedule.pypp.cpp +++ b/wrapper/CAS/LambdaSchedule.pypp.cpp @@ -42,9 +42,8 @@ void register_LambdaSchedule_class(){ LambdaSchedule_exposer.def( "addChargeScaleStages" , addChargeScaleStages_function_value - , ( bp::arg("scale") ) - , bp::release_gil_policy() - , "" ); + , ( bp::arg("scale")=0.20000000000000001 ) + , "Sandwich the current set of stages with a charge-descaling and\n a charge-scaling stage. This prepends a charge-descaling stage\n that scales the charge parameter down from `initial` to\n :gamma:.initial (where :gamma:=`scale`). The charge parameter in all of\n the exising stages in this schedule are then multiplied\n by :gamma:. A final charge-rescaling stage is then appended that\n scales the charge parameter from :gamma:.final to final.\n" ); } { //::SireCAS::LambdaSchedule::addLever @@ -57,7 +56,7 @@ void register_LambdaSchedule_class(){ , addLever_function_value , ( bp::arg("lever") ) , bp::release_gil_policy() - , "" ); + , "Add a lever to the schedule. This is only useful if you want to\n plot how the equations would affect the lever. Levers will be\n automatically added by any perturbation run that needs them,\n so you dont need to add them manually yourself.\n" ); } { //::SireCAS::LambdaSchedule::addLevers @@ -70,7 +69,7 @@ void register_LambdaSchedule_class(){ , addLevers_function_value , ( bp::arg("levers") ) , bp::release_gil_policy() - , "" ); + , "Add some levers to the schedule. This is only useful if you want to\n plot how the equations would affect the lever. Levers will be\n automatically added by any perturbation run that needs them,\n so you dont need to add them manually yourself.\n" ); } { //::SireCAS::LambdaSchedule::addMorphStage @@ -82,7 +81,7 @@ void register_LambdaSchedule_class(){ "addMorphStage" , addMorphStage_function_value , bp::release_gil_policy() - , "" ); + , "Append a morph stage onto this schedule. The morph stage is a\n standard stage that scales each forcefield parameter by\n (1-:lambda:).initial + :lambda:.final\n" ); } { //::SireCAS::LambdaSchedule::addStage @@ -95,7 +94,7 @@ void register_LambdaSchedule_class(){ , addStage_function_value , ( bp::arg("stage"), bp::arg("equation") ) , bp::release_gil_policy() - , "" ); + , "Append a stage called name which uses the passed equation\n to the end of this schedule. The equation will be the default\n equation that scales all parameters (levers) that dont have\n a custom lever for this stage.\n" ); } { //::SireCAS::LambdaSchedule::appendStage @@ -108,7 +107,32 @@ void register_LambdaSchedule_class(){ , appendStage_function_value , ( bp::arg("stage"), bp::arg("equation") ) , bp::release_gil_policy() - , "" ); + , "Append a stage called name which uses the passed equation\n to the end of this schedule. The equation will be the default\n equation that scales all parameters (levers) that dont have\n a custom lever for this stage.\n" ); + + } + { //::SireCAS::LambdaSchedule::charge_scaled_morph + + typedef ::SireCAS::LambdaSchedule ( *charge_scaled_morph_function_type )( double ); + charge_scaled_morph_function_type charge_scaled_morph_function_value( &::SireCAS::LambdaSchedule::charge_scaled_morph ); + + LambdaSchedule_exposer.def( + "charge_scaled_morph" + , charge_scaled_morph_function_value + , ( bp::arg("scale")=0.20000000000000001 ) + , "Return a LambdaSchedule that represents a central morph\n stage that is sandwiched between a charge descaling,\n and a charge rescaling stage. The first stage scales\n the charge lever down from 1.0 to `scale`. This\n is followed by a standard morph stage using the\n descaled charges. This the finished with a recharging\n stage that restores the charges back to their\n original values.\n" ); + + } + { //::SireCAS::LambdaSchedule::clamp + + typedef double ( ::SireCAS::LambdaSchedule::*clamp_function_type)( double ) const; + clamp_function_type clamp_function_value( &::SireCAS::LambdaSchedule::clamp ); + + LambdaSchedule_exposer.def( + "clamp" + , clamp_function_value + , ( bp::arg("lambda_value") ) + , bp::release_gil_policy() + , "Clamp and return the passed lambda value so that it is between a valid\n range for this schedule (typically between [0.0-1.0] inclusive).\n" ); } { //::SireCAS::LambdaSchedule::clear @@ -120,7 +144,7 @@ void register_LambdaSchedule_class(){ "clear" , clear_function_value , bp::release_gil_policy() - , "" ); + , "Completely clear all stages and levers" ); } { //::SireCAS::LambdaSchedule::final @@ -132,7 +156,7 @@ void register_LambdaSchedule_class(){ "final" , final_function_value , bp::release_gil_policy() - , "" ); + , "Return the symbol used to represent the final\n (:lambda:=1) value of the forcefield parameter\n" ); } { //::SireCAS::LambdaSchedule::getConstant @@ -145,7 +169,7 @@ void register_LambdaSchedule_class(){ , getConstant_function_value , ( bp::arg("constant") ) , bp::release_gil_policy() - , "" ); + , "Return the value of the passed constant that may be\n used in any of the stage equations\n" ); } { //::SireCAS::LambdaSchedule::getConstant @@ -158,7 +182,7 @@ void register_LambdaSchedule_class(){ , getConstant_function_value , ( bp::arg("constant") ) , bp::release_gil_policy() - , "" ); + , "Return the value of the passed constant that may be\n used in any of the stage equations\n" ); } { //::SireCAS::LambdaSchedule::getConstantSymbol @@ -171,7 +195,7 @@ void register_LambdaSchedule_class(){ , getConstantSymbol_function_value , ( bp::arg("constant") ) , bp::release_gil_policy() - , "" ); + , "Get the Symbol used to represent the named constant constant" ); } { //::SireCAS::LambdaSchedule::getEquation @@ -184,7 +208,7 @@ void register_LambdaSchedule_class(){ , getEquation_function_value , ( bp::arg("stage") ) , bp::release_gil_policy() - , "" ); + , "Return the default equation used to control the parameters for\n the stage `stage`.\n" ); } { //::SireCAS::LambdaSchedule::getEquation @@ -197,7 +221,7 @@ void register_LambdaSchedule_class(){ , getEquation_function_value , ( bp::arg("stage"), bp::arg("lever") ) , bp::release_gil_policy() - , "" ); + , "Return the equation used to control the specified `lever`\n at the specified `stage`. This will be a custom equation\n if that has been set for this lever, or else the\n default equation for this stage.\n" ); } { //::SireCAS::LambdaSchedule::getLambdaInStage @@ -210,7 +234,7 @@ void register_LambdaSchedule_class(){ , getLambdaInStage_function_value , ( bp::arg("lambda_value") ) , bp::release_gil_policy() - , "" ); + , "Return the stage-local value of :lambda: that corresponds to the\n global value of :lambda: at `lambda_value`\n" ); } { //::SireCAS::LambdaSchedule::getLeverStages @@ -223,7 +247,7 @@ void register_LambdaSchedule_class(){ , getLeverStages_function_value , ( bp::arg("lambda_values") ) , bp::release_gil_policy() - , "" ); + , "Return the list of lever stages that are used for the passed list\n of lambda values. The lever names will be returned in the matching\n order of the lambda values.\n" ); } { //::SireCAS::LambdaSchedule::getLeverStages @@ -235,7 +259,7 @@ void register_LambdaSchedule_class(){ "getLeverStages" , getLeverStages_function_value , ( bp::arg("num_lambda")=(int)(101) ) - , "" ); + , "Return the lever stages used for the list of `nvalue` lambda values\n generated for the global lambda value between 0 and 1 inclusive.\n" ); } { //::SireCAS::LambdaSchedule::getLeverValues @@ -247,7 +271,7 @@ void register_LambdaSchedule_class(){ "getLeverValues" , getLeverValues_function_value , ( bp::arg("lambda_values"), bp::arg("initial")=1., bp::arg("final")=2. ) - , "" ); + , "Return the lever name and parameter values for that lever\n for the specified list of lambda values, assuming that a\n parameter for that lever has an initial value of\n `initial_value` and a final value of `final_value`. This\n is mostly useful for testing and graphing how this\n schedule would change some hyperthetical forcefield\n parameters for the specified lambda values.\n" ); } { //::SireCAS::LambdaSchedule::getLeverValues @@ -259,7 +283,7 @@ void register_LambdaSchedule_class(){ "getLeverValues" , getLeverValues_function_value , ( bp::arg("num_lambda")=(int)(101), bp::arg("initial")=1., bp::arg("final")=2. ) - , "" ); + , "Return the lever name and parameter values for that lever\n for the specified number of lambda values generated\n evenly between 0 and 1, assuming that a\n parameter for that lever has an initial value of\n `initial_value` and a final value of `final_value`. This\n is mostly useful for testing and graphing how this\n schedule would change some hyperthetical forcefield\n parameters for the specified lambda values.\n" ); } { //::SireCAS::LambdaSchedule::getLevers @@ -271,7 +295,7 @@ void register_LambdaSchedule_class(){ "getLevers" , getLevers_function_value , bp::release_gil_policy() - , "" ); + , "Return all of the levers that have been explicitly added\n to the schedule. Note that levers will be automatically added\n by any perturbation run that needs them, so you dont normally\n need to manage them manually yourself.\n" ); } { //::SireCAS::LambdaSchedule::getStage @@ -284,7 +308,7 @@ void register_LambdaSchedule_class(){ , getStage_function_value , ( bp::arg("lambda_value") ) , bp::release_gil_policy() - , "" ); + , "Return the name of the stage that controls the forcefield parameters\n at the global value of :lambda: equal to `lambda_value`\n" ); } { //::SireCAS::LambdaSchedule::getStages @@ -296,7 +320,7 @@ void register_LambdaSchedule_class(){ "getStages" , getStages_function_value , bp::release_gil_policy() - , "" ); + , "Return the names of all of the stages in this schedule, in\n the order they will be performed\n" ); } { //::SireCAS::LambdaSchedule::initial @@ -308,7 +332,7 @@ void register_LambdaSchedule_class(){ "initial" , initial_function_value , bp::release_gil_policy() - , "" ); + , "Return the symbol used to represent the initial\n (:lambda:=0) value of the forcefield parameter\n" ); } { //::SireCAS::LambdaSchedule::insertStage @@ -321,7 +345,7 @@ void register_LambdaSchedule_class(){ , insertStage_function_value , ( bp::arg("i"), bp::arg("stage"), bp::arg("equation") ) , bp::release_gil_policy() - , "" ); + , "Insert a stage called name at position `i` which uses the passed\n equation. The equation will be the default\n equation that scales all parameters (levers) that dont have\n a custom lever for this stage.\n" ); } { //::SireCAS::LambdaSchedule::isNull @@ -345,7 +369,20 @@ void register_LambdaSchedule_class(){ "lam" , lam_function_value , bp::release_gil_policy() - , "" ); + , "Return the symbol used to represent the :lambda: coordinate.\n This symbol is used to represent the per-stage :lambda:\n variable that goes from 0.0-1.0 within that stage.\n" ); + + } + { //::SireCAS::LambdaSchedule::morph + + typedef double ( ::SireCAS::LambdaSchedule::*morph_function_type)( ::QString const &,double,double,double ) const; + morph_function_type morph_function_value( &::SireCAS::LambdaSchedule::morph ); + + LambdaSchedule_exposer.def( + "morph" + , morph_function_value + , ( bp::arg("lever"), bp::arg("initial"), bp::arg("final"), bp::arg("lambda_value") ) + , bp::release_gil_policy() + , "Return the parameters for the specified lever called `lever_name`\n that have been morphed from the passed list of initial values\n (in `initial`) to the passed list of final values (in `final`)\n for the specified global value of :lambda: (in `lambda_value`).\n\n The morphed parameters will be returned in the matching\n order to `initial` and `final`.\n\n This morphs floating point parameters. There is an overload\n of this function that morphs integer parameters, in which\n case the result would be rounded to the nearest integer.\n" ); } { //::SireCAS::LambdaSchedule::morph @@ -358,7 +395,7 @@ void register_LambdaSchedule_class(){ , morph_function_value , ( bp::arg("lever"), bp::arg("initial"), bp::arg("final"), bp::arg("lambda_value") ) , bp::release_gil_policy() - , "" ); + , "Return the parameters for the specified lever called `lever_name`\n that have been morphed from the passed list of initial values\n (in `initial`) to the passed list of final values (in `final`)\n for the specified global value of :lambda: (in `lambda_value`).\n\n The morphed parameters will be returned in the matching\n order to `initial` and `final`.\n\n This morphs floating point parameters. There is an overload\n of this function that morphs integer parameters, in which\n case the result would be rounded to the nearest integer.\n" ); } { //::SireCAS::LambdaSchedule::morph @@ -371,7 +408,7 @@ void register_LambdaSchedule_class(){ , morph_function_value , ( bp::arg("lever"), bp::arg("initial"), bp::arg("final"), bp::arg("lambda_value") ) , bp::release_gil_policy() - , "" ); + , "Return the parameters for the specified lever called `lever_name`\n that have been morphed from the passed list of initial values\n (in `initial`) to the passed list of final values (in `final`)\n for the specified global value of :lambda: (in `lambda_value`).\n\n The morphed parameters will be returned in the matching\n order to `initial` and `final`.\n\n This function morphs integer parameters. In this case,\n the result will be the rounded to the nearest integer.\n" ); } { //::SireCAS::LambdaSchedule::nLevers @@ -383,7 +420,7 @@ void register_LambdaSchedule_class(){ "nLevers" , nLevers_function_value , bp::release_gil_policy() - , "" ); + , "Return the number of levers that have been explicitly added\n to the schedule. Note that levers will be automatically added\n by any perturbation run that needs them, so you dont normally\n need to manage them manually yourself.\n" ); } { //::SireCAS::LambdaSchedule::nStages @@ -395,7 +432,7 @@ void register_LambdaSchedule_class(){ "nStages" , nStages_function_value , bp::release_gil_policy() - , "" ); + , "Return the number of stages in this schedule" ); } LambdaSchedule_exposer.def( bp::self != bp::self ); @@ -423,7 +460,7 @@ void register_LambdaSchedule_class(){ , prependStage_function_value , ( bp::arg("stage"), bp::arg("equation") ) , bp::release_gil_policy() - , "" ); + , "Prepend a stage called name which uses the passed equation\n to the start of this schedule. The equation will be the default\n equation that scales all parameters (levers) that dont have\n a custom lever for this stage.\n" ); } { //::SireCAS::LambdaSchedule::removeEquation @@ -436,7 +473,7 @@ void register_LambdaSchedule_class(){ , removeEquation_function_value , ( bp::arg("stage"), bp::arg("lever") ) , bp::release_gil_policy() - , "" ); + , "Remove the custom equation for the specified `lever` at the\n specified `stage`. The lever will now use the default\n equation at this stage.\n" ); } { //::SireCAS::LambdaSchedule::removeLever @@ -449,7 +486,7 @@ void register_LambdaSchedule_class(){ , removeLever_function_value , ( bp::arg("lever") ) , bp::release_gil_policy() - , "" ); + , "Remove a lever from the schedule. This will not impact any\n perturbation runs that use this schedule, as any missing\n levers will be re-added.\n" ); } { //::SireCAS::LambdaSchedule::removeLevers @@ -462,7 +499,7 @@ void register_LambdaSchedule_class(){ , removeLevers_function_value , ( bp::arg("levers") ) , bp::release_gil_policy() - , "" ); + , "Remove some levers from the schedule. This will not impact any\n perturbation runs that use this schedule, as any missing\n levers will be re-added.\n" ); } { //::SireCAS::LambdaSchedule::setConstant @@ -475,7 +512,7 @@ void register_LambdaSchedule_class(){ , setConstant_function_value , ( bp::arg("constant"), bp::arg("value") ) , bp::release_gil_policy() - , "" ); + , "Set the value of a constant that may be used in any\n of the stage equations.\n" ); } { //::SireCAS::LambdaSchedule::setConstant @@ -488,7 +525,7 @@ void register_LambdaSchedule_class(){ , setConstant_function_value , ( bp::arg("constant"), bp::arg("value") ) , bp::release_gil_policy() - , "" ); + , "Set the value of a constant that may be used in any\n of the stage equations.\n" ); } { //::SireCAS::LambdaSchedule::setDefaultEquation @@ -501,7 +538,7 @@ void register_LambdaSchedule_class(){ , setDefaultEquation_function_value , ( bp::arg("stage"), bp::arg("equation") ) , bp::release_gil_policy() - , "" ); + , "Set the default equation used to control levers for the\n stage stage to equation. This equation will be used\n to control any levers in this stage that dont have\n their own custom equation.\n" ); } { //::SireCAS::LambdaSchedule::setEquation @@ -514,7 +551,19 @@ void register_LambdaSchedule_class(){ , setEquation_function_value , ( bp::arg("stage"), bp::arg("lever"), bp::arg("equation") ) , bp::release_gil_policy() - , "" ); + , "Set the custom equation used to control the specified\n `lever` at the stage `stage` to `equation`. This equation\n will only be used to control the parameters for the\n specified lever at the specified stage.\n" ); + + } + { //::SireCAS::LambdaSchedule::standard_morph + + typedef ::SireCAS::LambdaSchedule ( *standard_morph_function_type )( ); + standard_morph_function_type standard_morph_function_value( &::SireCAS::LambdaSchedule::standard_morph ); + + LambdaSchedule_exposer.def( + "standard_morph" + , standard_morph_function_value + , bp::release_gil_policy() + , "Return a LambdaSchedule that represents a standard morph,\n where every forcefield parameter is scaled by\n (1-:lambda:).initial + :lambda:.final\n" ); } { //::SireCAS::LambdaSchedule::toString @@ -553,9 +602,11 @@ void register_LambdaSchedule_class(){ , "" ); } + LambdaSchedule_exposer.staticmethod( "charge_scaled_morph" ); LambdaSchedule_exposer.staticmethod( "final" ); LambdaSchedule_exposer.staticmethod( "initial" ); LambdaSchedule_exposer.staticmethod( "lam" ); + LambdaSchedule_exposer.staticmethod( "standard_morph" ); LambdaSchedule_exposer.staticmethod( "typeName" ); LambdaSchedule_exposer.def( "__copy__", &__copy__); LambdaSchedule_exposer.def( "__deepcopy__", &__copy__); diff --git a/wrapper/Convert/SireOpenMM/CMakeLists.txt b/wrapper/Convert/SireOpenMM/CMakeLists.txt index 953d4133f..91003c267 100644 --- a/wrapper/Convert/SireOpenMM/CMakeLists.txt +++ b/wrapper/Convert/SireOpenMM/CMakeLists.txt @@ -32,6 +32,7 @@ if (${SIRE_USE_OPENMM}) lambdalever.cpp openmmmolecule.cpp sire_openmm.cpp + sire_to_openmm_system.cpp ) diff --git a/wrapper/Convert/SireOpenMM/_SireOpenMM.main.cpp b/wrapper/Convert/SireOpenMM/_SireOpenMM.main.cpp index e7d3be94c..c8c317313 100644 --- a/wrapper/Convert/SireOpenMM/_SireOpenMM.main.cpp +++ b/wrapper/Convert/SireOpenMM/_SireOpenMM.main.cpp @@ -44,21 +44,6 @@ void *extract_swig_wrapped_pointer(PyObject *obj) BOOST_PYTHON_MODULE(_SireOpenMM) { - typedef SireMol::SelectorMol (*openmm_system_to_sire_function_type)(const OpenMM::System &, SireBase::PropertyMap const &); - typedef OpenMMMetaData (*sire_to_openmm_system_function_type)(OpenMM::System &, const SireMol::SelectorMol &, SireBase::PropertyMap const &); - typedef void (*set_openmm_coordinates_and_velocities_function_type)(OpenMM::Context &, const OpenMMMetaData &); - typedef SireMol::SelectorMol (*extract_coordinates_function_type)(OpenMM::State const &, SireMol::SelectorMol const &, SireBase::PropertyMap const &); - typedef SireMol::SelectorMol (*extract_coordinates_function_type)(OpenMM::State const &, SireMol::SelectorMol const &, SireBase::PropertyMap const &); - typedef SireMol::SelectorMol (*extract_coordinates_and_velocities_function_type)(OpenMM::State const &, SireMol::SelectorMol const &, SireBase::PropertyMap const &); - typedef SireVol::SpacePtr (*extract_space_function_type)(OpenMM::State const &); - - openmm_system_to_sire_function_type openmm_system_to_sire_function_value(&openmm_system_to_sire); - sire_to_openmm_system_function_type sire_to_openmm_system_function_value(&sire_to_openmm_system); - set_openmm_coordinates_and_velocities_function_type set_openmm_coordinates_and_velocities_function_value(&set_openmm_coordinates_and_velocities); - extract_coordinates_function_type extract_coordinates_function_value(&extract_coordinates); - extract_coordinates_and_velocities_function_type extract_coordinates_and_velocities_function_value(&extract_coordinates_and_velocities); - extract_space_function_type extract_space_function_value(&extract_space); - bp::class_ OpenMMMetaData_exposer_t("OpenMMMetaData", "Internal class used to hold OpenMM coordinates and velocities data"); @@ -91,33 +76,37 @@ BOOST_PYTHON_MODULE(_SireOpenMM) "set_schedule", &LambdaLever::setSchedule, "Set the LambdaSchedule used to control the parameters by lambda"); + LambdaLever_exposer_t.def( + "get_perturbable_molecule_maps", &LambdaLever::getPerturbableMoleculeMaps, + "Return the perturbable molecule maps for all of the perturbable molecules"); + bp::def("_openmm_system_to_sire", - openmm_system_to_sire_function_value, + &openmm_system_to_sire, (bp::arg("system"), bp::arg("map")), "Convert an OpenMM::System to a set of sire molecules."); bp::def("_sire_to_openmm_system", - sire_to_openmm_system_function_value, + &sire_to_openmm_system, (bp::arg("system"), bp::arg("mols"), bp::arg("map")), "Convert sire molecules to an OpenMM::System"); bp::def("_set_openmm_coordinates_and_velocities", - set_openmm_coordinates_and_velocities_function_value, + &set_openmm_coordinates_and_velocities, (bp::arg("context"), bp::arg("coords_and_velocities")), "Set the coordinates and velocities in a context"); bp::def("_openmm_extract_coordinates", - extract_coordinates_function_value, - (bp::arg("state"), bp::arg("mols"), bp::arg("map")), + &extract_coordinates, + (bp::arg("state"), bp::arg("mols"), bp::arg("perturbable_maps"), bp::arg("map")), "Extract the coordinates from 'state' and copy then into the passed 'mols'"); bp::def("_openmm_extract_coordinates_and_velocities", - extract_coordinates_and_velocities_function_value, - (bp::arg("state"), bp::arg("mols"), bp::arg("map")), + &extract_coordinates_and_velocities, + (bp::arg("state"), bp::arg("mols"), bp::arg("perturbable_maps"), bp::arg("map")), "Extract the coordinates and velocities from 'state' and copy then into the passed 'mols'"); bp::def("_openmm_extract_space", - extract_space_function_value, + &extract_space, (bp::arg("state")), "Extract and return the space from 'state'"); diff --git a/wrapper/Convert/SireOpenMM/_sommcontext.py b/wrapper/Convert/SireOpenMM/_sommcontext.py index 562aff6c6..39c3bd074 100644 --- a/wrapper/Convert/SireOpenMM/_sommcontext.py +++ b/wrapper/Convert/SireOpenMM/_sommcontext.py @@ -18,25 +18,59 @@ class that provides extra information and functions """ def __init__( - self, system=None, integrator=None, platform=None, metadata=None + self, + system=None, + integrator=None, + platform=None, + metadata=None, + map=None, ): """ Construct from a passed OpenMM Context, the atom index, and the lambda lever """ if system is not None: - super().__init__(system, integrator, platform) - + from ...base import create_map from ._SireOpenMM import _set_openmm_coordinates_and_velocities - # place the coordinates and velocities into the context - _set_openmm_coordinates_and_velocities(self, metadata) + map = create_map(map) self._atom_index = metadata.index() self._lambda_lever = metadata.lambdaLever() + + # we need to update the constraints in the system + # to match the current value of lambda, before we + # turn this system into a context + if map.specified("lambda"): + lambda_value = map["lambda"].value().as_double() + else: + lambda_value = 0.0 + + # we have space here, if we need it, to update + # the system based on the requested value of lambda + # There are some things that are unchangeable once + # the context has been created (e.g. constraints) + # + # Note the initialisation of the constraints is slow, + # so if there are many constraints then this constructor + # can take several seconds to complete. However, the + # increase in dynamics performance through having a larger + # timestep if we constraint bonds and angles makes this + # more than worth it. Note that this is why minimisations + # start running more quickly than dynamics jobs. There + # are no constraints in minimisations + super().__init__(system, integrator, platform) + + # place the coordinates and velocities into the context + _set_openmm_coordinates_and_velocities(self, metadata) + + self._lambda_value = self._lambda_lever.set_lambda( + self, lambda_value + ) else: self._atom_index = None self._lambda_lever = None + self._lambda_value = 0.0 def __str__(self): p = self.getPlatform() @@ -100,6 +134,10 @@ def get_platform_property(self, key): def set_platform_property(self, key, value): """ Set the value of the platform property 'key' to 'value' + + Note that this probably doesn't do anything as it looks + like platform properties cannot be changed after + construction. This function may be removed. """ value = str(value) @@ -127,6 +165,12 @@ def get_atom_index(self): """ return self._atom_index + def get_lambda(self): + """ + Return the current value of lambda for this context + """ + return self._lambda_value + def get_lambda_lever(self): """ Return the LambdaLever used to change the lambda value @@ -162,17 +206,42 @@ def set_lambda(self, lambda_value: float): if self._lambda_lever is None: return - self._lambda_lever.set_lambda(self, lambda_value) + self._lambda_value = self._lambda_lever.set_lambda(self, lambda_value) + + def set_temperature(self, temperature, rescale_velocities=True): + """ + Set the target temperature for the dynamics. If + rescale_velocities is True then the velocities will + be rescaled to the new temperature + """ + raise NotImplementedError("We can't yet set the temperature") + + def set_pressure(self, pressure): + """ + Set the target pressure for the dynamics. + """ + raise NotImplementedError("We can't yet set the pressure") - def get_potential_energy(self): + def get_potential_energy(self, to_sire_units: bool = True): """ Calculate and return the potential energy of the system """ s = self.getState(getEnergy=True) - return s.getPotentialEnergy() + nrg = s.getPotentialEnergy() + + if to_sire_units: + import openmm + from ...units import kcal_per_mol + + return ( + nrg.value_in_unit(openmm.unit.kilocalorie_per_mole) + * kcal_per_mol + ) + else: + return nrg - def get_energy(self): + def get_energy(self, to_sire_units: bool = True): """ Synonym for self.get_potential_energy() """ - return self.get_potential_energy() + return self.get_potential_energy(to_sire_units=to_sire_units) diff --git a/wrapper/Convert/SireOpenMM/lambdalever.cpp b/wrapper/Convert/SireOpenMM/lambdalever.cpp index 90c537b6b..55529e26d 100644 --- a/wrapper/Convert/SireOpenMM/lambdalever.cpp +++ b/wrapper/Convert/SireOpenMM/lambdalever.cpp @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ @@ -30,6 +30,8 @@ #include "SireCAS/values.h" +#include "tostring.h" + using namespace SireOpenMM; using namespace SireCAS; @@ -40,9 +42,11 @@ LambdaLever::LambdaLever() : SireBase::ConcreteProperty(other), name_to_ffidx(other.name_to_ffidx), + name_to_restraintidx(other.name_to_restraintidx), lambda_schedule(other.lambda_schedule), perturbable_mols(other.perturbable_mols), - start_indicies(other.start_indicies) + start_indicies(other.start_indicies), + perturbable_maps(other.perturbable_maps) { } @@ -55,9 +59,11 @@ LambdaLever &LambdaLever::operator=(const LambdaLever &other) if (this != &other) { name_to_ffidx = other.name_to_ffidx; + name_to_restraintidx = other.name_to_restraintidx; lambda_schedule = other.lambda_schedule; perturbable_mols = other.perturbable_mols; start_indicies = other.start_indicies; + perturbable_maps = other.perturbable_maps; Property::operator=(other); } @@ -67,9 +73,11 @@ LambdaLever &LambdaLever::operator=(const LambdaLever &other) bool LambdaLever::operator==(const LambdaLever &other) const { return name_to_ffidx == other.name_to_ffidx and + name_to_restraintidx == other.name_to_restraintidx and lambda_schedule == other.lambda_schedule and perturbable_mols == other.perturbable_mols and - start_indicies == other.start_indicies; + start_indicies == other.start_indicies and + perturbable_maps == other.perturbable_maps; } bool LambdaLever::operator!=(const LambdaLever &other) const @@ -92,61 +100,77 @@ const char *LambdaLever::typeName() return QMetaType::typeName(qMetaTypeId()); } -template -T *get_force(const QString &name, OpenMM::Context &context, - const QHash &name_to_index, - const QString &force_type) +bool LambdaLever::hasLever(const QString &lever_name) +{ + return this->lambda_schedule.getLevers().contains(lever_name); +} + +void LambdaLever::addLever(const QString &lever_name) { - auto it = name_to_index.constFind(name); + if (this->hasLever(lever_name)) + throw SireError::invalid_key(QObject::tr( + "You cannot add a new lever called '%1' as a lever with " + "this name already exists.") + .arg(lever_name), + CODELOC); + + this->lambda_schedule.addLever(lever_name); +} - if (it == name_to_index.constEnd()) +/** Get the index of the force called 'name'. Returns -1 if + * there is no force with this name + */ +int LambdaLever::getForceIndex(const QString &name) const +{ + auto it = name_to_ffidx.constFind(name); + + if (it == name_to_ffidx.constEnd()) { - return 0; + return -1; } - const int idx = it.value(); + return it.value(); +} - OpenMM::System &system = const_cast(context.getSystem()); +/** Get the C++ type of the force called 'name'. Returns an + * empty string if there is no such force + */ +QString LambdaLever::getForceType(const QString &name, + const OpenMM::System &system) const +{ + int idx = this->getForceIndex(name); - const int num_forces = system.getNumForces(); + if (idx == -1) + return QString(); - if (idx < 0 or idx >= num_forces) + if (idx < 0 or idx >= system.getNumForces()) { throw SireError::invalid_key(QObject::tr( "The index for the Force called '%1', %2, is invalid for an " "OpenMM System which has %3 forces.") .arg(name) .arg(idx) - .arg(num_forces), + .arg(system.getNumForces()), CODELOC); } - OpenMM::Force &force = system.getForce(idx); - - T *t_force = dynamic_cast(&force); + const OpenMM::Force &force = system.getForce(idx); - if (t_force == 0) - { - throw SireError::invalid_cast(QObject::tr( - "Cannot case the force called '%1' to a %2.") - .arg(name) - .arg(force_type), - CODELOC); - } - - return t_force; + return QString::fromStdString(force.getName()); } -std::tuple +std::tuple get_exception(int atom0, int atom1, int start_index, double coul_14_scl, double lj_14_scl, const QVector &morphed_charges, const QVector &morphed_sigmas, - const QVector &morphed_epsilons) + const QVector &morphed_epsilons, + const QVector &morphed_alphas) { double charge = 0.0; double sigma = 0.0; double epsilon = 0.0; + double alpha = 0.0; if (coul_14_scl != 0 or lj_14_scl != 0) { @@ -175,6 +199,17 @@ get_exception(int atom0, int atom1, int start_index, charge = coul_14_scl * morphed_charges.constData()[atom0] * morphed_charges.constData()[atom1]; sigma = 0.5 * (morphed_sigmas.constData()[atom0] + morphed_sigmas.constData()[atom1]); epsilon = lj_14_scl * std::sqrt(morphed_epsilons.constData()[atom0] * morphed_epsilons.constData()[atom1]); + + if (not morphed_alphas.isEmpty()) + { + alpha = std::max(morphed_alphas[atom0], morphed_alphas[atom1]); + } + + // clamp alpha between 0 and 1 + if (alpha < 0) + alpha = 0; + else if (alpha > 1) + alpha = 1; } if (charge == 0 and epsilon == 0) @@ -190,20 +225,40 @@ get_exception(int atom0, int atom1, int start_index, return std::make_tuple(atom0 + start_index, atom1 + start_index, - charge, sigma, epsilon); + charge, sigma, epsilon, + alpha); } -void LambdaLever::setLambda(OpenMM::Context &context, - double lambda_value) const +/** Set the value of lambda in the passed context. Returns the + * actual value of lambda set. + */ +double LambdaLever::setLambda(OpenMM::Context &context, + double lambda_value) const { // go over each forcefield and update the parameters in the forcefield, // and then pass those updated parameters to the context + if (this->lambda_schedule.isNull()) + return 0.0; + + lambda_value = this->lambda_schedule.clamp(lambda_value); + + // we need an editable reference to the system to get editable + // pointers to the forces... + OpenMM::System &system = const_cast(context.getSystem()); // get copies of the forcefields in which the parameters will be changed - auto cljff = get_force("clj", context, name_to_ffidx, "NonbondedForce"); - auto bondff = get_force("bond", context, name_to_ffidx, "HarmonicBondForce"); - auto angff = get_force("angle", context, name_to_ffidx, "HarmonicAngleForce"); - auto dihff = get_force("torsion", context, name_to_ffidx, "PeriodicTorsionForce"); + auto cljff = this->getForce("clj", system); + auto ghost_ghostff = this->getForce("ghost/ghost", system); + auto ghost_nonghostff = this->getForce("ghost/non-ghost", system); + auto ghost_14ff = this->getForce("ghost-14", system); + auto bondff = this->getForce("bond", system); + auto angff = this->getForce("angle", system); + auto dihff = this->getForce("torsion", system); + + // we know if we have peturbable ghost atoms if we have the ghost forcefields + const bool have_ghost_atoms = (ghost_ghostff != 0 or ghost_nonghostff != 0); + + std::vector custom_params = {0.0, 0.0, 0.0, 0.0}; // change the parameters for all of the perturbable molecules for (int i = 0; i < this->perturbable_mols.count(); ++i) @@ -230,6 +285,12 @@ void LambdaLever::setLambda(OpenMM::Context &context, perturbable_mol.perturbed->getEpsilons(), lambda_value); + const auto morphed_alphas = this->lambda_schedule.morph( + "alpha", + perturbable_mol.getAlphas(), + perturbable_mol.perturbed->getAlphas(), + lambda_value); + const auto morphed_bond_k = this->lambda_schedule.morph( "bond_k", perturbable_mol.getBondKs(), @@ -272,21 +333,57 @@ void LambdaLever::setLambda(OpenMM::Context &context, perturbable_mol.perturbed->getTorsionKs(), lambda_value); + // will eventually morph the NB14 / exception parameters :-) + // now update the forcefields int start_index = start_idxs.value("clj", -1); - if (start_index != -1) + if (start_index != -1 and cljff != 0) { const int nparams = morphed_charges.count(); - if (cljff == 0) - throw SireError::incompatible_error(QObject::tr( - "There is no NonbondedForce called 'clj' in this context!"), - CODELOC); - - for (int j = 0; j < nparams; ++j) + if (have_ghost_atoms) { - cljff->setParticleParameters(start_index + j, morphed_charges[j], morphed_sigmas[j], morphed_epsilons[j]); + for (int j = 0; j < nparams; ++j) + { + const bool is_from_ghost = perturbable_mol.from_ghost_idxs.contains(j); + const bool is_to_ghost = perturbable_mol.to_ghost_idxs.contains(j); + + // reduced charge + custom_params[0] = morphed_charges[j]; + // half_sigma + custom_params[1] = 0.5 * morphed_sigmas[j]; + // two_sqrt_epsilon + custom_params[2] = 2.0 * std::sqrt(morphed_epsilons[j]); + // alpha + custom_params[3] = morphed_alphas[j]; + + // clamp alpha between 0 and 1 + if (custom_params[3] < 0) + custom_params[3] = 0; + else if (custom_params[3] > 1) + custom_params[3] = 1; + + ghost_ghostff->setParticleParameters(start_index + j, custom_params); + ghost_nonghostff->setParticleParameters(start_index + j, custom_params); + + if (is_from_ghost or is_to_ghost) + { + // don't set the LJ parameters in the cljff + cljff->setParticleParameters(start_index + j, morphed_charges[j], 0.0, 0.0); + } + else + { + cljff->setParticleParameters(start_index + j, morphed_charges[j], morphed_sigmas[j], morphed_epsilons[j]); + } + } + } + else + { + for (int j = 0; j < nparams; ++j) + { + cljff->setParticleParameters(start_index + j, morphed_charges[j], morphed_sigmas[j], morphed_epsilons[j]); + } } const auto idxs = perturbable_mol.exception_idxs.value("clj"); @@ -297,30 +394,71 @@ void LambdaLever::setLambda(OpenMM::Context &context, { const auto ¶m = perturbable_mol.exception_params[j]; + const auto atom0 = std::get<0>(param); + const auto atom1 = std::get<1>(param); + + const auto coul_14_scale = std::get<2>(param); + const auto lj_14_scale = std::get<3>(param); + + const bool atom0_is_ghost = perturbable_mol.isGhostAtom(atom0); + const bool atom1_is_ghost = perturbable_mol.isGhostAtom(atom1); + const auto p = get_exception(std::get<0>(param), std::get<1>(param), - start_index, std::get<2>(param), std::get<3>(param), - morphed_charges, morphed_sigmas, morphed_epsilons); - - cljff->setExceptionParameters( - idxs[j], - std::get<0>(p), std::get<1>(p), - std::get<2>(p), std::get<3>(p), - std::get<4>(p)); + start_index, coul_14_scale, lj_14_scale, + morphed_charges, morphed_sigmas, morphed_epsilons, + morphed_alphas); + + // don't set LJ terms for ghost atoms + if (atom0_is_ghost or atom1_is_ghost) + { + cljff->setExceptionParameters( + std::get<0>(idxs[j]), + std::get<0>(p), std::get<1>(p), + std::get<2>(p), 1e-9, 1e-9); + + if (coul_14_scale != 0 or lj_14_scale != 0) + { + // this is a 1-4 parameter - need to update + // the ghost 1-4 forcefield + int nbidx = std::get<1>(idxs[j]); + + if (nbidx < 0) + throw SireError::program_bug(QObject::tr( + "Unset NB14 index for a ghost atom?"), + CODELOC); + + if (ghost_14ff != 0) + { + // parameters are q, sigma, four_epsilon and alpha + std::vector params14 = + {std::get<2>(p), std::get<3>(p), + 4.0 * std::get<4>(p), std::get<5>(p)}; + + ghost_14ff->setBondParameters(nbidx, + std::get<0>(p), + std::get<1>(p), + params14); + } + } + } + else + { + cljff->setExceptionParameters( + std::get<0>(idxs[j]), + std::get<0>(p), std::get<1>(p), + std::get<2>(p), std::get<3>(p), + std::get<4>(p)); + } } } } start_index = start_idxs.value("bond", -1); - if (start_index != -1) + if (start_index != -1 and bondff != 0) { const int nparams = morphed_bond_k.count(); - if (bondff == 0) - throw SireError::incompatible_error(QObject::tr( - "There is no HarmonicBondForce called 'bond' in this context!"), - CODELOC); - for (int j = 0; j < nparams; ++j) { const int index = start_index + j; @@ -339,15 +477,10 @@ void LambdaLever::setLambda(OpenMM::Context &context, start_index = start_idxs.value("angle", -1); - if (start_index != -1) + if (start_index != -1 and angff != 0) { const int nparams = morphed_angle_k.count(); - if (angff == 0) - throw SireError::incompatible_error(QObject::tr( - "There is no HarmonicAngleForce called 'angle' in this context!"), - CODELOC); - for (int j = 0; j < nparams; ++j) { const int index = start_index + j; @@ -368,15 +501,10 @@ void LambdaLever::setLambda(OpenMM::Context &context, start_index = start_idxs.value("torsion", -1); - if (start_index != -1) + if (start_index != -1 and dihff != 0) { const int nparams = morphed_torsion_k.count(); - if (dihff == 0) - throw SireError::incompatible_error(QObject::tr( - "There is no PeriodicTorsionForce called 'torsion' in this context!"), - CODELOC); - for (int j = 0; j < nparams; ++j) { const int index = start_index + j; @@ -400,9 +528,19 @@ void LambdaLever::setLambda(OpenMM::Context &context, } } + // update the parameters in the context if (cljff) cljff->updateParametersInContext(context); + if (ghost_ghostff) + ghost_ghostff->updateParametersInContext(context); + + if (ghost_nonghostff) + ghost_nonghostff->updateParametersInContext(context); + + if (ghost_14ff) + ghost_14ff->updateParametersInContext(context); + if (bondff) bondff->updateParametersInContext(context); @@ -411,9 +549,162 @@ void LambdaLever::setLambda(OpenMM::Context &context, if (dihff) dihff->updateParametersInContext(context); + + // now update any restraints that are scaled + for (const auto &restraint : this->name_to_restraintidx.keys()) + { + // restraints always morph between 1 and 1 (i.e. they fully + // follow whatever is set by lambda, e.g. 'initial*lambda' + // to switch them on, or `final*lambda` to switch them off) + const double rho = lambda_schedule.morph(restraint, + 1.0, 1.0, + lambda_value); + + for (auto &ff : this->getRestraints(restraint, system)) + { + if (ff != 0) + { + this->updateRestraintInContext(*ff, rho, context); + } + } + } + + return lambda_value; +} + +/** Update the parameters for a CustomCompoundBondForce for scale factor 'rho' + * in the passed context */ +void _update_restraint_in_context(OpenMM::CustomCompoundBondForce *ff, double rho, + OpenMM::Context &context) +{ + if (ff == 0) + throw SireError::invalid_cast(QObject::tr( + "Unable to cast the restraint force to an OpenMM::CustomCompoundBondForce, " + "despite it reporting that is was an object of this type..."), + CODELOC); + + const int nbonds = ff->getNumBonds(); + + if (nbonds == 0) + // nothing to update + return; + + const int nparams = ff->getNumPerBondParameters(); + + if (nparams == 0) + throw SireError::incompatible_error(QObject::tr( + "Unable to set 'rho' for this restraint as it has no custom parameters!"), + CODELOC); + + // we set the first parameter - we can see what the current value + // is from the first restraint. This is because rho should be the + // first parameter and have the same value for all restraints + std::vector custom_params; + std::vector particles; + + custom_params.resize(nparams); + particles.resize(ff->getNumParticlesPerBond()); + + ff->getBondParameters(0, particles, custom_params); + + if (custom_params[0] == rho) + // nothing to do - it is already equal to this value + return; + + for (int i = 0; i < nbonds; ++i) + { + ff->getBondParameters(i, particles, custom_params); + custom_params[0] = rho; + ff->setBondParameters(i, particles, custom_params); + } + + ff->updateParametersInContext(context); +} + +/** Update the parameters for a CustomBondForce for scale factor 'rho' + * in the passed context */ +void _update_restraint_in_context(OpenMM::CustomBondForce *ff, double rho, + OpenMM::Context &context) +{ + if (ff == 0) + throw SireError::invalid_cast(QObject::tr( + "Unable to cast the restraint force to an OpenMM::CustomBondForce, " + "despite it reporting that is was an object of this type..."), + CODELOC); + + const int nbonds = ff->getNumBonds(); + + if (nbonds == 0) + // nothing to update + return; + + const int nparams = ff->getNumPerBondParameters(); + + if (nparams == 0) + throw SireError::incompatible_error(QObject::tr( + "Unable to set 'rho' for this restraint as it has no custom parameters!"), + CODELOC); + + // we set the first parameter - we can see what the current value + // is from the first restraint. This is because rho should be the + // first parameter and have the same value for all restraints + std::vector custom_params; + custom_params.resize(nparams); + int atom0, atom1; + + ff->getBondParameters(0, atom0, atom1, custom_params); + + if (custom_params[0] == rho) + // nothing to do - it is already equal to this value + return; + + for (int i = 0; i < nbonds; ++i) + { + ff->getBondParameters(i, atom0, atom1, custom_params); + custom_params[0] = rho; + ff->setBondParameters(i, atom0, atom1, custom_params); + } + + ff->updateParametersInContext(context); } -void LambdaLever::setForceIndex(const QString &force, int index) +/** Internal function used to update the restraint force to use the + * supplied value of rho in the passed context */ +void LambdaLever::updateRestraintInContext(OpenMM::Force &ff, double rho, + OpenMM::Context &context) const +{ + // what is the type of this force...? + const auto ff_type = ff.getName(); + + if (ff_type == "CustomBondForce") + { + _update_restraint_in_context( + dynamic_cast(&ff), + rho, context); + } + else if (ff_type == "CustomCompoundBondForce") + { + _update_restraint_in_context( + dynamic_cast(&ff), + rho, context); + } + else + { + throw SireError::unknown_type(QObject::tr( + "Unable to update the restraints for the passed force as it has " + "an unknown type (%1). We currently only support a limited number " + "of force types, e.g. CustomBondForce etc") + .arg(QString::fromStdString(ff_type)), + CODELOC); + } +} + +/** Set the index of the force called 'force' in the OpenMM System. + * There can only be one force with this name. Attempts to add + * a duplicate will cause an error to be raised. + */ +void LambdaLever::setForceIndex(const QString &force, + int index) { if (index < 0) throw SireError::invalid_index(QObject::tr( @@ -425,17 +716,64 @@ void LambdaLever::setForceIndex(const QString &force, int index) this->name_to_ffidx.insert(force, index); } -void LambdaLever::addPerturbableMolecule(const OpenMMMolecule &molecule, - const QHash &starts) +/** Add the index of a restraint force called 'restraint' in the + * OpenMM System. There can be multiple restraint forces with + * the same name + */ +void LambdaLever::addRestraintIndex(const QString &restraint, + int index) +{ + this->name_to_restraintidx.insertMulti(restraint, index); +} + +/** Return the pointers to all of the forces from the passed System + * are restraints called 'restraint'. This returns an empty + * list if there are no restraints with this name */ +QList LambdaLever::getRestraints(const QString &name, + OpenMM::System &system) const +{ + QList forces; + + const int num_forces = system.getNumForces(); + + for (const auto &idx : this->name_to_restraintidx.values(name)) + { + if (idx < 0 or idx >= num_forces) + { + throw SireError::invalid_key(QObject::tr( + "The index for the Restraint Force called '%1', %2, is invalid for an " + "OpenMM System which has %3 forces.") + .arg(name) + .arg(idx) + .arg(num_forces), + CODELOC); + } + + forces.append(&(system.getForce(idx))); + } + + return forces; +} + +/** Add info for the passed perturbable OpenMMMolecule, returning + * its index in the list of perturbable molecules + */ +int LambdaLever::addPerturbableMolecule(const OpenMMMolecule &molecule, + const QHash &starts) { // should add in some sanity checks for these inputs this->perturbable_mols.append(molecule); this->start_indicies.append(starts); + this->perturbable_maps.insert(molecule.number, molecule.perturtable_map); + return this->perturbable_mols.count() - 1; } +/** Set the exception indices for the perturbable molecule at + * index 'mol_idx' + */ void LambdaLever::setExceptionIndicies(int mol_idx, const QString &name, - const QVector &exception_idxs) + const QVector> &exception_idxs) { mol_idx = SireID::Index(mol_idx).map(this->perturbable_mols.count()); @@ -443,6 +781,14 @@ void LambdaLever::setExceptionIndicies(int mol_idx, name, exception_idxs); } +/** Return all of the property maps used to find the perturbable properties + * of the perturbable molecules. This is indexed by molecule number + */ +QHash LambdaLever::getPerturbableMoleculeMaps() const +{ + return perturbable_maps; +} + LambdaSchedule LambdaLever::getSchedule() const { return lambda_schedule; diff --git a/wrapper/Convert/SireOpenMM/lambdalever.h b/wrapper/Convert/SireOpenMM/lambdalever.h index a52cffbc7..ecb4a4de1 100644 --- a/wrapper/Convert/SireOpenMM/lambdalever.h +++ b/wrapper/Convert/SireOpenMM/lambdalever.h @@ -22,7 +22,7 @@ * that should have come with this distribution. * * You can contact the authors via the website - * at http://sire.openbiosim.org + * at https://sire.openbiosim.org * \*********************************************/ @@ -37,7 +37,6 @@ SIRE_BEGIN_HEADER namespace SireOpenMM { - /** This is a lever that is used to change the parameters in an OpenMM * context according to a lambda value. This is actually a collection * of levers, each of which is controlled by the main lever. @@ -63,24 +62,49 @@ namespace SireOpenMM const char *what() const; static const char *typeName(); - void setLambda(OpenMM::Context &context, double lam_val) const; + double setLambda(OpenMM::Context &context, double lam_val) const; void setForceIndex(const QString &force, int index); - void addPerturbableMolecule(const OpenMMMolecule &molecule, - const QHash &start_indicies); + void addRestraintIndex(const QString &force, int index); + + int addPerturbableMolecule(const OpenMMMolecule &molecule, + const QHash &start_indicies); void setExceptionIndicies(int idx, const QString &ff, - const QVector &exception_idxs); + const QVector> &exception_idxs); void setSchedule(const SireCAS::LambdaSchedule &schedule); + void addLever(const QString &lever_name); + + bool hasLever(const QString &lever_name); + + QHash getPerturbableMoleculeMaps() const; + SireCAS::LambdaSchedule getSchedule() const; + template + T *getForce(const QString &name, OpenMM::System &system) const; + + QList getRestraints(const QString &name, + OpenMM::System &system) const; + + int getForceIndex(const QString &name) const; + QString getForceType(const QString &name, + const OpenMM::System &system) const; + protected: + void updateRestraintInContext(OpenMM::Force &ff, double rho, + OpenMM::Context &context) const; + /** Map from a forcefield name to its index in the associated System */ QHash name_to_ffidx; + /** Map from a restraint name to its index in the associated System. + * Note that multiple restraints can have the same name */ + QMultiHash name_to_restraintidx; + /** The schedule used to set lambda */ SireCAS::LambdaSchedule lambda_schedule; @@ -90,7 +114,78 @@ namespace SireOpenMM /** The start indicies of the parameters in each named forcefield for each perturbable moleucle */ QVector> start_indicies; + + /** All of the property maps for the perturbable molecules */ + QHash perturbable_maps; }; + +#ifndef SIRE_SKIP_INLINE_FUNCTION + + template + inline QString _get_typename() + { + return QString::fromStdString(T().getName()); + } + + template <> + inline QString _get_typename() + { + return "OpenMM::CustomNonbondedForce"; + } + + template <> + inline QString _get_typename() + { + return "OpenMM::CustomBondForce"; + } + + /** Return the OpenMM::Force (of type T) that is called 'name' + * from the passed OpenMM::System. This returns 0 if the force + * doesn't exist + */ + template + T *LambdaLever::getForce(const QString &name, OpenMM::System &system) const + { + auto it = name_to_ffidx.constFind(name); + + if (it == name_to_ffidx.constEnd()) + { + return 0; + } + + const int idx = it.value(); + + const int num_forces = system.getNumForces(); + + if (idx < 0 or idx >= num_forces) + { + throw SireError::invalid_key(QObject::tr( + "The index for the Force called '%1', %2, is invalid for an " + "OpenMM System which has %3 forces.") + .arg(name) + .arg(idx) + .arg(num_forces), + CODELOC); + } + + OpenMM::Force &force = system.getForce(idx); + + T *t_force = dynamic_cast(&force); + + if (t_force == 0) + { + throw SireError::invalid_cast(QObject::tr( + "Cannot cast the force called '%1' to a %2. We think it is a %3.") + .arg(name) + .arg(_get_typename()) + .arg(QString::fromStdString(force.getName())), + CODELOC); + } + + return t_force; + } + +#endif } Q_DECLARE_METATYPE(SireOpenMM::LambdaLever) diff --git a/wrapper/Convert/SireOpenMM/openmmmolecule.cpp b/wrapper/Convert/SireOpenMM/openmmmolecule.cpp index c992d98a9..ba91761e3 100644 --- a/wrapper/Convert/SireOpenMM/openmmmolecule.cpp +++ b/wrapper/Convert/SireOpenMM/openmmmolecule.cpp @@ -16,6 +16,7 @@ #include "SireMM/atomljs.h" #include "SireMM/selectorbond.h" #include "SireMM/amberparams.h" +#include "SireMM/twoatomfunctions.h" #include "SireMaths/vector.h" @@ -45,6 +46,7 @@ OpenMMMolecule::OpenMMMolecule(const Molecule &mol, const PropertyMap &map) { molinfo = mol.info(); + number = mol.number(); if (molinfo.nAtoms() == 0) { @@ -136,15 +138,28 @@ OpenMMMolecule::OpenMMMolecule(const Molecule &mol, std::swap(map0, map1); } - this->constructFromAmber(mol, map0); + // save this perturbable map - this will help us set + // new properties from the results of dynamics, e.g. + // updating coordinates after minimisation + perturtable_map = map0; + + // extract the parameters in amber format - this should work, + // as the 'forcefield' property has promised that this is + // an amber-style molecule + const auto params = SireMM::AmberParams(mol, map0); + const auto params1 = SireMM::AmberParams(mol, map1); perturbed.reset(new OpenMMMolecule(*this)); - perturbed->constructFromAmber(mol, map1); - this->alignInternals(); + perturbed->constructFromAmber(mol, params1, params, map1, true); + + this->constructFromAmber(mol, params, params1, map0, true); + + this->alignInternals(map); } else { - this->constructFromAmber(mol, map); + const auto params = SireMM::AmberParams(mol, map); + this->constructFromAmber(mol, params, params, map, false); } } else @@ -179,6 +194,11 @@ bool OpenMMMolecule::isPerturbable() const return perturbed.get() != 0; } +bool OpenMMMolecule::isGhostAtom(int atom) const +{ + return from_ghost_idxs.contains(atom) or to_ghost_idxs.contains(atom); +} + OpenMM::Vec3 to_vec3(const SireMaths::Vector &coords) { const double internal_to_nm = (1 * SireUnits::angstrom).to(SireUnits::nanometer); @@ -244,7 +264,10 @@ std::tuple OpenMMMolecule::getException( } void OpenMMMolecule::constructFromAmber(const Molecule &mol, - const PropertyMap &map) + const AmberParams ¶ms, + const AmberParams ¶ms1, + const PropertyMap &map, + bool is_perturbable) { const auto &moldata = mol.data(); atoms = mol.atoms(); @@ -255,11 +278,6 @@ void OpenMMMolecule::constructFromAmber(const Molecule &mol, return; } - // extract the parameters in amber format - this should work, - // as the 'forcefield' property has promised that this is - // an amber-style molecule - const auto params = SireMM::AmberParams(mol, map); - // look up the CGAtomIdx of each atom - this is because we // will use AtomIdx for the ordering and atom identifiers auto idx_to_cgatomidx = QVector(nats); @@ -272,7 +290,7 @@ void OpenMMMolecule::constructFromAmber(const Molecule &mol, // extract the coordinates and convert to OpenMM units const auto coords_prop = map["coordinates"]; - coords = QVector(nats, OpenMM::Vec3(0, 0, 0)); + this->coords = QVector(nats, OpenMM::Vec3(0, 0, 0)); if (moldata.hasProperty(coords_prop)) { @@ -302,36 +320,69 @@ void OpenMMMolecule::constructFromAmber(const Molecule &mol, // extract the masses and convert to OpenMM units const auto params_masses = params.masses(); - masses = QVector(nats, 0.0); + + this->masses = QVector(nats, 0.0); auto masses_data = masses.data(); - for (int i = 0; i < nats; ++i) + if (is_perturbable) { - double mass = params_masses.at(idx_to_cgatomidx_data[i]).to(SireUnits::g_per_mol); + const auto params1_masses = params1.masses(); - if (mass < 0.05) + for (int i = 0; i < nats; ++i) { - mass = 0.0; - } + const auto cgatomidx = idx_to_cgatomidx_data[i]; - if (mass < 0.5) - { - virtual_sites.append(i); + // use the largest mass of the perturbing atoms + double mass = std::max(params_masses.at(cgatomidx).to(SireUnits::g_per_mol), + params1_masses.at(cgatomidx).to(SireUnits::g_per_mol)); + + if (mass < 0.05) + { + mass = 0.0; + } + + if (mass < 0.5) + { + virtual_sites.append(i); + } + else if (mass < 2.5) + { + light_atoms.append(i); + } + + masses_data[i] = mass; } - else if (mass < 2.5) + } + else + { + for (int i = 0; i < nats; ++i) { - light_atoms.append(i); - } + double mass = params_masses.at(idx_to_cgatomidx_data[i]).to(SireUnits::g_per_mol); + + if (mass < 0.05) + { + mass = 0.0; + } + + if (mass < 0.5) + { + virtual_sites.append(i); + } + else if (mass < 2.5) + { + light_atoms.append(i); + } - masses_data[i] = mass; + masses_data[i] = mass; + } } // extract the charges and LJ parameters and convert to OpenMM units const auto params_charges = params.charges(); const auto params_ljs = params.ljs(); - cljs = QVector>(nats, std::make_tuple(0.0, 0.0, 0.0)); + this->cljs = QVector>(nats, std::make_tuple(0.0, 0.0, 0.0)); auto cljs_data = cljs.data(); for (int i = 0; i < nats; ++i) @@ -347,12 +398,9 @@ void OpenMMMolecule::constructFromAmber(const Molecule &mol, cljs_data[i] = std::make_tuple(chg, sig, eps); } - bond_pairs.clear(); - bond_params.clear(); - constraints.clear(); - - auto bond_param = bond_params.data(); - auto bond_pair = bond_pairs.data(); + this->bond_pairs.clear(); + this->bond_params.clear(); + this->constraints.clear(); // now the bonds const double bond_k_to_openmm = 2.0 * (SireUnits::kcal_per_mol / (SireUnits::angstrom * SireUnits::angstrom)).to(SireUnits::kJ_per_mol / (SireUnits::nanometer * SireUnits::nanometer)); @@ -380,16 +428,16 @@ void OpenMMMolecule::constructFromAmber(const Molecule &mol, const bool has_light_atom = includes_h or (masses_data[atom0] < 2.5 or masses_data[atom1] < 2.5); const bool has_massless_atom = masses_data[atom0] < 0.5 or masses_data[atom1] < 0.5; - bond_pairs.append(std::make_pair(atom0, atom1)); + this->bond_pairs.append(std::make_pair(atom0, atom1)); if ((not has_massless_atom) and ((constraint_type & CONSTRAIN_BONDS) or (has_light_atom and (constraint_type & CONSTRAIN_HBONDS)))) { - constraints.append(std::make_tuple(atom0, atom1, r0)); + this->constraints.append(std::make_tuple(atom0, atom1, r0)); constrained_pairs.insert(to_pair(atom0, atom1)); } else { - bond_params.append(std::make_tuple(atom0, atom1, r0, k)); + this->bond_params.append(std::make_tuple(atom0, atom1, r0, k)); } } @@ -404,7 +452,6 @@ void OpenMMMolecule::constructFromAmber(const Molecule &mol, { const auto angid = it.key().map(molinfo); const auto &angparam = it.value().first; - const auto includes_h = it.value().second; int atom0 = angid.get<0>().value(); int atom1 = angid.get<1>().value(); @@ -416,9 +463,11 @@ void OpenMMMolecule::constructFromAmber(const Molecule &mol, const double k = angparam.k() * angle_k_to_openmm; const double theta0 = angparam.theta0(); // already in radians - const bool is_h_x_h = includes_h and (masses_data[atom0] < 2.5 and masses_data[atom2] < 2.5); + const bool is_h_x_h = masses_data[atom0] < 2.5 and masses_data[atom2] < 2.5; + + const auto key = to_pair(atom0, atom2); - if (not constrained_pairs.contains(to_pair(atom0, atom2))) + if (not constrained_pairs.contains(key)) { // only include the angle X-y-Z if X-Z are not already constrained if ((constraint_type & CONSTRAIN_ANGLES) and is_h_x_h) @@ -428,7 +477,7 @@ void OpenMMMolecule::constructFromAmber(const Molecule &mol, (delta[1] * delta[1]) + (delta[2] * delta[2])); constraints.append(std::make_tuple(atom0, atom2, length)); - constrained_pairs.insert(to_pair(atom0, atom2)); + constrained_pairs.insert(key); } else { @@ -530,114 +579,131 @@ void OpenMMMolecule::constructFromAmber(const Molecule &mol, } } - exception_params.clear(); + this->buildPerturbableExceptions(mol, constrained_pairs, map); - for (auto it = params.nb14s().constBegin(); - it != params.nb14s().constEnd(); - ++it) + /*if (is_perturbable) { - const double cscl = it.value().cscl(); - const double ljscl = it.value().ljscl(); - - const auto nbid = it.key().map(molinfo); - - const int atom0 = nbid.get<0>().value(); - const int atom1 = nbid.get<1>().value(); - - exception_params.append(std::make_tuple( - atom0, atom1, cscl, ljscl)); + // build all of the exceptions for perturbable molecules. + // This is because the exceptions could change during the + // perturbation, so we need more control over them + this->buildPerturbableExceptions(mol, constrained_pairs, map); } - - // finally, add in all of the excluded atoms - excl_pairs = ExcludedPairs(mol, map); - - if (excl_pairs.count() > 0) + else { - for (int i = 0; i < excl_pairs.count(); ++i) - { - auto pair = excl_pairs[i]; - - exception_params.append(std::make_tuple( - std::get<0>(pair).value(), std::get<1>(pair).value(), - 0.0, 0.0)); - } - - // and finally (finally!) find any atoms that are not bonded to - // anything else and make sure that they are constrained. These - // atoms will be excluded atoms (by definition) so just look - // through those - const auto &connectivity = mol.property(map["connectivity"]).asA(); + // just find any exceptions that would be missing based on just + // a cursary look at the bonding + this->buildStandardExceptions(mol, params, + constrained_pairs, map); + }*/ +} - for (int i = 0; i < excl_pairs.count(); ++i) - { - auto pair = excl_pairs[i]; +bool is_ghost(const std::tuple &clj) +{ + return std::get<0>(clj) == 0 and (std::get<1>(clj) == 0 or std::get<2>(clj) == 0); +} - const int atom0 = std::get<0>(pair).value(); - const int atom1 = std::get<1>(pair).value(); +bool is_ghost_charge(const std::tuple &clj) +{ + return std::get<0>(clj) == 0; +} - if (not constrained_pairs.contains(to_pair(atom0, atom1))) - { - if (connectivity.nConnections(std::get<0>(pair)) == 0 or - connectivity.nConnections(std::get<1>(pair)) == 0) - { - const auto delta = coords[atom1] - coords[atom0]; - const auto length = std::sqrt((delta[0] * delta[0]) + - (delta[1] * delta[1]) + - (delta[2] * delta[2])); - constraints.append(std::make_tuple(atom0, atom1, length)); - constrained_pairs.insert(to_pair(atom0, atom1)); - } - } - } - } +bool is_ghost_lj(const std::tuple &clj) +{ + return std::get<1>(clj) == 0 or std::get<2>(clj) == 0; } /** Go through all of the internals and compare them to the perturbed * state. Ensure that there is a one-to-one mapping, with them all * in the same order. Any that are missing are added as nulls in - * the correct end state + * the correct end state. + * + * Note that bonds and angles involved in constraints have been + * removed from the list of potentials. This is because we don't + * evaluate the energy of constrained degrees of freedom */ -void OpenMMMolecule::alignInternals() +void OpenMMMolecule::alignInternals(const PropertyMap &map) { + // first go through an see which atoms are ghosts + // While we do this, set the alpha values for the + // reference and perturbed molecules + if (cljs.count() != perturbed->cljs.count()) + throw SireError::incompatible_error(QObject::tr( + "Different number of CLJ parameters between the reference " + "(%1) and perturbed (%2) states.") + .arg(cljs.count()) + .arg(perturbed->cljs.count()), + CODELOC); + + this->alphas = QVector(cljs.count(), 0.0); + this->perturbed->alphas = this->alphas; + + for (int i = 0; i < cljs.count(); ++i) + { + const auto &clj0 = cljs.at(i); + const auto &clj1 = perturbed->cljs.at(i); + + if (clj0 != clj1) + { + if (is_ghost(clj0)) + { + from_ghost_idxs.insert(i); + this->alphas[i] = 1.0; + } + else if (is_ghost(clj1)) + { + to_ghost_idxs.insert(i); + this->perturbed->alphas[i] = 1.0; + } + } + } + QVector> bond_params_1; bond_params_1.reserve(bond_params.count()); - QVector found_index(perturbed->bond_params.count(), false); + QVector found_index_0(bond_params.count(), false); + QVector found_index_1(perturbed->bond_params.count(), false); - for (const auto &bond0 : bond_params) + for (int i = 0; i < bond_params.count(); ++i) { + const auto &bond0 = bond_params.at(i); + int atom0 = std::get<0>(bond0); int atom1 = std::get<1>(bond0); bool found = false; - for (int i = 0; i < perturbed->bond_params.count(); ++i) + for (int j = 0; j < perturbed->bond_params.count(); ++j) { - const auto &bond1 = perturbed->bond_params.at(i); - - if (std::get<0>(bond1) == atom0 and std::get<1>(bond1) == atom1) + if (not found_index_1[j]) { - // we have found the matching bond! - bond_params_1.append(bond1); - found_index[i] = true; - found = true; - break; + const auto &bond1 = perturbed->bond_params.at(j); + + if (std::get<0>(bond1) == atom0 and std::get<1>(bond1) == atom1) + { + // we have found the matching bond! + bond_params_1.append(bond1); + found_index_0[i] = true; + found_index_1[j] = true; + found = true; + break; + } } } if (not found) { // add a null bond with the same r0, but null k + found_index_0[i] = true; bond_params_1.append(std::tuple(atom0, atom1, std::get<2>(bond0), 0.0)); } } - for (int i = 0; i < perturbed->bond_params.count(); ++i) + for (int j = 0; j < perturbed->bond_params.count(); ++j) { - if (not found_index[i]) + if (not found_index_1[j]) { // need to add a bond missing in the reference state - const auto &bond1 = perturbed->bond_params.at(i); + const auto &bond1 = perturbed->bond_params.at(j); int atom0 = std::get<0>(bond1); int atom1 = std::get<1>(bond1); @@ -645,51 +711,69 @@ void OpenMMMolecule::alignInternals() // add a null bond with the same r0, but null k bond_params.append(std::tuple(atom0, atom1, std::get<2>(bond1), 0.0)); bond_params_1.append(bond1); + + found_index_1[j] = true; } } + // all of found_index_0 and found_index_1 should be true... + if (found_index_0.indexOf(false) != -1 or found_index_1.indexOf(false) != -1) + { + throw SireError::program_bug(QObject::tr( + "Failed to align the bonds!"), + CODELOC); + } + perturbed->bond_params = bond_params_1; QVector> ang_params_1; ang_params_1.reserve(ang_params.count()); - found_index = QVector(perturbed->ang_params.count(), false); + found_index_0 = QVector(ang_params.count(), false); + found_index_1 = QVector(perturbed->ang_params.count(), false); - for (const auto &ang0 : ang_params) + for (int i = 0; i < ang_params.count(); ++i) { + const auto &ang0 = ang_params.at(i); + int atom0 = std::get<0>(ang0); int atom1 = std::get<1>(ang0); int atom2 = std::get<2>(ang0); bool found = false; - for (int i = 0; i < perturbed->ang_params.count(); ++i) + for (int j = 0; j < perturbed->ang_params.count(); ++j) { - const auto &ang1 = perturbed->ang_params.at(i); - - if (std::get<0>(ang1) == atom0 and std::get<1>(ang1) == atom1 and std::get<2>(ang1) == atom2) + if (not found_index_1[j]) { - // we have found the matching angle! - ang_params_1.append(ang1); - found_index[i] = true; - found = true; - break; + const auto &ang1 = perturbed->ang_params.at(j); + + if (std::get<0>(ang1) == atom0 and std::get<1>(ang1) == atom1 and std::get<2>(ang1) == atom2) + { + // we have found the matching angle! + ang_params_1.append(ang1); + found_index_0[i] = true; + found_index_1[j] = true; + found = true; + break; + } } } if (not found) { // add a null angle with the same theta0, but null k + found_index_0[i] = true; ang_params_1.append(std::tuple(atom0, atom1, atom2, std::get<3>(ang0), 0.0)); } } - for (int i = 0; i < perturbed->ang_params.count(); ++i) + for (int j = 0; j < perturbed->ang_params.count(); ++j) { - if (not found_index[i]) + if (not found_index_1[j]) { // need to add a bond missing in the reference state - const auto &ang1 = perturbed->ang_params.at(i); + const auto &ang1 = perturbed->ang_params.at(j); int atom0 = std::get<0>(ang1); int atom1 = std::get<1>(ang1); @@ -698,18 +782,31 @@ void OpenMMMolecule::alignInternals() // add a null angle with the same theta0, but null k ang_params.append(std::tuple(atom0, atom1, atom2, std::get<3>(ang1), 0.0)); ang_params_1.append(ang1); + + found_index_1[j] = true; } } + // all of found_index_0 and found_index_1 should be true... + if (found_index_0.indexOf(false) != -1 or found_index_1.indexOf(false) != -1) + { + throw SireError::program_bug(QObject::tr( + "Failed to align the angles!"), + CODELOC); + } + perturbed->ang_params = ang_params_1; QVector> dih_params_1; dih_params_1.reserve(dih_params.count()); - found_index = QVector(perturbed->dih_params.count(), false); + found_index_0 = QVector(dih_params.count(), false); + found_index_1 = QVector(perturbed->dih_params.count(), false); - for (const auto &dih0 : dih_params) + for (int i = 0; i < dih_params.count(); ++i) { + const auto &dih0 = dih_params.at(i); + int atom0 = std::get<0>(dih0); int atom1 = std::get<1>(dih0); int atom2 = std::get<2>(dih0); @@ -717,18 +814,22 @@ void OpenMMMolecule::alignInternals() bool found = false; - for (int i = 0; i < perturbed->dih_params.count(); ++i) + for (int j = 0; j < perturbed->dih_params.count(); ++j) { - const auto &dih1 = perturbed->dih_params.at(i); - - if (std::get<0>(dih1) == atom0 and std::get<1>(dih1) == atom1 and - std::get<2>(dih1) == atom2 and std::get<3>(dih1) == atom3) + if (not found_index_1[j]) { - // we have found the matching bond! - dih_params_1.append(dih1); - found_index[i] = true; - found = true; - break; + const auto &dih1 = perturbed->dih_params.at(j); + + if (std::get<0>(dih1) == atom0 and std::get<1>(dih1) == atom1 and + std::get<2>(dih1) == atom2 and std::get<3>(dih1) == atom3) + { + // we have found the matching bond! + dih_params_1.append(dih1); + found_index_0[i] = true; + found_index_1[j] = true; + found = true; + break; + } } } @@ -736,15 +837,16 @@ void OpenMMMolecule::alignInternals() { // add a null dihedral with the same periodicity and phase, but null k dih_params_1.append(std::tuple(atom0, atom1, atom2, atom3, std::get<4>(dih0), std::get<5>(dih0), 0.0)); + found_index_0[i] = true; } } - for (int i = 0; i < perturbed->dih_params.count(); ++i) + for (int j = 0; j < perturbed->dih_params.count(); ++j) { - if (not found_index[i]) + if (not found_index_1[j]) { // need to add a bond missing in the reference state - const auto &dih1 = perturbed->dih_params.at(i); + const auto &dih1 = perturbed->dih_params.at(j); int atom0 = std::get<0>(dih1); int atom1 = std::get<1>(dih1); @@ -754,9 +856,18 @@ void OpenMMMolecule::alignInternals() // add a null dihedral with the same periodicity and phase, but null k dih_params.append(std::tuple(atom0, atom1, atom2, atom3, std::get<4>(dih1), std::get<5>(dih1), 0.0)); dih_params_1.append(dih1); + found_index_1[j] = true; } } + // all of found_index_0 and found_index_1 should be true... + if (found_index_0.indexOf(false) != -1 or found_index_1.indexOf(false) != -1) + { + throw SireError::program_bug(QObject::tr( + "Failed to align the dihedrals!"), + CODELOC); + } + perturbed->dih_params = dih_params_1; // we may need to align the the standard_14_pairs @@ -764,6 +875,283 @@ void OpenMMMolecule::alignInternals() // if the bonding changes across the perturbation! } +void OpenMMMolecule::buildStandardExceptions(const Molecule &mol, + const AmberParams ¶ms, + QSet &constrained_pairs, + const PropertyMap &map) +{ + exception_params.clear(); + + // add all of the 1-4 terms + for (auto it = params.nb14s().constBegin(); + it != params.nb14s().constEnd(); + ++it) + { + const double cscl = it.value().cscl(); + const double ljscl = it.value().ljscl(); + + const auto nbid = it.key().map(molinfo); + + const int atom0 = nbid.get<0>().value(); + const int atom1 = nbid.get<1>().value(); + + exception_params.append(std::make_tuple( + atom0, atom1, cscl, ljscl)); + } + + // add in all of the excluded atoms + const auto excl_pairs = ExcludedPairs(mol, map); + + if (excl_pairs.count() > 0) + { + for (int i = 0; i < excl_pairs.count(); ++i) + { + auto pair = excl_pairs[i]; + + exception_params.append(std::make_tuple( + std::get<0>(pair).value(), std::get<1>(pair).value(), + 0.0, 0.0)); + } + + // and finally (finally!) find any atoms that are not bonded to + // anything else and make sure that they are constrained. These + // atoms will be excluded atoms (by definition) so just look + // through those + const auto &connectivity = mol.property(map["connectivity"]).asA(); + + for (int i = 0; i < excl_pairs.count(); ++i) + { + auto pair = excl_pairs[i]; + + const int atom0 = std::get<0>(pair).value(); + const int atom1 = std::get<1>(pair).value(); + + if (not constrained_pairs.contains(to_pair(atom0, atom1))) + { + if (connectivity.nConnections(std::get<0>(pair)) == 0 or + connectivity.nConnections(std::get<1>(pair)) == 0) + { + const auto delta = coords[atom1] - coords[atom0]; + const auto length = std::sqrt((delta[0] * delta[0]) + + (delta[1] * delta[1]) + + (delta[2] * delta[2])); + constraints.append(std::make_tuple(atom0, atom1, length)); + constrained_pairs.insert(to_pair(atom0, atom1)); + } + } + } + } +} + +void OpenMMMolecule::buildPerturbableExceptions(const Molecule &mol, + QSet &constrained_pairs, + const PropertyMap &map) +{ + // we will build the complete exception list, and will not rely + // on this list being built by an OpenMM forcefield. This is because + // we need to allow this set to morph + exception_params.clear(); + + const int nats = this->cljs.count(); + + const auto &nbpairs = mol.property(map["intrascale"]).asA(); + const auto &connectivity = mol.property(map["connectivity"]).asA(); + + const int ncgs = mol.nCutGroups(); + + auto add_exception = [&](const AtomIdx &atom0, const AtomIdx &atom1, + double cscl, double ljscl) + { + if (cscl != 1 or ljscl != 1) + { + const int i = atom0.value(); + const int j = atom1.value(); + + exception_params.append(std::make_tuple(i, j, cscl, ljscl)); + + if (cscl == 0 and ljscl == 0) + { + // are any of these atoms unbonded? If so, then + // we will need to add a constraint to hold them + // in place + if (connectivity.nConnections(AtomIdx(i)) == 0 or + connectivity.nConnections(AtomIdx(j)) == 0) + { + if (not constrained_pairs.contains(to_pair(i, j))) + { + const auto delta = coords[j] - coords[i]; + const auto length = std::sqrt((delta[0] * delta[0]) + + (delta[1] * delta[1]) + + (delta[2] * delta[2])); + constraints.append(std::make_tuple(i, j, length)); + constrained_pairs.insert(to_pair(i, j)); + } + } + } + } + }; + + // loop over all pairs of CutGroups and get the NB scale factors + if (ncgs == 1) + { + if (nats == 1) + { + // nothing to do :-) + } + else if (nats == 2) + { + // two atoms must be excluded + exception_params.append(std::make_tuple(0, 1, 0.0, 0.0)); + } + else if (nats <= 3) + { + // three atoms must be excluded + exception_params.append(std::make_tuple(0, 1, 0.0, 0.0)); + exception_params.append(std::make_tuple(1, 2, 0.0, 0.0)); + exception_params.append(std::make_tuple(0, 2, 0.0, 0.0)); + } + else + { + // we only need to worry about ourselves + const auto &cgpairs = nbpairs.get(CGIdx(0), CGIdx(0)); + + if (cgpairs.isEmpty()) + { + // all of the pairs have the same value (surprising!) + const auto &cljscl = cgpairs.defaultValue(); + + if (cljscl.coulomb() != 1 or cljscl.lj() != 1) + { + for (int i = 0; i < nats - 1; ++i) + { + for (int j = i + 1; j < nats; ++j) + { + add_exception(AtomIdx(i), AtomIdx(j), + cljscl.coulomb(), cljscl.lj()); + } + } + } + } + else + { + // the pairs have different values, so add these in + for (int i = 0; i < nats - 1; ++i) + { + for (int j = i + 1; j < nats; ++j) + { + const auto &scl = cgpairs.get(i, j); + + if (scl.coulomb() != 1 or scl.lj() != 1) + { + add_exception(AtomIdx(i), AtomIdx(j), + scl.coulomb(), scl.lj()); + } + } + } + } + } + } + else + { + const auto &molinfo = mol.data().info(); + + // do all individual cutgroups + for (int icg = 0; icg < ncgs; ++icg) + { + const int i_nats = molinfo.nAtoms(CGIdx(icg)); + + const auto &cgpairs = nbpairs.get(CGIdx(icg), CGIdx(icg)); + + if (cgpairs.isEmpty()) + { + const auto &scl = cgpairs.defaultValue(); + + if (scl.coulomb() != 1 or scl.lj() != 1) + { + // all of the pairs are excluded for all atoms + for (int i = 0; i < i_nats - 1; ++i) + { + for (int j = i + 1; j < i_nats; ++j) + { + add_exception(molinfo.atomIdx(CGAtomIdx(CGIdx(icg), Index(i))), + molinfo.atomIdx(CGAtomIdx(CGIdx(icg), Index(j))), + scl.coulomb(), scl.lj()); + } + } + } + } + else + { + // all of the pairs are excluded for all atoms + for (int i = 0; i < i_nats - 1; ++i) + { + for (int j = i + 1; j < i_nats; ++j) + { + const auto &scl = cgpairs.get(i, j); + + if (scl.coulomb() != 1 or scl.lj() != 1) + { + add_exception(molinfo.atomIdx(CGAtomIdx(CGIdx(icg), Index(i))), + molinfo.atomIdx(CGAtomIdx(CGIdx(icg), Index(j))), + scl.coulomb(), scl.lj()); + } + } + } + } + } + + // do all cutgroup pairs + for (int icg = 0; icg < ncgs - 1; ++icg) + { + const int i_nats = molinfo.nAtoms(CGIdx(icg)); + + for (int jcg = icg + 1; jcg < ncgs; ++jcg) + { + const int j_nats = molinfo.nAtoms(CGIdx(jcg)); + + const auto &cgpairs = nbpairs.get(CGIdx(icg), CGIdx(jcg)); + + if (cgpairs.isEmpty()) + { + const auto &scl = cgpairs.defaultValue(); + + if (scl.coulomb() != 1 or scl.lj() != 1) + { + // all of the pairs are excluded for all atoms + for (int i = 0; i < i_nats; ++i) + { + for (int j = 0; j < j_nats; ++j) + { + add_exception(molinfo.atomIdx(CGAtomIdx(CGIdx(icg), Index(i))), + molinfo.atomIdx(CGAtomIdx(CGIdx(jcg), Index(j))), + scl.coulomb(), scl.lj()); + } + } + } + } + else + { + // all of the pairs are excluded for all atoms + for (int i = 0; i < i_nats; ++i) + { + for (int j = 0; j < j_nats; ++j) + { + const auto &scl = cgpairs.get(i, j); + + if (scl.coulomb() != 1 or scl.lj() != 1) + { + add_exception(molinfo.atomIdx(CGAtomIdx(CGIdx(icg), Index(i))), + molinfo.atomIdx(CGAtomIdx(CGIdx(jcg), Index(j))), + scl.coulomb(), scl.lj()); + } + } + } + } + } + } + } +} + void OpenMMMolecule::copyInCoordsAndVelocities(OpenMM::Vec3 *c, OpenMM::Vec3 *v) const { const auto *coords_data = coords.constData(); @@ -793,6 +1181,11 @@ void OpenMMMolecule::copyInCoordsAndVelocities(OpenMM::Vec3 *c, OpenMM::Vec3 *v) } } +QVector OpenMMMolecule::getAlphas() const +{ + return this->alphas; +} + QVector OpenMMMolecule::getCharges() const { const int natoms = this->cljs.count(); diff --git a/wrapper/Convert/SireOpenMM/openmmmolecule.h b/wrapper/Convert/SireOpenMM/openmmmolecule.h index 954b545a8..022876c65 100644 --- a/wrapper/Convert/SireOpenMM/openmmmolecule.h +++ b/wrapper/Convert/SireOpenMM/openmmmolecule.h @@ -10,6 +10,7 @@ #include "SireMM/mmdetail.h" #include "SireMM/excludedpairs.h" +#include "SireMM/amberparams.h" SIRE_BEGIN_HEADER @@ -51,6 +52,7 @@ namespace SireOpenMM QVector getCharges() const; QVector getSigmas() const; QVector getEpsilons() const; + QVector getAlphas() const; QVector getBondKs() const; QVector getBondLengths() const; @@ -62,10 +64,13 @@ namespace SireOpenMM QVector getTorsionPhases() const; QVector getTorsionKs() const; - std::tuple getException(int atom0, int atom1, - int start_index, - double coul_14_scl, - double lj_14_scl) const; + bool isGhostAtom(int atom) const; + + std::tuple + getException(int atom0, int atom1, + int start_index, + double coul_14_scl, + double lj_14_scl) const; /** All the member data is public as this is an internal * class. This class should not be used outside of @@ -73,6 +78,7 @@ namespace SireOpenMM * * Any values are in the internal units of OpenMM */ + SireMol::MolNum number; /** The molecule info that contains metadata about the molecule */ SireMol::MoleculeInfo molinfo; @@ -82,9 +88,6 @@ namespace SireOpenMM */ SireMol::Selector atoms; - /** All of the excluded atom pairs */ - SireMM::ExcludedPairs excl_pairs; - /** The forcefield info that contains metadata about the parameters */ SireMM::MMDetail ffinfo; @@ -130,16 +133,48 @@ namespace SireOpenMM /** The indicies of the added exceptions - only populated * if this is a peturbable molecule */ - QHash> exception_idxs; + QHash>> exception_idxs; + + /** The property map used to get the perturbable properties - + * this is only non-default if the molecule is perturbable + */ + SireBase::PropertyMap perturtable_map; + + /** Alpha values for all of the atoms. This is equal to zero for + * non-ghost atoms, and one for ghost atoms + */ + QVector alphas; + + /** The indexes of atoms that become ghosts in the + * perturbed state + */ + QSet to_ghost_idxs; + + /** The indexes of atoms that are ghosts in the reference + * state and are real in the perturbed state + */ + QSet from_ghost_idxs; /** What type of constraint to use */ qint32 constraint_type; private: void constructFromAmber(const SireMol::Molecule &mol, - const SireBase::PropertyMap &map); + const SireMM::AmberParams ¶ms, + const SireMM::AmberParams ¶ms1, + const SireBase::PropertyMap &map, + bool is_perturbable); + + void buildStandardExceptions(const SireMol::Molecule &mol, + const SireMM::AmberParams ¶ms, + QSet &constrained_pairs, + const SireBase::PropertyMap &map); + + void buildPerturbableExceptions(const SireMol::Molecule &mol, + QSet &constrained_pairs, + const SireBase::PropertyMap &map); - void alignInternals(); + void alignInternals(const SireBase::PropertyMap &map); }; } diff --git a/wrapper/Convert/SireOpenMM/sire_openmm.cpp b/wrapper/Convert/SireOpenMM/sire_openmm.cpp index 5877186e2..f2f58de39 100644 --- a/wrapper/Convert/SireOpenMM/sire_openmm.cpp +++ b/wrapper/Convert/SireOpenMM/sire_openmm.cpp @@ -27,6 +27,8 @@ #include "SireVol/periodicbox.h" #include "SireVol/triclinicbox.h" +#include "SireCAS/lambdaschedule.h" + #include "SireMaths/vector.h" #include "SireBase/parallel.h" @@ -43,25 +45,14 @@ #include using SireBase::PropertyMap; +using SireCAS::LambdaSchedule; using SireMol::Molecule; +using SireMol::MolNum; using SireMol::SelectorMol; using SireSystem::ForceFieldInfo; namespace SireOpenMM { - bool use_parallel(int n, const SireBase::PropertyMap &map) - { - if (n <= 16) - return false; - - if (map["parallel"].hasValue()) - { - return map["parallel"].value().asABoolean(); - } - - return true; - } - //// //// Implementation of OpenMMMetaData //// @@ -152,459 +143,22 @@ namespace SireOpenMM return SelectorMol(); } - OpenMMMetaData sire_to_openmm_system(OpenMM::System &system, - const SelectorMol &mols, - const PropertyMap &map) - { - // we can assume that an empty system has been passed to us - - // we will get parameters from the map (e.g. cutoffs etc) - ForceFieldInfo ffinfo(mols, map); - - // extract the data from all of the molecules - const int nmols = mols.count(); - - if (nmols == 0) - { - // nothing to do - return OpenMMMetaData(); - } - - // whether or not to ignore perturbations - bool ignore_perturbations = false; - bool any_perturbable = false; - - if (map.specified("ignore_perturbations")) - { - ignore_perturbations = map["ignore_perturbations"].value().asABoolean(); - } - - // Extract all of the data needed by OpenMM from the Sire - // molecules into some temporary OpenMMMolecule objects - QVector openmm_mols(nmols); - auto openmm_mols_data = openmm_mols.data(); - - if (use_parallel(nmols, map)) - { - tbb::parallel_for(tbb::blocked_range(0, mols.count()), [&](const tbb::blocked_range &r) - { - for (int i=r.begin(); isetUseDispersionCorrection(use_dispersion_correction); - - // create the periodic box vectors - std::shared_ptr> boxvecs; - - if (ffinfo.space().isPeriodic()) - { - boxvecs.reset(new std::vector(3)); - auto boxvecs_data = boxvecs->data(); - - if (ffinfo.space().isA()) - { - const auto &space = ffinfo.space().asA(); - - const double x = space.dimensions()[0] * OpenMM::NmPerAngstrom; - const double y = space.dimensions()[1] * OpenMM::NmPerAngstrom; - const double z = space.dimensions()[2] * OpenMM::NmPerAngstrom; - - boxvecs_data[0] = OpenMM::Vec3(x, 0, 0); - boxvecs_data[1] = OpenMM::Vec3(0, y, 0); - boxvecs_data[2] = OpenMM::Vec3(0, 0, z); - } - else if (ffinfo.space().isA()) - { - const auto &space = ffinfo.space().asA(); - - // Get the three triclinic box vectors. - const auto v0 = space.vector0(); - const auto v1 = space.vector1(); - const auto v2 = space.vector2(); - - // Get cell matrix components in nm. - const double xx = v0.x() * OpenMM::NmPerAngstrom; - const double xy = v0.y() * OpenMM::NmPerAngstrom; - const double xz = v0.z() * OpenMM::NmPerAngstrom; - const double yx = v1.x() * OpenMM::NmPerAngstrom; - const double yy = v1.y() * OpenMM::NmPerAngstrom; - const double yz = v1.z() * OpenMM::NmPerAngstrom; - const double zx = v2.x() * OpenMM::NmPerAngstrom; - const double zy = v2.y() * OpenMM::NmPerAngstrom; - const double zz = v2.z() * OpenMM::NmPerAngstrom; - - boxvecs_data[0] = OpenMM::Vec3(xx, xy, xz); - boxvecs_data[1] = OpenMM::Vec3(yx, yy, yz); - boxvecs_data[2] = OpenMM::Vec3(zx, zy, zz); - } - - system.setDefaultPeriodicBoxVectors(boxvecs_data[0], - boxvecs_data[1], - boxvecs_data[2]); - } - - if (ffinfo.hasCutoff()) - { - const auto typ = ffinfo.cutoffType(); - - // need to set cutoff for perturbable forcefields - - if (typ == "PME" or typ == "EWALD") - { - if (not ffinfo.space().isPeriodic()) - { - throw SireError::incompatible_error(QObject::tr( - "You cannot use Ewald or PME with the non-periodic space %1.") - .arg(ffinfo.space().toString()), - CODELOC); - } - - auto nbmethod = OpenMM::NonbondedForce::PME; - - if (typ != "PME") - nbmethod = OpenMM::NonbondedForce::Ewald; - - cljff->setNonbondedMethod(nbmethod); - - double tolerance = ffinfo.getParameter("tolerance").value(); - - if (tolerance <= 0) - tolerance = 0.001; - - cljff->setEwaldErrorTolerance(tolerance); - } - else if (typ == "REACTION_FIELD") - { - auto nbmethod = OpenMM::NonbondedForce::CutoffPeriodic; - - if (not ffinfo.space().isPeriodic()) - { - nbmethod = OpenMM::NonbondedForce::CutoffNonPeriodic; - } - - auto dielectric = ffinfo.getParameter("dielectric").value(); - - if (dielectric <= 0) - dielectric = 78.3; - - cljff->setNonbondedMethod(nbmethod); - cljff->setReactionFieldDielectric(dielectric); - } - else if (typ == "CUTOFF") - { - // use reaction field for non-periodic spaces, and PME for periodic - if (ffinfo.space().isPeriodic()) - { - const auto nbmethod = OpenMM::NonbondedForce::PME; - cljff->setNonbondedMethod(nbmethod); - - double tolerance = ffinfo.getParameter("tolerance").value(); - - if (tolerance <= 0) - tolerance = 0.001; - - cljff->setEwaldErrorTolerance(tolerance); - } - else - { - const auto nbmethod = OpenMM::NonbondedForce::CutoffNonPeriodic; - cljff->setNonbondedMethod(nbmethod); - - double dielectric = ffinfo.getParameter("dielectric").value(); - - if (dielectric <= 0) - dielectric = 78.3; - - cljff->setReactionFieldDielectric(dielectric); - } - } - - const auto cutoff = ffinfo.cutoff().to(SireUnits::nanometers); - cljff->setCutoffDistance(cutoff); - } - - // also populate a LambaLever for any perturbable molecules - LambdaLever lambda_lever; - - // make sure that we tell the lever the index of the named - // forcefields when they are added - lambda_lever.setForceIndex("clj", system.addForce(cljff)); - - OpenMM::HarmonicBondForce *bondff = new OpenMM::HarmonicBondForce(); - lambda_lever.setForceIndex("bond", system.addForce(bondff)); - - OpenMM::HarmonicAngleForce *angff = new OpenMM::HarmonicAngleForce(); - lambda_lever.setForceIndex("angle", system.addForce(angff)); - - OpenMM::PeriodicTorsionForce *dihff = new OpenMM::PeriodicTorsionForce(); - lambda_lever.setForceIndex("torsion", system.addForce(dihff)); - - // Now copy data from the temporary OpenMMMolecule objects - // into these forcefields - // (will deal with restraints, light atoms and virtual sites later) - - int start_index = 0; - - std::vector> bond_pairs; - std::vector> exception_params; - - // get the 1-4 scaling factors from the first molecule - const double coul_14_scl = openmm_mols_data[0].ffinfo.electrostatic14ScaleFactor(); - const double lj_14_scl = openmm_mols_data[0].ffinfo.vdw14ScaleFactor(); - - QVector start_indexes(nmols); - - // save the atoms in the order they are added to the system - SireMol::SelectorM atom_index; - - for (int i = 0; i < nmols; ++i) - { - start_indexes[i] = start_index; - const auto &mol = openmm_mols_data[i]; - - // add all of the atoms to the index of atoms - // (we guarantee to add them in atomidx order) - atom_index += mol.atoms; - - if (std::abs(mol.ffinfo.electrostatic14ScaleFactor() - coul_14_scl) > 0.001 or - std::abs(mol.ffinfo.vdw14ScaleFactor() - lj_14_scl) > 0.001) - { - throw SireError::incompatible_error(QObject::tr( - "We cannot create the OpenMM system because the forcefields " - "of %1 and %2 (%3 and %4) are not compatible. They have " - "different 1-4 non-bonded scale factors.") - .arg(mols[0].toString()) - .arg(mols[i].toString()) - .arg(openmm_mols_data[0].ffinfo.toString()) - .arg(mol.ffinfo.toString()), - CODELOC); - } - - QHash start_indicies; - - // is this a perturbable molecule (and we haven't disabled perturbations)? - if (any_perturbable and mol.isPerturbable()) - { - // add a perturbable molecule, recording the start index - // for each of the forcefields - start_indicies.reserve(5); - - start_indicies.insert("clj", start_index); - start_indicies.insert("bond", bondff->getNumBonds()); - start_indicies.insert("angle", angff->getNumAngles()); - start_indicies.insert("torsion", dihff->getNumTorsions()); - - lambda_lever.addPerturbableMolecule(mol, start_indicies); - } - - // first the atom parameters - auto masses_data = mol.masses.constData(); - auto cljs_data = mol.cljs.constData(); - - for (int j = 0; j < mol.molinfo.nAtoms(); ++j) - { - system.addParticle(masses_data[j]); - const int atom_index = start_index + j; - - const auto &clj = cljs_data[j]; - - cljff->addParticle(std::get<0>(clj), std::get<1>(clj), std::get<2>(clj)); - } - - for (const auto &bond : mol.bond_pairs) - { - bond_pairs.push_back(std::make_pair(std::get<0>(bond) + start_index, - std::get<1>(bond) + start_index)); - } - - // now bond parameters - for (const auto &bond : mol.bond_params) - { - bondff->addBond(std::get<0>(bond) + start_index, - std::get<1>(bond) + start_index, - std::get<2>(bond), std::get<3>(bond)); - } - - // now the angles - for (const auto &ang : mol.ang_params) - { - angff->addAngle(std::get<0>(ang) + start_index, - std::get<1>(ang) + start_index, - std::get<2>(ang) + start_index, - std::get<3>(ang), std::get<4>(ang)); - } - - // now the dihedrals and impropers - for (const auto &dih : mol.dih_params) - { - dihff->addTorsion(std::get<0>(dih) + start_index, - std::get<1>(dih) + start_index, - std::get<2>(dih) + start_index, - std::get<3>(dih) + start_index, - std::get<4>(dih), std::get<5>(dih), std::get<6>(dih)); - } - - // now constraints - for (const auto &constraint : mol.constraints) - { - system.addConstraint(std::get<0>(constraint) + start_index, - std::get<1>(constraint) + start_index, - std::get<2>(constraint)); - } - - start_index += mol.masses.count(); - } - - const int natoms = start_index; - - // add exclusions based on the bonding of the molecules - cljff->createExceptionsFromBonds(bond_pairs, coul_14_scl, lj_14_scl); - - // now exceptions based on the 1-4 and excluded parameters - // in the molecules - for (int i = 0; i < nmols; ++i) - { - int start_index = start_indexes[i]; - const auto &mol = openmm_mols_data[i]; - - QVector exception_idxs; - - const bool is_perturbable = any_perturbable and mol.isPerturbable(); - - if (is_perturbable) - { - exception_idxs = QVector(mol.exception_params.count(), -1); - } - - for (int j = 0; j < mol.exception_params.count(); ++j) - { - const auto ¶m = mol.exception_params[j]; - - auto p = mol.getException(std::get<0>(param), - std::get<1>(param), - start_index, - std::get<2>(param), - std::get<3>(param)); - - int idx = cljff->addException(std::get<0>(p), std::get<1>(p), - std::get<2>(p), std::get<3>(p), - std::get<4>(p), true); - - if (is_perturbable) - { - exception_idxs[j] = idx; - } - } - - if (is_perturbable) - { - lambda_lever.setExceptionIndicies(i, "clj", exception_idxs); - } - } - - // see if we want to remove COM motion - const auto com_remove_prop = map["com_reset_frequency"]; - - if (com_remove_prop.hasValue()) - { - const int freq = com_remove_prop.value().asAnInteger(); - - if (freq > 0) - { - OpenMM::CMMotionRemover *com_remover = new OpenMM::CMMotionRemover(freq); - system.addForce(com_remover); - } - } - - // now get the coordinates and velocities - std::shared_ptr> coords, vels; - - coords.reset(new std::vector(natoms)); - vels.reset(new std::vector(natoms)); - - auto coords_data = coords->data(); - auto vels_data = vels->data(); - - const int *start_indexes_data = start_indexes.constData(); - - if (use_parallel(nmols, map)) - { - tbb::parallel_for(tbb::blocked_range(0, nmols), [&](tbb::blocked_range r) - { - for (int i=r.begin(); i &perturbable_maps, + const SireBase::PropertyMap &map) { const auto positions = state.getPositions(); - const auto velocities = state.getVelocities(); const int natoms = positions.size(); const auto positions_data = positions.data(); - const auto velocities_data = velocities.data(); - if (mols.nAtoms() != natoms) + if (mols.nAtoms() > natoms) { throw SireError::incompatible_error(QObject::tr( "Different number of atoms from OpenMM and sire. " @@ -720,7 +296,6 @@ namespace SireOpenMM } const auto coords_prop = map["coordinates"]; - const auto vels_prop = map["velocity"]; const int nmols = mols.count(); @@ -739,12 +314,11 @@ namespace SireOpenMM const auto offsets_data = offsets.constData(); - if (use_parallel(nmols, map)) + if (SireBase::should_run_in_parallel(nmols, map)) { tbb::parallel_for(tbb::blocked_range(0, nmols), [&](const tbb::blocked_range &r) { QVector converted_coords; - QVector converted_vels; for (int i=r.begin(); i(); - } - else - { - mol_coords = SireMol::AtomCoords(mol.data().info()); + my_coords_prop = perturbable_maps[mol.number()]["coordinates"].source(); } - if (mol.data().hasProperty(vels_prop)) + if (not mol.updatePropertyFrom(my_coords_prop, + converted_coords, false)) { - mol_vels = mol.data().property(vels_prop).asA(); + SireMol::AtomCoords c(mol.data().info()); + c.copyFrom(converted_coords); + mol.setProperty(my_coords_prop, c); } - else - { - mol_vels = SireMol::AtomVelocities(mol.data().info()); - } - - mol_coords.copyFrom(converted_coords); - mol_vels.copyFrom(converted_vels); - - mol.setProperty(coords_prop.source(), mol_coords); - mol.setProperty(vels_prop.source(), mol_vels); ret_data[i] = mol.commit(); } }); @@ -787,7 +348,6 @@ namespace SireOpenMM else { QVector converted_coords; - QVector converted_vels; for (int i = 0; i < nmols; ++i) { @@ -795,35 +355,22 @@ namespace SireOpenMM const int mol_natoms = mol.nAtoms(); _populate_coords(converted_coords, positions_data + offsets_data[i], mol_natoms); - _populate_vels(converted_vels, velocities_data + offsets_data[i], mol_natoms); - SireMol::AtomCoords mol_coords; - SireMol::AtomVelocities mol_vels; + auto my_coords_prop = coords_prop.source(); - if (mol.data().hasProperty(coords_prop)) - { - mol_coords = mol.data().property(coords_prop).asA(); - } - else + if (perturbable_maps.contains(mol.number())) { - mol_coords = SireMol::AtomCoords(mol.data().info()); + my_coords_prop = perturbable_maps[mol.number()]["coordinates"].source(); } - if (mol.data().hasProperty(vels_prop)) - { - mol_vels = mol.data().property(vels_prop).asA(); - } - else + if (not mol.updatePropertyFrom(my_coords_prop, + converted_coords, false)) { - mol_vels = SireMol::AtomVelocities(mol.data().info()); + SireMol::AtomCoords c(mol.data().info()); + c.copyFrom(converted_coords); + mol.setProperty(my_coords_prop, c); } - mol_coords.copyFrom(converted_coords); - mol_vels.copyFrom(converted_vels); - - mol.setProperty(coords_prop.source(), mol_coords); - mol.setProperty(vels_prop.source(), mol_vels); - ret_data[i] = mol.commit(); } } @@ -831,39 +378,19 @@ namespace SireOpenMM return SelectorMol(ret); } - void set_context_platform_property(OpenMM::Context &context, - const QString &key, - const QString &value) - { - OpenMM::Platform &platform = context.getPlatform(); - - platform.setPropertyValue(context, - key.toStdString(), - value.toStdString()); - - QString new_value = QString::fromStdString(platform.getPropertyValue(context, key.toStdString())); - - if (new_value != value) - throw SireError::incompatible_error(QObject::tr( - "Unable to change the value of property %1 to `%2` in the " - "platform %3. The property value is still '%4'.") - .arg(key) - .arg(value) - .arg(QString::fromStdString(platform.getName())) - .arg(new_value), - CODELOC); - } - - SelectorMol extract_coordinates(const OpenMM::State &state, - const SelectorMol &mols, - const PropertyMap &map) + SelectorMol extract_coordinates_and_velocities(const OpenMM::State &state, + const SireMol::SelectorMol &mols, + const QHash &perturbable_maps, + const SireBase::PropertyMap &map) { const auto positions = state.getPositions(); + const auto velocities = state.getVelocities(); const int natoms = positions.size(); const auto positions_data = positions.data(); + const auto velocities_data = velocities.data(); - if (mols.nAtoms() != natoms) + if (mols.nAtoms() > natoms) { throw SireError::incompatible_error(QObject::tr( "Different number of atoms from OpenMM and sire. " @@ -874,6 +401,7 @@ namespace SireOpenMM } const auto coords_prop = map["coordinates"]; + const auto vels_prop = map["velocity"]; const int nmols = mols.count(); @@ -892,33 +420,45 @@ namespace SireOpenMM const auto offsets_data = offsets.constData(); - if (use_parallel(nmols, map)) + if (SireBase::should_run_in_parallel(nmols, map)) { tbb::parallel_for(tbb::blocked_range(0, nmols), [&](const tbb::blocked_range &r) { QVector converted_coords; + QVector converted_vels; for (int i=r.begin(); i(); + my_coords_prop = perturbable_maps[mol.number()]["coordinates"].source(); + my_vels_prop = perturbable_maps[mol.number()]["velocity"].source(); } - else + + _populate_coords(converted_coords, positions_data+offsets_data[i], mol_natoms); + _populate_vels(converted_vels, velocities_data+offsets_data[i], mol_natoms); + + if (not mol.updatePropertyFrom(my_coords_prop, + converted_coords, false)) { - mol_coords = SireMol::AtomCoords(mol.data().info()); + SireMol::AtomCoords c(mol.data().info()); + c.copyFrom(converted_coords); + mol.setProperty(my_coords_prop, c); } - mol_coords.copyFrom(converted_coords); - - mol.setProperty(coords_prop.source(), mol_coords); + if (not mol.updatePropertyFrom(my_vels_prop, + converted_vels, false)) + { + SireMol::AtomVelocities v(mol.data().info()); + v.copyFrom(converted_vels); + mol.setProperty(my_vels_prop, v); + } ret_data[i] = mol.commit(); } }); @@ -926,28 +466,40 @@ namespace SireOpenMM else { QVector converted_coords; + QVector converted_vels; for (int i = 0; i < nmols; ++i) { auto mol = mols[i].edit(); const int mol_natoms = mol.nAtoms(); - _populate_coords(converted_coords, positions_data + offsets_data[i], mol_natoms); + auto my_coords_prop = coords_prop.source(); + auto my_vels_prop = vels_prop.source(); - SireMol::AtomCoords mol_coords; - - if (mol.data().hasProperty(coords_prop)) + if (perturbable_maps.contains(mol.number())) { - mol_coords = mol.data().property(coords_prop).asA(); + my_coords_prop = perturbable_maps[mol.number()]["coordinates"].source(); + my_vels_prop = perturbable_maps[mol.number()]["velocity"].source(); } - else + + _populate_coords(converted_coords, positions_data + offsets_data[i], mol_natoms); + _populate_vels(converted_vels, velocities_data + offsets_data[i], mol_natoms); + + if (not mol.updatePropertyFrom(my_coords_prop, + converted_coords, false)) { - mol_coords = SireMol::AtomCoords(mol.data().info()); + SireMol::AtomCoords c(mol.data().info()); + c.copyFrom(converted_coords); + mol.setProperty(my_coords_prop, c); } - mol_coords.copyFrom(converted_coords); - - mol.setProperty(coords_prop.source(), mol_coords); + if (not mol.updatePropertyFrom(my_vels_prop, + converted_vels, false)) + { + SireMol::AtomVelocities v(mol.data().info()); + v.copyFrom(converted_vels); + mol.setProperty(my_vels_prop, v); + } ret_data[i] = mol.commit(); } diff --git a/wrapper/Convert/SireOpenMM/sire_openmm.h b/wrapper/Convert/SireOpenMM/sire_openmm.h index 178ecef4a..9e9dbd760 100644 --- a/wrapper/Convert/SireOpenMM/sire_openmm.h +++ b/wrapper/Convert/SireOpenMM/sire_openmm.h @@ -9,6 +9,7 @@ #include "SireMol/core.h" #include "SireMol/selectorm.hpp" #include "SireMol/atom.h" +#include "SireMol/molnum.h" #include "SireBase/propertymap.h" @@ -82,10 +83,12 @@ namespace SireOpenMM SireMol::SelectorMol extract_coordinates(const OpenMM::State &state, const SireMol::SelectorMol &mols, + const QHash &perturbable_maps, const SireBase::PropertyMap &map); SireMol::SelectorMol extract_coordinates_and_velocities(const OpenMM::State &state, const SireMol::SelectorMol &mols, + const QHash &perturbable_maps, const SireBase::PropertyMap &map); SireVol::SpacePtr extract_space(const OpenMM::State &state); diff --git a/wrapper/Convert/SireOpenMM/sire_to_openmm_system.cpp b/wrapper/Convert/SireOpenMM/sire_to_openmm_system.cpp new file mode 100644 index 000000000..9c775561e --- /dev/null +++ b/wrapper/Convert/SireOpenMM/sire_to_openmm_system.cpp @@ -0,0 +1,1589 @@ + +#include "sire_openmm.h" + +#include + +#include "SireStream/datastream.h" +#include "SireStream/shareddatastream.h" + +#include "SireSystem/forcefieldinfo.h" + +#include "SireMol/core.h" +#include "SireMol/moleditor.h" +#include "SireMol/atomelements.h" +#include "SireMol/atomcharges.h" +#include "SireMol/atomcoords.h" +#include "SireMol/atommasses.h" +#include "SireMol/atomproperty.hpp" +#include "SireMol/connectivity.h" +#include "SireMol/bondid.h" +#include "SireMol/bondorder.h" +#include "SireMol/atomvelocities.h" + +#include "SireMM/atomljs.h" +#include "SireMM/selectorbond.h" +#include "SireMM/amberparams.h" +#include "SireMM/bondrestraints.h" +#include "SireMM/positionalrestraints.h" +#include "SireMM/boreschrestraints.h" + +#include "SireVol/periodicbox.h" +#include "SireVol/triclinicbox.h" + +#include "SireCAS/lambdaschedule.h" + +#include "SireMaths/vector.h" + +#include "SireBase/parallel.h" +#include "SireBase/propertylist.h" +#include "SireBase/lengthproperty.h" +#include "SireBase/generalunitproperty.h" + +#include "SireUnits/units.h" + +#include "SireError/errors.h" + +#include "tostring.h" + +#include "openmmmolecule.h" + +#include + +using SireBase::PropertyMap; +using SireCAS::LambdaSchedule; +using SireMol::Molecule; +using SireMol::MolNum; +using SireMol::SelectorMol; +using SireSystem::ForceFieldInfo; + +using namespace SireOpenMM; + +/** Add all of the Boresch restraints from 'restraints' to the + * passed systen, which is being acted on by the passed LambdaLever. + * The number of real (non-anchor) atoms in the OpenMM::System is 'natoms' + */ +void _add_boresch_restraints(const SireMM::BoreschRestraints &restraints, + OpenMM::System &system, LambdaLever &lambda_lever, + int natoms) +{ + if (restraints.isEmpty()) + return; + + // energy expression of the Boresch restraint, which acts over + // six atoms - this is a set of + // one distance restraint, two angle restraints and three + // torsion restraints between the three receptor (particles 0 to 2) + // and three ligand atoms (particles 3 to 5) + // + // r is | Ligand1 - Receptor1 | = distance(P1, P4) + // thetaA = angle(R2, R1, L1) = angle(P2, P1, P4) + // thetaB = angle(R1, L1, L2) = angle(P1, P4, P5) + // phiA = dihedral(R3, R2, R1, L1) = dihedral(P3, P2, P1, P4) + // phiB = dihedral(R2, R1, L1, L2) = dihedral(P2, P1, P4, P5) + // phiC = dihedral(R1, L1, L2, L3) = dihedral(P1, P4, P5, P6) + // + // Then the energies are + // + // e_restraint = rho * (e_bond + e_angle + e_torsion) + // e_bond = kr (r - r0)^2 + // e_angle_i = ktheta_i (theta_i - theta0_i)^2 + // e_torsion_i = k_phi_i (min(dphi_i, 2pi-dphi_i))^2 where + // dphi_i = abs(phi_i - phi0_i) + // + const auto energy_expression = QString( + "rho*(e_bond + e_angle_A + e_angle_B + e_torsion_A + e_torsion_B + e_torsion_C);" + "e_bond=kr*(r-r0)^2;" + "e_angle_B=ktheta_B*(theta_B-theta0_B)^2;" + "e_angle_A=ktheta_A*(theta_A-theta0_A)^2;" + "e_torsion_C=kphi_C*(min(dphi_C, two_pi-dphi_C))^2;" + "e_torsion_B=kphi_B*(min(dphi_B, two_pi-dphi_B))^2;" + "e_torsion_A=kphi_A*(min(dphi_A, two_pi-dphi_A))^2;" + "dphi_C=abs(phi_C-phi0_C);" + "dphi_B=abs(phi_B-phi0_B);" + "dphi_A=abs(phi_A-phi0_A);" + "two_pi=6.283185307179586;" + "phi_C=dihedral(p1, p4, p5, p6);" + "phi_B=dihedral(p2, p1, p4, p5);" + "phi_A=dihedral(p3, p2, p1, p4);" + "theta_B=angle(p1, p4, p5);" + "theta_A=angle(p2, p1, p4);" + "r=distance(p1, p4);") + .toStdString(); + + auto *restraintff = new OpenMM::CustomCompoundBondForce(6, energy_expression); + + restraintff->addPerBondParameter("rho"); + restraintff->addPerBondParameter("kr"); + restraintff->addPerBondParameter("r0"); + restraintff->addPerBondParameter("ktheta_A"); + restraintff->addPerBondParameter("theta0_A"); + restraintff->addPerBondParameter("ktheta_B"); + restraintff->addPerBondParameter("theta0_B"); + restraintff->addPerBondParameter("kphi_A"); + restraintff->addPerBondParameter("phi0_A"); + restraintff->addPerBondParameter("kphi_B"); + restraintff->addPerBondParameter("phi0_B"); + restraintff->addPerBondParameter("kphi_C"); + restraintff->addPerBondParameter("phi0_C"); + + restraintff->setUsesPeriodicBoundaryConditions(true); + + lambda_lever.addRestraintIndex(restraints.name(), + system.addForce(restraintff)); + + const double internal_to_nm = (1 * SireUnits::angstrom).to(SireUnits::nanometer); + const double internal_to_k = (1 * SireUnits::kcal_per_mol / (SireUnits::angstrom2)).to(SireUnits::kJ_per_mol / SireUnits::nanometer2); + const double internal_to_ktheta = (1 * SireUnits::kcal_per_mol / (SireUnits::radian2)).to(SireUnits::kJ_per_mol / SireUnits::radian2); + + for (const auto &restraint : restraints.restraints()) + { + if (restraint.isNull()) + continue; + + std::vector particles; + particles.resize(6); + + std::vector parameters; + parameters.resize(13); + + for (int i = 0; i < 3; ++i) + { + particles[i] = restraint.receptorAtoms()[i]; + particles[i + 3] = restraint.ligandAtoms()[i]; + + if (particles[i] < 0 or particles[i] >= natoms or + particles[i + 3] < 0 or particles[i + 3] >= natoms) + { + throw SireError::invalid_index(QObject::tr( + "Invalid particle indicies (ligand=%1, receptor=2) " + "for a Boresch restraint for %3 atoms.") + .arg(Sire::toString(restraint.ligandAtoms())) + .arg(Sire::toString(restraint.receptorAtoms())) + .arg(natoms), + CODELOC); + } + } + + parameters[0] = 1.0; // rho + parameters[1] = restraint.kr().value() * internal_to_k; // kr + parameters[2] = restraint.r0().value() * internal_to_nm; // r0 + parameters[3] = restraint.ktheta()[0].value() * internal_to_ktheta; // ktheta_A + parameters[4] = restraint.theta0()[0].value(); // theta0_A (already in radians) + parameters[5] = restraint.ktheta()[1].value() * internal_to_ktheta; // ktheta_B + parameters[6] = restraint.theta0()[1].value(); // theta0_B + parameters[7] = restraint.kphi()[0].value() * internal_to_ktheta; // kphi_A + parameters[8] = restraint.phi0()[0].value(); // phi0_A + parameters[9] = restraint.kphi()[1].value() * internal_to_ktheta; // kphi_B + parameters[10] = restraint.phi0()[1].value(); // phi0_B + parameters[11] = restraint.kphi()[2].value() * internal_to_ktheta; // kphi_C + parameters[12] = restraint.phi0()[2].value(); // phi0_C + + restraintff->addBond(particles, parameters); + } +} + +/** Add all of the bond restraints from 'restraints' to the passed + * system, which is acted on by the passed LambdaLever. The number + * of real (non-anchor) atoms in the OpenMM::System is 'natoms' + */ +void _add_bond_restraints(const SireMM::BondRestraints &restraints, + OpenMM::System &system, LambdaLever &lambda_lever, + int natoms) +{ + if (restraints.isEmpty()) + return; + + if (restraints.hasCentroidRestraints()) + { + throw SireError::unsupported(QObject::tr( + "Centroid bond restraints aren't yet supported..."), + CODELOC); + } + + // energy expression of a harmonic bond potential, scaled by rho + const auto energy_expression = QString( + "rho*k*delta*delta;" + "delta=(r-r0)") + .toStdString(); + + auto *restraintff = new OpenMM::CustomBondForce(energy_expression); + + restraintff->addPerBondParameter("rho"); + restraintff->addPerBondParameter("k"); + restraintff->addPerBondParameter("r0"); + + restraintff->setUsesPeriodicBoundaryConditions(true); + + lambda_lever.addRestraintIndex(restraints.name(), + system.addForce(restraintff)); + + const auto atom_restraints = restraints.atomRestraints(); + + const double internal_to_nm = (1 * SireUnits::angstrom).to(SireUnits::nanometer); + const double internal_to_k = (1 * SireUnits::kcal_per_mol / (SireUnits::angstrom2)).to(SireUnits::kJ_per_mol / (SireUnits::nanometer2)); + + auto cljff = lambda_lever.getForce("clj", system); + + std::vector custom_params = {1.0, 0.0, 0.0}; + + for (const auto &restraint : atom_restraints) + { + int atom0_index = restraint.atom0(); + int atom1_index = restraint.atom1(); + + if (atom0_index < 0 or atom0_index >= natoms) + throw SireError::invalid_index(QObject::tr( + "Invalid particle index! %1 from %2") + .arg(atom0_index) + .arg(natoms), + CODELOC); + + if (atom1_index < 0 or atom1_index >= natoms) + throw SireError::invalid_index(QObject::tr( + "Invalid particle index! %1 from %2") + .arg(atom1_index) + .arg(natoms), + CODELOC); + + custom_params[0] = 1.0; // rho - always equal to 1 (scaled by lever) + custom_params[1] = restraint.k().value() * internal_to_k; // k + custom_params[2] = restraint.r0().value() * internal_to_nm; // rb + + restraintff->addBond(atom0_index, atom1_index, custom_params); + } +} + +/** Add all of the positional restraints from 'restraints' to the passed + * system, which is acted on by the passed LambdaLever. All of the + * existing anchor atoms are in 'anchor_coords', which this function + * will add to if any more need adding. The number of real (non-anchor) + * atoms in the OpenMM::System is 'natoms' + */ +void _add_positional_restraints(const SireMM::PositionalRestraints &restraints, + OpenMM::System &system, LambdaLever &lambda_lever, + std::vector &anchor_coords, + int natoms) +{ + if (restraints.isEmpty()) + return; + + if (restraints.hasCentroidRestraints()) + { + throw SireError::unsupported(QObject::tr( + "Centroid positional restraints aren't yet supported..."), + CODELOC); + } + + // energy expression of a flat-bottom well potential, scaled by rho + const auto energy_expression = QString( + "rho*k*step(delta)*delta*delta;" + "delta=(r-rb)") + .toStdString(); + + auto *restraintff = new OpenMM::CustomBondForce(energy_expression); + + restraintff->addPerBondParameter("rho"); + restraintff->addPerBondParameter("k"); + restraintff->addPerBondParameter("rb"); + + restraintff->setUsesPeriodicBoundaryConditions(true); + + lambda_lever.addRestraintIndex(restraints.name(), + system.addForce(restraintff)); + + const auto atom_restraints = restraints.atomRestraints(); + + anchor_coords.reserve(anchor_coords.size() + atom_restraints.count()); + + auto to_vec3 = [](const SireMaths::Vector &coords) + { + const double internal_to_nm = (1 * SireUnits::angstrom).to(SireUnits::nanometer); + + return OpenMM::Vec3(internal_to_nm * coords.x(), + internal_to_nm * coords.y(), + internal_to_nm * coords.z()); + }; + + QVector anchor_idxs; + + const double internal_to_nm = (1 * SireUnits::angstrom).to(SireUnits::nanometer); + const double internal_to_k = (1 * SireUnits::kcal_per_mol / (SireUnits::angstrom2)).to(SireUnits::kJ_per_mol / (SireUnits::nanometer2)); + + auto cljff = lambda_lever.getForce("clj", system); + auto ghost_ghostff = lambda_lever.getForce("ghost/ghost", system); + auto ghost_nonghostff = lambda_lever.getForce("ghost/non-ghost", system); + + std::vector custom_params = {1.0, 0.0, 0.0}; + std::vector custom_clj_params = {0.0, 0.0, 0.0, 0.0}; + + // we need to add all of the positions as anchor particles + for (const auto &restraint : atom_restraints) + { + int atom_index = restraint.atom(); + + if (atom_index < 0 or atom_index >= natoms) + throw SireError::invalid_index(QObject::tr( + "Invalid particle index! %1 from %2") + .arg(atom_index) + .arg(natoms), + CODELOC); + + // find the anchor at this position + int anchor_index = anchor_idxs.indexOf(restraint.position()); + + if (anchor_index != -1) + { + anchor_index += natoms; + } + else if (anchor_index == -1) + { + // doesn't exist - create it as a massless particle + // (won't be moved) + anchor_index = system.addParticle(0.0); + anchor_coords.push_back(to_vec3(restraint.position())); + anchor_idxs.append(restraint.position()); + + // add a null particle to the nonbonded forces + if (cljff != 0) + { + cljff->addParticle(0, 0, 0); + } + + if (ghost_ghostff != 0) + { + ghost_ghostff->addParticle(custom_clj_params); + } + + if (ghost_nonghostff != 0) + { + ghost_nonghostff->addParticle(custom_clj_params); + } + } + + custom_params[0] = 1.0; // rho - always equal to 1 (scaled by lever) + custom_params[1] = restraint.k().value() * internal_to_k; // k + custom_params[2] = restraint.r0().value() * internal_to_nm; // rb + + if (cljff != 0) + { + // make sure to exclude interactions between + // the atom being positionally restrained and + // the anchor + cljff->addException(anchor_index, atom_index, 0, 0, 0, true); + } + + if (ghost_ghostff != 0) + { + // make sure to exclude interactions between + // the atom being positionally restrained and + // the anchor + ghost_ghostff->addExclusion(anchor_index, atom_index); + } + + if (ghost_nonghostff != 0) + { + // make sure to exclude interactions between + // the atom being positionally restrained and + // the anchor + ghost_nonghostff->addExclusion(anchor_index, atom_index); + } + + restraintff->addBond(anchor_index, atom_index, custom_params); + } +} + +/** Set the coulomb and LJ cutoff in the passed NonbondedForce, + * based on the information in the passed ForceFieldInfo. + * This sets the cutoff type (e.g. PME) and the actual + * cutoff length (if one is used) + */ +void _set_clj_cutoff(OpenMM::NonbondedForce &cljff, + const ForceFieldInfo &ffinfo) +{ + if (ffinfo.hasCutoff()) + { + const auto typ = ffinfo.cutoffType(); + + // need to set cutoff for perturbable forcefields + + if (typ == "PME" or typ == "EWALD") + { + if (not ffinfo.space().isPeriodic()) + { + throw SireError::incompatible_error(QObject::tr( + "You cannot use Ewald or PME with the non-periodic space %1.") + .arg(ffinfo.space().toString()), + CODELOC); + } + + auto nbmethod = OpenMM::NonbondedForce::PME; + + if (typ != "PME") + nbmethod = OpenMM::NonbondedForce::Ewald; + + cljff.setNonbondedMethod(nbmethod); + + double tolerance = ffinfo.getParameter("tolerance").value(); + + if (tolerance <= 0) + tolerance = 0.001; + + cljff.setEwaldErrorTolerance(tolerance); + } + else if (typ == "REACTION_FIELD") + { + auto nbmethod = OpenMM::NonbondedForce::CutoffPeriodic; + + if (not ffinfo.space().isPeriodic()) + { + nbmethod = OpenMM::NonbondedForce::CutoffNonPeriodic; + } + + auto dielectric = ffinfo.getParameter("dielectric").value(); + + if (dielectric <= 0) + dielectric = 78.3; + + cljff.setNonbondedMethod(nbmethod); + cljff.setReactionFieldDielectric(dielectric); + } + else if (typ == "CUTOFF") + { + // use reaction field for non-periodic spaces, and PME for periodic + if (ffinfo.space().isPeriodic()) + { + const auto nbmethod = OpenMM::NonbondedForce::PME; + cljff.setNonbondedMethod(nbmethod); + + double tolerance = ffinfo.getParameter("tolerance").value(); + + if (tolerance <= 0) + tolerance = 0.001; + + cljff.setEwaldErrorTolerance(tolerance); + } + else + { + const auto nbmethod = OpenMM::NonbondedForce::CutoffNonPeriodic; + cljff.setNonbondedMethod(nbmethod); + + double dielectric = ffinfo.getParameter("dielectric").value(); + + if (dielectric <= 0) + dielectric = 78.3; + + cljff.setReactionFieldDielectric(dielectric); + } + } + + const auto cutoff = ffinfo.cutoff().to(SireUnits::nanometers); + cljff.setCutoffDistance(cutoff); + } +} + +/** Set the periodic space box vectors in the system, returning + * them if they are set (we don't do anything for non-periodic + * spaces) + */ +std::shared_ptr> +_set_box_vectors(OpenMM::System &system, + const ForceFieldInfo &ffinfo) +{ + // create the periodic box vectors + std::shared_ptr> boxvecs; + + if (ffinfo.space().isPeriodic()) + { + boxvecs.reset(new std::vector(3)); + auto boxvecs_data = boxvecs->data(); + + if (ffinfo.space().isA()) + { + const auto &space = ffinfo.space().asA(); + + const double x = space.dimensions()[0] * OpenMM::NmPerAngstrom; + const double y = space.dimensions()[1] * OpenMM::NmPerAngstrom; + const double z = space.dimensions()[2] * OpenMM::NmPerAngstrom; + + boxvecs_data[0] = OpenMM::Vec3(x, 0, 0); + boxvecs_data[1] = OpenMM::Vec3(0, y, 0); + boxvecs_data[2] = OpenMM::Vec3(0, 0, z); + } + else if (ffinfo.space().isA()) + { + const auto &space = ffinfo.space().asA(); + + // Get the three triclinic box vectors. + const auto v0 = space.vector0(); + const auto v1 = space.vector1(); + const auto v2 = space.vector2(); + + // Get cell matrix components in nm. + const double xx = v0.x() * OpenMM::NmPerAngstrom; + const double xy = v0.y() * OpenMM::NmPerAngstrom; + const double xz = v0.z() * OpenMM::NmPerAngstrom; + const double yx = v1.x() * OpenMM::NmPerAngstrom; + const double yy = v1.y() * OpenMM::NmPerAngstrom; + const double yz = v1.z() * OpenMM::NmPerAngstrom; + const double zx = v2.x() * OpenMM::NmPerAngstrom; + const double zy = v2.y() * OpenMM::NmPerAngstrom; + const double zz = v2.z() * OpenMM::NmPerAngstrom; + + boxvecs_data[0] = OpenMM::Vec3(xx, xy, xz); + boxvecs_data[1] = OpenMM::Vec3(yx, yy, yz); + boxvecs_data[2] = OpenMM::Vec3(zx, zy, zz); + } + + system.setDefaultPeriodicBoxVectors(boxvecs_data[0], + boxvecs_data[1], + boxvecs_data[2]); + } + + return boxvecs; +} + +/** + +This is the (monster) function that converts a passed set of Sire +molecules (in the passed SelectorMols) into an OpenMM::System, +controlled via the properties in the passed PropertyMap. + +The OpenMM::System is constructed in the passed (empty) +OpenMM::System that is the first argument. This is because this +function is called from Python, and this was the only way found to +have the resulting OpenMM::System make its way back up to the +Python layer. + +This returns an extra set of metadata that doesn't fit into the +OpenMM::System. This metadata includes information about any +perturbations, the atom index, plus the coordinates and velocities +from the molecules (if these could be found) + +This is a monster function, as it does need to do everything, and +the parts of not easily decomposable (they need information from +a prior part that could be passed as function arguments, but +would be messy). + +This function is best read as a sequence of stages. These stages +are commented within the function. The stages are: + +1. Initialisation - copying molecular data from sire into OpenMMMolecule + +2. Create base forces (forcefields) + +3. Define the LambdaLever and LambdaSchedule + +4. Define the forces (forcefields) for the ghost atoms + +5. Copy atomistic forcefield parameters to the OpenMM forces + +6. Set up the nonbonded pair exceptions + +7. Set up the restraints + +8. Copy across all of the coordinates and velocities + +*/ +OpenMMMetaData SireOpenMM::sire_to_openmm_system(OpenMM::System &system, + const SelectorMol &mols, + const PropertyMap &map) +{ + /// + /// Stage 1 - initialisation. + /// + /// Aim is to copy all of the molecular information from the + /// sire SelectorMol and put it into a set of OpenMMMolecule + /// objects ready for that information to then be fed into the + /// OpenMM::System. + /// + /// We assume that a valid, empty OpenMM::System has been + /// passed to us + /// + + // We get the forcefield parameters from the map (e.g. cutoffs etc) + ForceFieldInfo ffinfo(mols, map); + + // Some initialisation - do we have anything to convert? + const int nmols = mols.count(); + + if (nmols == 0) + { + // nothing to do + return OpenMMMetaData(); + } + + // set the box vectors for periodic spaces + auto boxvecs = _set_box_vectors(system, ffinfo); + + // Are any of the molecules perturbable, and if they are, + // should we just ignore perturbations? + bool ignore_perturbations = false; + bool any_perturbable = false; + + if (map.specified("ignore_perturbations")) + { + ignore_perturbations = map["ignore_perturbations"].value().asABoolean(); + } + + // Extract all of the data needed by OpenMM from the Sire + // molecules into some temporary OpenMMMolecule objects + QVector openmm_mols(nmols); + auto openmm_mols_data = openmm_mols.data(); + + if (SireBase::should_run_in_parallel(nmols, map)) + { + tbb::parallel_for(tbb::blocked_range(0, mols.count()), [&](const tbb::blocked_range &r) + { + for (int i=r.begin(); i fixed_atoms; + + if (map.specified("fixed")) + { + // this should be a list of indexes of atoms to fix + const auto idxs = map["fixed"].value().asA().toVector(); + + if (not idxs.isEmpty()) + { + fixed_atoms.reserve(idxs.count()); + + for (const auto &idx : idxs) + { + fixed_atoms.insert(idx); + } + } + } + + // End of stage 1 - we have now extracted all of the molecular information + // and have worked out what parameters to use and whether any of the + // molecules are perturbable + + /// + /// Stage 2 - Base Forces + /// + /// Aim is to create the base forces, e.g. Nonbonded, Bond, Angle, + /// Torsion. These forces are created and parameterised via the + /// PropertyMap and ForceFieldInfo (e.g. cutoff type etc) + /// + + // Force for the Coulomb and LJ (CLJ) energy between + // all non-perturbable atoms + OpenMM::NonbondedForce *cljff = new OpenMM::NonbondedForce(); + + bool use_dispersion_correction = false; + + if (map.specified("use_dispersion_correction")) + { + use_dispersion_correction = map["use_dispersion_correction"].value().asABoolean(); + } + + // note that this will be very slow for perturbable systems, as + // it needs recalculating for every change of lambda + cljff->setUseDispersionCorrection(use_dispersion_correction); + + // set the non-bonded cutoff type and length based on + // the infomation in ffinfo + _set_clj_cutoff(*cljff, ffinfo); + + // now create the base bond, angle and torsion forcefields + OpenMM::HarmonicBondForce *bondff = new OpenMM::HarmonicBondForce(); + OpenMM::HarmonicAngleForce *angff = new OpenMM::HarmonicAngleForce(); + OpenMM::PeriodicTorsionForce *dihff = new OpenMM::PeriodicTorsionForce(); + + // end of stage 2 - we now have the base forces + + /// + /// Stage 3 - define the LambdaLever and LambdaSchedule + /// + /// Aim is to create the lambda lever that perturbs forcefield + /// potentials from the reference state to the perturbed state. + /// This lever exists even if there are no perturbable molecules, + /// as it provides useful metadata about the OpenMM::System. + /// + + // First, create the LambdaLever + LambdaLever lambda_lever; + + // Let the user supply their own schedule for any perturbations + if (map.specified("schedule")) + { + lambda_lever.setSchedule( + map["schedule"].value().asA()); + } + else if (any_perturbable) + { + // use a standard morph if we have an alchemical perturbation + lambda_lever.setSchedule( + LambdaSchedule::standard_morph()); + } + + // We can now add the standard forces to the OpenMM::System. + // We do this here, so that we can capture the index of the + // force and associate it with a name in the lever. + lambda_lever.setForceIndex("clj", system.addForce(cljff)); + + // We also want to name the levers available for this force, + // e.g. we can change the charge, sigma and epsilon parameters + lambda_lever.addLever("charge"); + lambda_lever.addLever("sigma"); + lambda_lever.addLever("epsilon"); + + // Do the same for the bond, angle and torsion forces + lambda_lever.setForceIndex("bond", system.addForce(bondff)); + lambda_lever.addLever("bond_length"); + lambda_lever.addLever("bond_k"); + + lambda_lever.setForceIndex("angle", system.addForce(angff)); + lambda_lever.addLever("angle_size"); + lambda_lever.addLever("angle_k"); + + lambda_lever.setForceIndex("torsion", system.addForce(dihff)); + lambda_lever.addLever("torsion_phase"); + lambda_lever.addLever("torsion_periodicity"); + lambda_lever.addLever("torsion_k"); + + /// + /// Stage 4 - define the forces for ghost atoms + /// + /// We now need to create the forces that enable the use of a + /// softening potential that smooths the interactions of + /// any ghost atoms that are created or deleted. We skip + /// this bit if there are no ghost atoms + /// + + OpenMM::CustomBondForce *ghost_14ff = 0; + OpenMM::CustomNonbondedForce *ghost_ghostff = 0; + OpenMM::CustomNonbondedForce *ghost_nonghostff = 0; + + if (any_perturbable) + { + lambda_lever.addLever("alpha"); + + // somd uses a default shift_delta of 2.0 A + SireUnits::Dimension::Length shift_delta = 2.0 * SireUnits::angstrom; + + if (map.specified("shift_delta")) + { + const auto &value = map["shift_delta"].value(); + + if (value.isA()) + shift_delta = value.asA().value(); + else + shift_delta = value.asA().toUnit(); + } + + if (shift_delta.value() < 0) + shift_delta = 0.0 * SireUnits::angstrom; + + // use a Taylor LJ power of 1 + int taylor_power = 1; + + if (map.specified("taylor_power")) + { + taylor_power = map["taylor_power"].value().asAnInteger(); + } + + if (taylor_power < 0) + taylor_power = 0; + else if (taylor_power > 4) + taylor_power = 4; + + // by default we use zacharias softening + bool use_taylor_softening = false; + + if (map.specified("use_taylor_softening")) + { + use_taylor_softening = map["use_taylor_softening"].value().asABoolean(); + } + + if (map.specified("use_zacharias_softening")) + { + use_taylor_softening = not map["use_zacharias_softening"].value().asABoolean(); + } + + int coulomb_power = 0; + + if (map.specified("coulomb_power")) + { + coulomb_power = map["coulomb_power"].value().asAnInteger(); + } + + if (coulomb_power < 0) + coulomb_power = 0; + else if (coulomb_power > 4) + coulomb_power = 4; + + auto coulomb_power_expression = [](const QString &alpha, int power) + { + if (power == 0) + return QString("1"); + else if (power == 1) + return QString("(1-%1)").arg(alpha); + else if (power == 2) + return QString("(1-%1)*(1-%1)").arg(alpha); + else + return QString("(1-%1)^%2").arg(alpha).arg(power); + }; + + auto taylor_power_expression = [](const QString &alpha, int power) + { + if (power == 0) + return QString("1"); + else if (power == 1) + return QString("%1").arg(alpha); + else if (power == 2) + return QString("%1*%1").arg(alpha); + else + return QString("%1^%2").arg(alpha).arg(power); + }; + + // see below for the description of this energy expression + std::string nb14_expression, clj_expression; + + if (use_taylor_softening) + { + nb14_expression = QString( + "coul_nrg+lj_nrg;" + "coul_nrg=138.9354558466661*q*(((%1)/sqrt(alpha+r^2))-(1.0/r));" + "lj_nrg=four_epsilon*((sig6^2)-sig6);" + "sig6=(sigma^6)/(%2*sigma^6 + r^6);") + .arg(coulomb_power_expression("alpha", coulomb_power)) + .arg(taylor_power_expression("alpha", taylor_power)) + .toStdString(); + } + else + { + nb14_expression = QString( + "coul_nrg+lj_nrg;" + "coul_nrg=138.9354558466661*q*(((%1)/sqrt(alpha+r^2))-(1.0/r));" + "lj_nrg=four_epsilon*((sig6^2)-sig6);" + "sig6=(sigma^6)/(((sigma*delta) + r^2)^3);" + "delta=%2*alpha;") + .arg(coulomb_power_expression("alpha", coulomb_power)) + .arg(shift_delta.to(SireUnits::nanometer)) + .toStdString(); + } + + ghost_14ff = new OpenMM::CustomBondForce(nb14_expression); + + ghost_14ff->addPerBondParameter("q"); + ghost_14ff->addPerBondParameter("sigma"); + ghost_14ff->addPerBondParameter("four_epsilon"); + ghost_14ff->addPerBondParameter("alpha"); + + // short-range intramolecular term that should not use + // periodic boundaries or cutoffs + ghost_14ff->setUsesPeriodicBoundaryConditions(false); + + if (use_taylor_softening) + { + // this uses the following potentials + // Zacharias and McCammon, J. Chem. Phys., 1994, and also, + // Michel et al., JCTC, 2007 + // LJ is Rich Taylor's softcore LJ + // + // V_{LJ}(r) = 4 epsilon [ (sigma^12 / (alpha^m sigma^6 + r^6)^2) - + // (sigma^6 / (alpha^m sigma^6 + r^6) ) ] + // + // V_{coul}(r) = (1-alpha)^n q_i q_j / 4 pi eps_0 (alpha+r^2)^(1/2) + // + // Note that we supply half_sigma and two_sqrt_epsilon to save some + // cycles + // + // Note also that we subtract the normal coulomb energy as this + // is calculated during the standard NonbondedForce + // + // 138.9354558466661 is the constant needed to get energies in + // kJ mol-1 given the units of charge (|e|) and distance (nm) + // + clj_expression = QString("coul_nrg+lj_nrg;" + "coul_nrg=138.9354558466661*q1*q2*(((%1)/sqrt(max_alpha+r^2))-(1.0/r));" + "lj_nrg=two_sqrt_epsilon1*two_sqrt_epsilon2*((sig6^2)-sig6);" + "sig6=(sigma^6)/(%2*sigma^6 + r^6);" + "max_alpha=max(alpha1, alpha2);" + "sigma=half_sigma1+half_sigma2;") + .arg(coulomb_power_expression("max_alpha", coulomb_power)) + .arg(taylor_power_expression("max_alpha", taylor_power)) + .toStdString(); + } + else + { + // this uses the following potentials + // Zacharias and McCammon, J. Chem. Phys., 1994, and also, + // Michel et al., JCTC, 2007 + // + // V_{LJ}(r) = 4 epsilon [ ( sigma^12 / (delta*sigma + r^2)^6 ) - + // ( sigma^6 / (delta*sigma + r^2)^3 ) ] + // + // delta = shift_delta * alpha + // + // V_{coul}(r) = (1-alpha)^n q_i q_j / 4 pi eps_0 (alpha+r^2)^(1/2) + // + // Note that we pre-calculate delta as a forcefield parameter, + // and also supply half_sigma and two_sqrt_epsilon to save some + // cycles + // + // Note also that we subtract the normal coulomb energy as this + // is calculated during the standard NonbondedForce + // + // 138.9354558466661 is the constant needed to get energies in + // kJ mol-1 given the units of charge (|e|) and distance (nm) + // + clj_expression = QString("coul_nrg+lj_nrg;" + "coul_nrg=138.9354558466661*q1*q2*(((%1)/sqrt(max_alpha+r^2))-(1.0/r));" + "lj_nrg=two_sqrt_epsilon1*two_sqrt_epsilon2*((sig6^2)-sig6);" + "sig6=(sigma^6)/(((sigma*delta) + r^2)^3);" + "delta=%2*max_alpha;" + "max_alpha=max(alpha1, alpha2);" + "sigma=half_sigma1+half_sigma2;") + .arg(coulomb_power_expression("max_alpha", coulomb_power)) + .arg(shift_delta.to(SireUnits::nanometer)) + .toStdString(); + } + + ghost_ghostff = new OpenMM::CustomNonbondedForce(clj_expression); + ghost_nonghostff = new OpenMM::CustomNonbondedForce(clj_expression); + + ghost_ghostff->addPerParticleParameter("q"); + ghost_ghostff->addPerParticleParameter("half_sigma"); + ghost_ghostff->addPerParticleParameter("two_sqrt_epsilon"); + ghost_ghostff->addPerParticleParameter("alpha"); + + ghost_nonghostff->addPerParticleParameter("q"); + ghost_nonghostff->addPerParticleParameter("half_sigma"); + ghost_nonghostff->addPerParticleParameter("two_sqrt_epsilon"); + ghost_nonghostff->addPerParticleParameter("alpha"); + + // this will be slow if switched on, as it needs recalculating + // for every change in parameters + ghost_ghostff->setUseLongRangeCorrection(use_dispersion_correction); + ghost_nonghostff->setUseLongRangeCorrection(use_dispersion_correction); + + if (ffinfo.hasCutoff()) + { + if (ffinfo.space().isPeriodic()) + { + ghost_ghostff->setNonbondedMethod(OpenMM::CustomNonbondedForce::CutoffPeriodic); + ghost_nonghostff->setNonbondedMethod(OpenMM::CustomNonbondedForce::CutoffPeriodic); + } + else + { + ghost_ghostff->setNonbondedMethod(OpenMM::CustomNonbondedForce::CutoffNonPeriodic); + ghost_nonghostff->setNonbondedMethod(OpenMM::CustomNonbondedForce::CutoffNonPeriodic); + } + + ghost_ghostff->setCutoffDistance(ffinfo.cutoff().to(SireUnits::nanometers)); + ghost_nonghostff->setCutoffDistance(ffinfo.cutoff().to(SireUnits::nanometers)); + } + else + { + ghost_ghostff->setNonbondedMethod(OpenMM::CustomNonbondedForce::NoCutoff); + ghost_nonghostff->setNonbondedMethod(OpenMM::CustomNonbondedForce::NoCutoff); + } + + lambda_lever.setForceIndex("ghost/ghost", system.addForce(ghost_ghostff)); + lambda_lever.setForceIndex("ghost/non-ghost", system.addForce(ghost_nonghostff)); + lambda_lever.setForceIndex("ghost-14", system.addForce(ghost_14ff)); + } + + // Stage 4 is complete. We now have all(*) of the forces we need to run + // a perturbable simulation. (*) well, we will define the restraint + // forces in a much later stage after the particles have been added. + + /// + /// Stage 5 - Copy in the atomic forcefield parameters + /// + /// We now go through and copy the atomic forcefield parameters from + /// the data in the temporary OpenMMMolecule objects into the + /// OpenMM forces that we've just created. + /// + + // start_index keeps track of the index of the first atom in each molecule + int start_index = 0; + + // get the 1-4 scaling factors from the first molecule + const double coul_14_scl = openmm_mols_data[0].ffinfo.electrostatic14ScaleFactor(); + const double lj_14_scl = openmm_mols_data[0].ffinfo.vdw14ScaleFactor(); + + // this maps from molecule index to the start_index for the first + // particle in that molecule + QVector start_indexes(nmols); + + // the index to the perturbable molecule for the specified molecule + // (i.e. the 5th perturbable molecule is the 10th molecule in the System) + QHash idx_to_pert_idx; + + // just a holder for all of the custom parameters for the + // ghost forces (prevents us having to continually re-allocate it) + std::vector custom_params = {0.0, 0.0, 0.0, 0.0}; + + // the sets of particle indexes for the ghost atoms and non-ghost atoms + std::set ghost_atoms; + std::set non_ghost_atoms; + + // the set of all ghost atoms, with the value + // indicating if this is a from-ghost (true) or + // a to-ghost (false) + QHash ghost_is_from; + + // loop over every molecule and add them one by one + for (int i = 0; i < nmols; ++i) + { + // save the index in the OpenMM system of the first + // particle for the first atom in this molecule + start_indexes[i] = start_index; + const auto &mol = openmm_mols_data[i]; + + // double-check that the molecule has a compatible forcefield with + // the other molecules in this system + if (std::abs(mol.ffinfo.electrostatic14ScaleFactor() - coul_14_scl) > 0.001 or + std::abs(mol.ffinfo.vdw14ScaleFactor() - lj_14_scl) > 0.001) + { + throw SireError::incompatible_error(QObject::tr( + "We cannot create the OpenMM system because the forcefields " + "of %1 and %2 (%3 and %4) are not compatible. They have " + "different 1-4 non-bonded scale factors.") + .arg(mols[0].toString()) + .arg(mols[i].toString()) + .arg(openmm_mols_data[0].ffinfo.toString()) + .arg(mol.ffinfo.toString()), + CODELOC); + } + + // this hash holds the start indicies for the various + // parameters for this molecule (e.g. bond, angle, CLJ parameters) + // We only need to record this if this is a perturbable molecule + QHash start_indicies; + + // is this a perturbable molecule (and we haven't disabled perturbations)? + if (any_perturbable and mol.isPerturbable()) + { + // add a perturbable molecule, recording the start index + // for each of the forcefields + start_indicies.reserve(7); + + start_indicies.insert("clj", start_index); + start_indicies.insert("ghost/ghost", start_index); + start_indicies.insert("ghost/non-ghost", start_index); + + // the start index for this molecules first bond, angle or + // torsion parameters will be however many of these + // parameters exist already (parameters are added + // contiguously for each molecule) + start_indicies.insert("bond", bondff->getNumBonds()); + start_indicies.insert("angle", angff->getNumAngles()); + start_indicies.insert("torsion", dihff->getNumTorsions()); + + // we can now record this as a perturbable molecule + // in the lambda lever. The returned index is the + // index of this perturbable molecule in the list + // of perturbable molecules (e.g. the first perturbable + // molecule we find has index 0) + auto pert_idx = lambda_lever.addPerturbableMolecule(mol, + start_indicies); + + // and we can record the map from the molecule index + // to the perturbable molecule index + idx_to_pert_idx.insert(i, pert_idx); + } + + // Copy in all of the atom (particle) parameters. These + // are the masses, charge and LJ parameters. + // There is a different code path depending on whether + // or not there are any perturbable molecules, and + // this particular molecule is perturbable + auto masses_data = mol.masses.constData(); + auto cljs_data = mol.cljs.constData(); + auto alphas_data = mol.alphas.constData(); + + if (any_perturbable and mol.isPerturbable()) + { + // This is a perturbable molecule and we're modelling perturbations + for (int j = 0; j < mol.molinfo.nAtoms(); ++j) + { + const bool is_from_ghost = mol.from_ghost_idxs.contains(j); + const bool is_to_ghost = mol.to_ghost_idxs.contains(j); + + // add the particle - the OpenMMMolecule has already + // ensured that the largest of the reference or perturbed + // masses is used + const int atom_index = start_index + j; + + if (fixed_atoms.contains(atom_index)) + { + // this is a fixed (zero mass) atom + system.addParticle(0.0); + } + else + { + // this is a mobile atom (if its mass is > 0) + system.addParticle(masses_data[j]); + } + + // now the reference CLJ parameters + const auto &clj = cljs_data[j]; + + // reduced_q + custom_params[0] = std::get<0>(clj); + // half_sigma + custom_params[1] = 0.5 * std::get<1>(clj); + // two_sqrt_epsilon + custom_params[2] = 2.0 * std::sqrt(std::get<2>(clj)); + // alpha + custom_params[3] = alphas_data[j]; + + // Add the particle to the ghost and nonghost forcefields + ghost_ghostff->addParticle(custom_params); + ghost_nonghostff->addParticle(custom_params); + + if (is_from_ghost or is_to_ghost) + { + // this is a ghost atom! We need to record this + // fact and make sure that we don't calculate + // the LJ energy using the standard cljff + ghost_atoms.insert(atom_index); + ghost_is_from.insert(atom_index, is_from_ghost); + + // don't include the LJ energy as this will be + // calculated using the ghost forcefields + // (the ghost forcefields include a coulomb term + // that subtracts from whatever was calculated here) + cljff->addParticle(std::get<0>(clj), 0.0, 0.0); + } + else + { + // this isn't a ghost atom. Record this fact and + // just add it to the standard cljff as normal + cljff->addParticle(std::get<0>(clj), std::get<1>(clj), + std::get<2>(clj)); + non_ghost_atoms.insert(atom_index); + } + } + } + else + { + // Code path if this isn't a perturbable molecule or + // we don't want to model perturbations + for (int j = 0; j < mol.molinfo.nAtoms(); ++j) + { + // Add the particle to the system + const int atom_index = start_index + j; + + if (fixed_atoms.contains(atom_index)) + { + // this is a fixed (zero mass) atom + system.addParticle(0.0); + } + else + { + // this is a mobile atom (if its mass is > 0) + system.addParticle(masses_data[j]); + } + + // Get the particle CLJ parameters + const auto &clj = cljs_data[j]; + + // Add the particle to the standard CLJ forcefield + cljff->addParticle(std::get<0>(clj), std::get<1>(clj), std::get<2>(clj)); + + // We need to add this molecule to the ghost and ghost + // forcefields if there are any perturbable molecules + if (any_perturbable) + { + // reduced charge + custom_params[0] = std::get<0>(clj); + // half_sigma + custom_params[1] = 0.5 * std::get<1>(clj); + // two_sqrt_epsilon + custom_params[2] = 2.0 * std::sqrt(std::get<2>(clj)); + // alpha - is zero for non-ghost atoms + custom_params[3] = 0.0; + ghost_ghostff->addParticle(custom_params); + ghost_nonghostff->addParticle(custom_params); + non_ghost_atoms.insert(atom_index); + } + } + } + + // now add all of the bond parameters + for (const auto &bond : mol.bond_params) + { + bondff->addBond(std::get<0>(bond) + start_index, + std::get<1>(bond) + start_index, + std::get<2>(bond), std::get<3>(bond)); + } + + // now add all of the angles + for (const auto &ang : mol.ang_params) + { + angff->addAngle(std::get<0>(ang) + start_index, + std::get<1>(ang) + start_index, + std::get<2>(ang) + start_index, + std::get<3>(ang), std::get<4>(ang)); + } + + // now add all of the dihedrals and impropers + for (const auto &dih : mol.dih_params) + { + dihff->addTorsion(std::get<0>(dih) + start_index, + std::get<1>(dih) + start_index, + std::get<2>(dih) + start_index, + std::get<3>(dih) + start_index, + std::get<4>(dih), std::get<5>(dih), std::get<6>(dih)); + } + + // now add the constraints + if (any_perturbable and mol.isPerturbable()) + { + // we may want to select out constraints that involve + // perturbing atoms... For now we will include them all, + // but will use the current coordinates to set the + // constrained values, rather than using the + // forcefield parameters. This will work as long + // as the user has minimised the system at the desired + // lambda value before running dynamics... + for (const auto &constraint : mol.constraints) + { + const auto atom0 = std::get<0>(constraint); + const auto atom1 = std::get<1>(constraint); + + const auto mass0 = system.getParticleMass(atom0 + start_index); + const auto mass1 = system.getParticleMass(atom1 + start_index); + + if (mass0 != 0 and mass1 != 0) + { + // we can add the constraint + const auto coords0 = mol.coords[atom0]; + const auto coords1 = mol.coords[atom1]; + + const auto delta = coords1 - coords0; + + const auto length = std::sqrt((delta[0] * delta[0]) + + (delta[1] * delta[1]) + + (delta[2] * delta[2])); + + system.addConstraint( + atom0 + start_index, + atom1 + start_index, + length); + } + // else we will need to think about how to constrain bonds + // involving fixed atoms. Could we fix the other atom too? + } + } + else + { + for (const auto &constraint : mol.constraints) + { + const auto atom0 = std::get<0>(constraint); + const auto atom1 = std::get<1>(constraint); + + const auto mass0 = system.getParticleMass(atom0 + start_index); + const auto mass1 = system.getParticleMass(atom1 + start_index); + + if (mass0 != 0 and mass1 != 0) + { + + system.addConstraint(atom0 + start_index, + atom1 + start_index, + std::get<2>(constraint)); + } + // else we will need to think about how to constrain bonds + // involving fixed atoms. Could we fix the other atom too? + } + } + + start_index += mol.masses.count(); + } + + /// Finally tell the ghost forcefields about the ghost and non-ghost + /// interaction groups, so that they can correctly calculate the + /// ghost/ghost and ghost/non-ghost energies + if (ghost_ghostff != 0 and ghost_nonghostff != 0) + { + // set up the interaction groups - ghost / non-ghost + // ghost / ghost + ghost_ghostff->addInteractionGroup(ghost_atoms, ghost_atoms); + ghost_nonghostff->addInteractionGroup(ghost_atoms, non_ghost_atoms); + } + + // see if we want to remove COM motion + const auto com_remove_prop = map["com_reset_frequency"]; + + if (com_remove_prop.hasValue()) + { + const int freq = com_remove_prop.value().asAnInteger(); + + if (freq > 0) + { + OpenMM::CMMotionRemover *com_remover = new OpenMM::CMMotionRemover(freq); + system.addForce(com_remover); + } + } + + /// Stage 5 is complete. We have added all of the parameter data + /// for the molecules to the OpenMM forces + + /// + /// Stage 6 - Set up the exceptions + /// + /// We now have to add all of the exceptions to the non-bonded + /// forces (including the ghost forces). Exceptions are overrides + /// that replace or switch off pair-pair energies between + /// pairs of particles. This part of the code is the slowest + /// of the entire function, as it involves lots of atom-atom + /// pair loops and can create a large exception list + /// + for (int i = 0; i < nmols; ++i) + { + int start_index = start_indexes[i]; + const auto &mol = openmm_mols_data[i]; + + QVector> exception_idxs; + + const bool is_perturbable = any_perturbable and mol.isPerturbable(); + + if (is_perturbable) + { + exception_idxs = QVector>(mol.exception_params.count(), + std::make_pair(-1, -1)); + } + + for (int j = 0; j < mol.exception_params.count(); ++j) + { + const auto ¶m = mol.exception_params[j]; + + const auto atom0 = std::get<0>(param); + const auto atom1 = std::get<1>(param); + const auto coul_14_scale = std::get<2>(param); + const auto lj_14_scale = std::get<3>(param); + + auto p = mol.getException(atom0, atom1, + start_index, + coul_14_scale, + lj_14_scale); + + if (is_perturbable) + { + const bool atom0_is_ghost = mol.isGhostAtom(atom0); + const bool atom1_is_ghost = mol.isGhostAtom(atom1); + + int idx = -1; + int nbidx = -1; + + if (atom0_is_ghost or atom1_is_ghost) + { + // don't include the LJ term, as this is calculated + // elsewhere - note that we need to use 1e-9 to + // make sure that OpenMM doesn't eagerly remove + // this, and cause "changed excluded atoms" warnings + idx = cljff->addException(std::get<0>(p), std::get<1>(p), + std::get<2>(p), 1e-9, + 1e-9, true); + + if (coul_14_scl != 0 or lj_14_scl != 0) + { + // this is a 1-4 interaction that should be added + // to the ghost-14 forcefield + if (ghost_14ff != 0) + { + // parameters are q, sigma, four_epsilon and alpha + std::vector params14 = + {std::get<2>(p), std::get<3>(p), + 4.0 * std::get<4>(p), 0.0}; + + if (params14[0] == 0) + // cannot use zero params in case they are + // eagerly removed + params14[0] = 1e-9; + + if (params14[1] == 0) + params14[1] = 1e-9; + + nbidx = ghost_14ff->addBond(std::get<0>(p), + std::get<1>(p), + params14); + } + } + } + else + { + idx = cljff->addException(std::get<0>(p), std::get<1>(p), + std::get<2>(p), std::get<3>(p), + std::get<4>(p), true); + } + + exception_idxs[j] = std::make_pair(idx, nbidx); + + // remove this interaction from the ghost forcefields + if (ghost_ghostff != 0) + { + ghost_ghostff->addExclusion(std::get<0>(p), std::get<1>(p)); + ghost_nonghostff->addExclusion(std::get<0>(p), std::get<1>(p)); + } + } + else + { + cljff->addException(std::get<0>(p), std::get<1>(p), + std::get<2>(p), std::get<3>(p), + std::get<4>(p), true); + + // we need to make sure that the list of exclusions in + // the NonbondedForce match those in the CustomNonbondedForces + if (ghost_ghostff != 0 and std::get<2>(p) == 0 and + std::get<3>(p) == 0 and std::get<4>(p) == 0) + { + ghost_ghostff->addExclusion(std::get<0>(p), std::get<1>(p)); + ghost_nonghostff->addExclusion(std::get<0>(p), std::get<1>(p)); + } + } + } + + if (is_perturbable) + { + auto pert_idx = idx_to_pert_idx.value(i, openmm_mols.count() + 1); + lambda_lever.setExceptionIndicies(pert_idx, + "clj", exception_idxs); + } + } + + // Stage 6 is complete. We have set up all of the exceptions. The + // total energy / force calculated for the system should now be + // correct. + + /// + /// Stage 7 - Set up the restraints + /// + /// In this stage we set up all of the user-specified restraints. + /// These give extra forces that are not defined in the list of + /// molecules, and for which we need to know the indexes of + /// all of the atoms (hence why we do this at the end) + /// + + /// Some of the restraints will depend on fixed "anchor" atoms. + /// This is the list of the coordinates of all of the anchor + /// atoms (we will use this later when we get all coordinates + /// and velocities) + std::vector anchor_coords; + + if (map.specified("restraints")) + { + // All restraints are provided via the "restraints" key in the map. + // This should either be a single Restraints object, or a + // PropertyList - the easiest thing is to turn this into a + // PropertyList first + auto all_restraints = map["restraints"].value().asAnArray(); + + // loop over all of the restraints groups and add them + for (const auto &prop : all_restraints.toList()) + { + if (not prop.read().isA()) + throw SireError::invalid_cast(QObject::tr( + "Cannot convert an object of type %1 to a SireMM::Restraints") + .arg(prop.read().what()), + CODELOC); + + // we now need to choose what to do based on the type of restraint... + if (prop.read().isA()) + { + _add_positional_restraints(prop.read().asA(), + system, lambda_lever, anchor_coords, start_index); + } + else if (prop.read().isA()) + { + _add_bond_restraints(prop.read().asA(), + system, lambda_lever, start_index); + } + else if (prop.read().isA()) + { + _add_boresch_restraints(prop.read().asA(), + system, lambda_lever, start_index); + } + } + } + + /// + /// Stage 8 - Copy across the coordinates and velocities + /// + /// In this final(!) stage we copy out all of the atoms (and anchors) + /// coordinates and velocities so that they can be returned + /// as metadata to be added to the OpenMM integrator + /// + + // shared pointer to these coorindates and velocities + std::shared_ptr> coords, vels; + + const int natoms = start_index; + const int nanchors = anchor_coords.size(); + + // allocate memory + coords.reset(new std::vector(natoms + nanchors)); + vels.reset(new std::vector(natoms + nanchors)); + + auto coords_data = coords->data(); + auto vels_data = vels->data(); + + const int *start_indexes_data = start_indexes.constData(); + + // now copy the atomic data into the arrays + if (SireBase::should_run_in_parallel(nmols, map)) + { + tbb::parallel_for(tbb::blocked_range(0, nmols), [&](tbb::blocked_range r) + { + for (int i=r.begin(); i 0) + { + for (int i = 0; i < nanchors; ++i) + { + coords_data[natoms + i] = anchor_coords[i]; + vels_data[natoms + i] = OpenMM::Vec3(0, 0, 0); + } + } + + // All done - we can return the metadata (atoms are always added in + // molidx/atomidx order) + return OpenMMMetaData(mols.atoms(), coords, vels, boxvecs, lambda_lever); +} diff --git a/wrapper/Convert/__init__.py b/wrapper/Convert/__init__.py index 3b95b2e58..677d3a298 100644 --- a/wrapper/Convert/__init__.py +++ b/wrapper/Convert/__init__.py @@ -29,9 +29,12 @@ _has_rdkit = True _register_smarts_search() -except Exception: +except Exception as e: + _rdkit_import_error = e + # RDKit support is not available def _no_rdkit(): + print(_rdkit_import_error) raise ModuleNotFoundError( "Unable to convert to/from RDKit as it is not installed. " "Please install using `mamba install -c conda-forge rdkit` " @@ -324,11 +327,12 @@ def sire_to_openmm(mols, map): try: from ._sommcontext import SOMMContext - return SOMMContext( + context = SOMMContext( system=system, integrator=integrator, platform=platform, metadata=openmm_metadata, + map=map, ) except Exception as e: raise ValueError( @@ -337,18 +341,48 @@ def sire_to_openmm(mols, map): f"or on this computer? The error message is: {e}" ) - def openmm_extract_coordinates(state, mols, map): - return _openmm_extract_coordinates(state, mols, map) + return context + + def openmm_extract_coordinates( + state, mols, perturbable_maps=None, map=None + ): + from ...base import create_map + + map = create_map(map) + + if perturbable_maps is None: + perturbable_maps = {} + + return _openmm_extract_coordinates( + state=state, mols=mols, perturbable_maps=perturbable_maps, map=map + ) + + def openmm_extract_coordinates_and_velocities( + state, mols, perturbable_maps=None, map=None + ): + from ...base import create_map - def openmm_extract_coordinates_and_velocities(state, mols, map): - return _openmm_extract_coordinates_and_velocities(state, mols, map) + map = create_map(map) + + if perturbable_maps is None: + from ..Mol import MolNum + + perturbable_maps = {} + + return _openmm_extract_coordinates_and_velocities( + state=state, mols=mols, perturbable_maps=perturbable_maps, map=map + ) def openmm_extract_space(state): return _openmm_extract_space(state) -except Exception: +except Exception as e: + _openmm_import_exception = e + # OpenMM support is not available def _no_openmm(): + print(_openmm_import_exception) + raise ModuleNotFoundError( "Unable to convert to/from OpenMM as this code hasn't been " "written yet. We hope to support this soon!" @@ -362,13 +396,13 @@ def sire_to_openmm(*args, **kwargs): def openmm_to_sire(*args, **kwargs): _no_openmm() - def openmm_extract_coordinates(state, mols, map): + def openmm_extract_coordinates(*arg, **kwargs): _no_openmm() - def openmm_extract_coordinates_and_velocities(state, mols, map): + def openmm_extract_coordinates_and_velocities(*args, **kwargs): _no_openmm() - def openmm_extract_space(state): + def openmm_extract_space(*args, **kwargs): _no_openmm() diff --git a/wrapper/MM/BondRestraint.pypp.cpp b/wrapper/MM/BondRestraint.pypp.cpp new file mode 100644 index 000000000..c680cfb17 --- /dev/null +++ b/wrapper/MM/BondRestraint.pypp.cpp @@ -0,0 +1,217 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#include "boost/python.hpp" +#include "BondRestraint.pypp.hpp" + +namespace bp = boost::python; + +#include "SireError/errors.h" + +#include "SireID/index.h" + +#include "SireStream/datastream.h" + +#include "SireStream/shareddatastream.h" + +#include "SireUnits/units.h" + +#include "bondrestraints.h" + +#include + +#include "bondrestraints.h" + +SireMM::BondRestraint __copy__(const SireMM::BondRestraint &other){ return SireMM::BondRestraint(other); } + +#include "Qt/qdatastream.hpp" + +#include "Helpers/str.hpp" + +#include "Helpers/release_gil_policy.hpp" + +void register_BondRestraint_class(){ + + { //::SireMM::BondRestraint + typedef bp::class_< SireMM::BondRestraint, bp::bases< SireBase::Property > > BondRestraint_exposer_t; + BondRestraint_exposer_t BondRestraint_exposer = BondRestraint_exposer_t( "BondRestraint", "This class represents a single bond restraint between any two\natoms in a system (or between the centroids of any two groups\nof atoms in a system)\n", bp::init< >("Null constructor") ); + bp::scope BondRestraint_scope( BondRestraint_exposer ); + BondRestraint_exposer.def( bp::init< qint64, qint64, SireUnits::Dimension::HarmonicBondConstant const &, SireUnits::Dimension::Length const & >(( bp::arg("atom0"), bp::arg("atom1"), bp::arg("k"), bp::arg("r0") ), "Construct to restrain the atom at index atom to the specified position\n using the specified force constant and flat-bottom well-width\n") ); + BondRestraint_exposer.def( bp::init< QList< long long > const &, QList< long long > const &, SireUnits::Dimension::HarmonicBondConstant const &, SireUnits::Dimension::Length const & >(( bp::arg("atoms0"), bp::arg("atoms1"), bp::arg("k"), bp::arg("r0") ), "Construct to restrain the centroid of the atoms whose indicies are\n in atoms to the specified position using the specified force constant\n and flat-bottom well width\n") ); + BondRestraint_exposer.def( bp::init< SireMM::BondRestraint const & >(( bp::arg("other") ), "Copy constructor") ); + { //::SireMM::BondRestraint::atom0 + + typedef ::qint64 ( ::SireMM::BondRestraint::*atom0_function_type)( ) const; + atom0_function_type atom0_function_value( &::SireMM::BondRestraint::atom0 ); + + BondRestraint_exposer.def( + "atom0" + , atom0_function_value + , bp::release_gil_policy() + , "Return the index of the atom if this is a single-atom restraint" ); + + } + { //::SireMM::BondRestraint::atom1 + + typedef ::qint64 ( ::SireMM::BondRestraint::*atom1_function_type)( ) const; + atom1_function_type atom1_function_value( &::SireMM::BondRestraint::atom1 ); + + BondRestraint_exposer.def( + "atom1" + , atom1_function_value + , bp::release_gil_policy() + , "Return the index of the atom if this is a single-atom restraint" ); + + } + { //::SireMM::BondRestraint::atoms0 + + typedef ::QVector< long long > ( ::SireMM::BondRestraint::*atoms0_function_type)( ) const; + atoms0_function_type atoms0_function_value( &::SireMM::BondRestraint::atoms0 ); + + BondRestraint_exposer.def( + "atoms0" + , atoms0_function_value + , bp::release_gil_policy() + , "Return the indexes of the atoms whose centroid is to be restrained" ); + + } + { //::SireMM::BondRestraint::atoms1 + + typedef ::QVector< long long > ( ::SireMM::BondRestraint::*atoms1_function_type)( ) const; + atoms1_function_type atoms1_function_value( &::SireMM::BondRestraint::atoms1 ); + + BondRestraint_exposer.def( + "atoms1" + , atoms1_function_value + , bp::release_gil_policy() + , "Return the indexes of the atoms whose centroid is to be restrained" ); + + } + { //::SireMM::BondRestraint::isAtomRestraint + + typedef bool ( ::SireMM::BondRestraint::*isAtomRestraint_function_type)( ) const; + isAtomRestraint_function_type isAtomRestraint_function_value( &::SireMM::BondRestraint::isAtomRestraint ); + + BondRestraint_exposer.def( + "isAtomRestraint" + , isAtomRestraint_function_value + , bp::release_gil_policy() + , "Return whether this is a single-atom restraint" ); + + } + { //::SireMM::BondRestraint::isCentroidRestraint + + typedef bool ( ::SireMM::BondRestraint::*isCentroidRestraint_function_type)( ) const; + isCentroidRestraint_function_type isCentroidRestraint_function_value( &::SireMM::BondRestraint::isCentroidRestraint ); + + BondRestraint_exposer.def( + "isCentroidRestraint" + , isCentroidRestraint_function_value + , bp::release_gil_policy() + , "Return whether this restraint acts on the centroid of a group\n of atoms" ); + + } + { //::SireMM::BondRestraint::isNull + + typedef bool ( ::SireMM::BondRestraint::*isNull_function_type)( ) const; + isNull_function_type isNull_function_value( &::SireMM::BondRestraint::isNull ); + + BondRestraint_exposer.def( + "isNull" + , isNull_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMM::BondRestraint::k + + typedef ::SireUnits::Dimension::HarmonicBondConstant ( ::SireMM::BondRestraint::*k_function_type)( ) const; + k_function_type k_function_value( &::SireMM::BondRestraint::k ); + + BondRestraint_exposer.def( + "k" + , k_function_value + , bp::release_gil_policy() + , "Return the force constant for the restraint" ); + + } + BondRestraint_exposer.def( bp::self != bp::self ); + BondRestraint_exposer.def( bp::self + bp::self ); + BondRestraint_exposer.def( bp::self + bp::other< SireMM::BondRestraints >() ); + { //::SireMM::BondRestraint::operator= + + typedef ::SireMM::BondRestraint & ( ::SireMM::BondRestraint::*assign_function_type)( ::SireMM::BondRestraint const & ) ; + assign_function_type assign_function_value( &::SireMM::BondRestraint::operator= ); + + BondRestraint_exposer.def( + "assign" + , assign_function_value + , ( bp::arg("other") ) + , bp::return_self< >() + , "" ); + + } + BondRestraint_exposer.def( bp::self == bp::self ); + { //::SireMM::BondRestraint::r0 + + typedef ::SireUnits::Dimension::Length ( ::SireMM::BondRestraint::*r0_function_type)( ) const; + r0_function_type r0_function_value( &::SireMM::BondRestraint::r0 ); + + BondRestraint_exposer.def( + "r0" + , r0_function_value + , bp::release_gil_policy() + , "Return the width of the harmonic bond." ); + + } + { //::SireMM::BondRestraint::toString + + typedef ::QString ( ::SireMM::BondRestraint::*toString_function_type)( ) const; + toString_function_type toString_function_value( &::SireMM::BondRestraint::toString ); + + BondRestraint_exposer.def( + "toString" + , toString_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMM::BondRestraint::typeName + + typedef char const * ( *typeName_function_type )( ); + typeName_function_type typeName_function_value( &::SireMM::BondRestraint::typeName ); + + BondRestraint_exposer.def( + "typeName" + , typeName_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMM::BondRestraint::what + + typedef char const * ( ::SireMM::BondRestraint::*what_function_type)( ) const; + what_function_type what_function_value( &::SireMM::BondRestraint::what ); + + BondRestraint_exposer.def( + "what" + , what_function_value + , bp::release_gil_policy() + , "" ); + + } + BondRestraint_exposer.staticmethod( "typeName" ); + BondRestraint_exposer.def( "__copy__", &__copy__); + BondRestraint_exposer.def( "__deepcopy__", &__copy__); + BondRestraint_exposer.def( "clone", &__copy__); + BondRestraint_exposer.def( "__rlshift__", &__rlshift__QDataStream< ::SireMM::BondRestraint >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + BondRestraint_exposer.def( "__rrshift__", &__rrshift__QDataStream< ::SireMM::BondRestraint >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + BondRestraint_exposer.def_pickle(sire_pickle_suite< ::SireMM::BondRestraint >()); + BondRestraint_exposer.def( "__str__", &__str__< ::SireMM::BondRestraint > ); + BondRestraint_exposer.def( "__repr__", &__str__< ::SireMM::BondRestraint > ); + } + +} diff --git a/wrapper/MM/BondRestraint.pypp.hpp b/wrapper/MM/BondRestraint.pypp.hpp new file mode 100644 index 000000000..551daa2b3 --- /dev/null +++ b/wrapper/MM/BondRestraint.pypp.hpp @@ -0,0 +1,10 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#ifndef BondRestraint_hpp__pyplusplus_wrapper +#define BondRestraint_hpp__pyplusplus_wrapper + +void register_BondRestraint_class(); + +#endif//BondRestraint_hpp__pyplusplus_wrapper diff --git a/wrapper/MM/BondRestraints.pypp.cpp b/wrapper/MM/BondRestraints.pypp.cpp new file mode 100644 index 000000000..5bc3ea598 --- /dev/null +++ b/wrapper/MM/BondRestraints.pypp.cpp @@ -0,0 +1,312 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#include "boost/python.hpp" +#include "Helpers/clone_const_reference.hpp" +#include "BondRestraints.pypp.hpp" + +namespace bp = boost::python; + +#include "SireError/errors.h" + +#include "SireID/index.h" + +#include "SireStream/datastream.h" + +#include "SireStream/shareddatastream.h" + +#include "SireUnits/units.h" + +#include "bondrestraints.h" + +#include + +#include "bondrestraints.h" + +SireMM::BondRestraints __copy__(const SireMM::BondRestraints &other){ return SireMM::BondRestraints(other); } + +#include "Qt/qdatastream.hpp" + +#include "Helpers/str.hpp" + +#include "Helpers/release_gil_policy.hpp" + +#include "Helpers/len.hpp" + +void register_BondRestraints_class(){ + + { //::SireMM::BondRestraints + typedef bp::class_< SireMM::BondRestraints, bp::bases< SireMM::Restraints, SireBase::Property > > BondRestraints_exposer_t; + BondRestraints_exposer_t BondRestraints_exposer = BondRestraints_exposer_t( "BondRestraints", "This class provides the information for a collection of bond\nrestraints that can be added to a collection of molecues. Each\nrestraint can act on a pair of particles or a pair of the\ncentroids of two collections of particles.\nThe restaints are spherically symmetric, and\nare simple harmonic potentials\n", bp::init< >("Null constructor") ); + bp::scope BondRestraints_scope( BondRestraints_exposer ); + BondRestraints_exposer.def( bp::init< QString const & >(( bp::arg("name") ), "") ); + BondRestraints_exposer.def( bp::init< SireMM::BondRestraint const & >(( bp::arg("restraint") ), "") ); + BondRestraints_exposer.def( bp::init< QList< SireMM::BondRestraint > const & >(( bp::arg("restraints") ), "") ); + BondRestraints_exposer.def( bp::init< QString const &, SireMM::BondRestraint const & >(( bp::arg("name"), bp::arg("restraint") ), "") ); + BondRestraints_exposer.def( bp::init< QString const &, QList< SireMM::BondRestraint > const & >(( bp::arg("name"), bp::arg("restraints") ), "") ); + BondRestraints_exposer.def( bp::init< SireMM::BondRestraints const & >(( bp::arg("other") ), "") ); + { //::SireMM::BondRestraints::add + + typedef void ( ::SireMM::BondRestraints::*add_function_type)( ::SireMM::BondRestraint const & ) ; + add_function_type add_function_value( &::SireMM::BondRestraints::add ); + + BondRestraints_exposer.def( + "add" + , add_function_value + , ( bp::arg("restraint") ) + , bp::release_gil_policy() + , "Add a restraint onto the list" ); + + } + { //::SireMM::BondRestraints::add + + typedef void ( ::SireMM::BondRestraints::*add_function_type)( ::SireMM::BondRestraints const & ) ; + add_function_type add_function_value( &::SireMM::BondRestraints::add ); + + BondRestraints_exposer.def( + "add" + , add_function_value + , ( bp::arg("restraints") ) + , bp::release_gil_policy() + , "Add a restraint onto the list" ); + + } + { //::SireMM::BondRestraints::at + + typedef ::SireMM::BondRestraint const & ( ::SireMM::BondRestraints::*at_function_type)( int ) const; + at_function_type at_function_value( &::SireMM::BondRestraints::at ); + + BondRestraints_exposer.def( + "at" + , at_function_value + , ( bp::arg("i") ) + , bp::return_value_policy() + , "Return the ith restraint" ); + + } + { //::SireMM::BondRestraints::atomRestraints + + typedef ::QList< SireMM::BondRestraint > ( ::SireMM::BondRestraints::*atomRestraints_function_type)( ) const; + atomRestraints_function_type atomRestraints_function_value( &::SireMM::BondRestraints::atomRestraints ); + + BondRestraints_exposer.def( + "atomRestraints" + , atomRestraints_function_value + , bp::release_gil_policy() + , "Return all of the atom restraints" ); + + } + { //::SireMM::BondRestraints::centroidRestraints + + typedef ::QList< SireMM::BondRestraint > ( ::SireMM::BondRestraints::*centroidRestraints_function_type)( ) const; + centroidRestraints_function_type centroidRestraints_function_value( &::SireMM::BondRestraints::centroidRestraints ); + + BondRestraints_exposer.def( + "centroidRestraints" + , centroidRestraints_function_value + , bp::release_gil_policy() + , "Return all of the centroid restraints" ); + + } + { //::SireMM::BondRestraints::count + + typedef int ( ::SireMM::BondRestraints::*count_function_type)( ) const; + count_function_type count_function_value( &::SireMM::BondRestraints::count ); + + BondRestraints_exposer.def( + "count" + , count_function_value + , bp::release_gil_policy() + , "Return the number of restraints" ); + + } + { //::SireMM::BondRestraints::hasAtomRestraints + + typedef bool ( ::SireMM::BondRestraints::*hasAtomRestraints_function_type)( ) const; + hasAtomRestraints_function_type hasAtomRestraints_function_value( &::SireMM::BondRestraints::hasAtomRestraints ); + + BondRestraints_exposer.def( + "hasAtomRestraints" + , hasAtomRestraints_function_value + , bp::release_gil_policy() + , "Return whether or not there are any atom restraints" ); + + } + { //::SireMM::BondRestraints::hasCentroidRestraints + + typedef bool ( ::SireMM::BondRestraints::*hasCentroidRestraints_function_type)( ) const; + hasCentroidRestraints_function_type hasCentroidRestraints_function_value( &::SireMM::BondRestraints::hasCentroidRestraints ); + + BondRestraints_exposer.def( + "hasCentroidRestraints" + , hasCentroidRestraints_function_value + , bp::release_gil_policy() + , "Return whether or not there are any centroid restraints" ); + + } + { //::SireMM::BondRestraints::isEmpty + + typedef bool ( ::SireMM::BondRestraints::*isEmpty_function_type)( ) const; + isEmpty_function_type isEmpty_function_value( &::SireMM::BondRestraints::isEmpty ); + + BondRestraints_exposer.def( + "isEmpty" + , isEmpty_function_value + , bp::release_gil_policy() + , "Return whether or not this is empty" ); + + } + { //::SireMM::BondRestraints::isNull + + typedef bool ( ::SireMM::BondRestraints::*isNull_function_type)( ) const; + isNull_function_type isNull_function_value( &::SireMM::BondRestraints::isNull ); + + BondRestraints_exposer.def( + "isNull" + , isNull_function_value + , bp::release_gil_policy() + , "Return whether or not this is empty" ); + + } + { //::SireMM::BondRestraints::nAtomRestraints + + typedef int ( ::SireMM::BondRestraints::*nAtomRestraints_function_type)( ) const; + nAtomRestraints_function_type nAtomRestraints_function_value( &::SireMM::BondRestraints::nAtomRestraints ); + + BondRestraints_exposer.def( + "nAtomRestraints" + , nAtomRestraints_function_value + , bp::release_gil_policy() + , "Return the number of atom restraints" ); + + } + { //::SireMM::BondRestraints::nCentroidRestraints + + typedef int ( ::SireMM::BondRestraints::*nCentroidRestraints_function_type)( ) const; + nCentroidRestraints_function_type nCentroidRestraints_function_value( &::SireMM::BondRestraints::nCentroidRestraints ); + + BondRestraints_exposer.def( + "nCentroidRestraints" + , nCentroidRestraints_function_value + , bp::release_gil_policy() + , "Return the number of centroid restraints" ); + + } + { //::SireMM::BondRestraints::nRestraints + + typedef int ( ::SireMM::BondRestraints::*nRestraints_function_type)( ) const; + nRestraints_function_type nRestraints_function_value( &::SireMM::BondRestraints::nRestraints ); + + BondRestraints_exposer.def( + "nRestraints" + , nRestraints_function_value + , bp::release_gil_policy() + , "Return the number of restraints" ); + + } + BondRestraints_exposer.def( bp::self != bp::self ); + BondRestraints_exposer.def( bp::self + bp::other< SireMM::BondRestraint >() ); + BondRestraints_exposer.def( bp::self + bp::self ); + { //::SireMM::BondRestraints::operator= + + typedef ::SireMM::BondRestraints & ( ::SireMM::BondRestraints::*assign_function_type)( ::SireMM::BondRestraints const & ) ; + assign_function_type assign_function_value( &::SireMM::BondRestraints::operator= ); + + BondRestraints_exposer.def( + "assign" + , assign_function_value + , ( bp::arg("other") ) + , bp::return_self< >() + , "" ); + + } + BondRestraints_exposer.def( bp::self == bp::self ); + { //::SireMM::BondRestraints::operator[] + + typedef ::SireMM::BondRestraint const & ( ::SireMM::BondRestraints::*__getitem___function_type)( int ) const; + __getitem___function_type __getitem___function_value( &::SireMM::BondRestraints::operator[] ); + + BondRestraints_exposer.def( + "__getitem__" + , __getitem___function_value + , ( bp::arg("i") ) + , bp::return_value_policy() + , "" ); + + } + { //::SireMM::BondRestraints::restraints + + typedef ::QList< SireMM::BondRestraint > ( ::SireMM::BondRestraints::*restraints_function_type)( ) const; + restraints_function_type restraints_function_value( &::SireMM::BondRestraints::restraints ); + + BondRestraints_exposer.def( + "restraints" + , restraints_function_value + , bp::release_gil_policy() + , "Return all of the restraints" ); + + } + { //::SireMM::BondRestraints::size + + typedef int ( ::SireMM::BondRestraints::*size_function_type)( ) const; + size_function_type size_function_value( &::SireMM::BondRestraints::size ); + + BondRestraints_exposer.def( + "size" + , size_function_value + , bp::release_gil_policy() + , "Return the number of restraints" ); + + } + { //::SireMM::BondRestraints::toString + + typedef ::QString ( ::SireMM::BondRestraints::*toString_function_type)( ) const; + toString_function_type toString_function_value( &::SireMM::BondRestraints::toString ); + + BondRestraints_exposer.def( + "toString" + , toString_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMM::BondRestraints::typeName + + typedef char const * ( *typeName_function_type )( ); + typeName_function_type typeName_function_value( &::SireMM::BondRestraints::typeName ); + + BondRestraints_exposer.def( + "typeName" + , typeName_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMM::BondRestraints::what + + typedef char const * ( ::SireMM::BondRestraints::*what_function_type)( ) const; + what_function_type what_function_value( &::SireMM::BondRestraints::what ); + + BondRestraints_exposer.def( + "what" + , what_function_value + , bp::release_gil_policy() + , "" ); + + } + BondRestraints_exposer.staticmethod( "typeName" ); + BondRestraints_exposer.def( "__copy__", &__copy__); + BondRestraints_exposer.def( "__deepcopy__", &__copy__); + BondRestraints_exposer.def( "clone", &__copy__); + BondRestraints_exposer.def( "__rlshift__", &__rlshift__QDataStream< ::SireMM::BondRestraints >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + BondRestraints_exposer.def( "__rrshift__", &__rrshift__QDataStream< ::SireMM::BondRestraints >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + BondRestraints_exposer.def_pickle(sire_pickle_suite< ::SireMM::BondRestraints >()); + BondRestraints_exposer.def( "__str__", &__str__< ::SireMM::BondRestraints > ); + BondRestraints_exposer.def( "__repr__", &__str__< ::SireMM::BondRestraints > ); + BondRestraints_exposer.def( "__len__", &__len_size< ::SireMM::BondRestraints > ); + } + +} diff --git a/wrapper/MM/BondRestraints.pypp.hpp b/wrapper/MM/BondRestraints.pypp.hpp new file mode 100644 index 000000000..81e053ef2 --- /dev/null +++ b/wrapper/MM/BondRestraints.pypp.hpp @@ -0,0 +1,10 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#ifndef BondRestraints_hpp__pyplusplus_wrapper +#define BondRestraints_hpp__pyplusplus_wrapper + +void register_BondRestraints_class(); + +#endif//BondRestraints_hpp__pyplusplus_wrapper diff --git a/wrapper/MM/BoreschRestraint.pypp.cpp b/wrapper/MM/BoreschRestraint.pypp.cpp new file mode 100644 index 000000000..e9bbc0d19 --- /dev/null +++ b/wrapper/MM/BoreschRestraint.pypp.cpp @@ -0,0 +1,216 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#include "boost/python.hpp" +#include "BoreschRestraint.pypp.hpp" + +namespace bp = boost::python; + +#include "SireError/errors.h" + +#include "SireID/index.h" + +#include "SireStream/datastream.h" + +#include "SireStream/shareddatastream.h" + +#include "SireUnits/units.h" + +#include "boreschrestraints.h" + +#include + +#include "boreschrestraints.h" + +SireMM::BoreschRestraint __copy__(const SireMM::BoreschRestraint &other){ return SireMM::BoreschRestraint(other); } + +#include "Qt/qdatastream.hpp" + +#include "Helpers/str.hpp" + +#include "Helpers/release_gil_policy.hpp" + +void register_BoreschRestraint_class(){ + + { //::SireMM::BoreschRestraint + typedef bp::class_< SireMM::BoreschRestraint, bp::bases< SireBase::Property > > BoreschRestraint_exposer_t; + BoreschRestraint_exposer_t BoreschRestraint_exposer = BoreschRestraint_exposer_t( "BoreschRestraint", "This class provides information about a single Boresch restaint.\nThis is a collection of distance, angle and torsion restraints\nthat hold a ligand in a binding pose relative to a receptor\n", bp::init< >("Null constructor") ); + bp::scope BoreschRestraint_scope( BoreschRestraint_exposer ); + BoreschRestraint_exposer.def( bp::init< QList< long long > const &, QList< long long > const &, SireUnits::Dimension::Length const &, QVector< SireUnits::Dimension::PhysUnit< 0, 0, 0, 0, 0, 0, 1 > > const &, QVector< SireUnits::Dimension::PhysUnit< 0, 0, 0, 0, 0, 0, 1 > > const &, SireUnits::Dimension::HarmonicBondConstant const &, QVector< SireUnits::Dimension::PhysUnit< 1, 2, -2, 0, 0, -1, -2 > > const &, QVector< SireUnits::Dimension::PhysUnit< 1, 2, -2, 0, 0, -1, -2 > > const & >(( bp::arg("receptor"), bp::arg("ligand"), bp::arg("r0"), bp::arg("theta0"), bp::arg("phi0"), bp::arg("kr"), bp::arg("ktheta"), bp::arg("kphi") ), "Construct to restrain the atom at index atom to the specified position\n using the specified force constant and flat-bottom well-width\n") ); + BoreschRestraint_exposer.def( bp::init< SireMM::BoreschRestraint const & >(( bp::arg("other") ), "Copy constructor") ); + { //::SireMM::BoreschRestraint::isNull + + typedef bool ( ::SireMM::BoreschRestraint::*isNull_function_type)( ) const; + isNull_function_type isNull_function_value( &::SireMM::BoreschRestraint::isNull ); + + BoreschRestraint_exposer.def( + "isNull" + , isNull_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMM::BoreschRestraint::kphi + + typedef ::QVector< SireUnits::Dimension::PhysUnit< 1, 2, -2, 0, 0, -1, -2 > > ( ::SireMM::BoreschRestraint::*kphi_function_type)( ) const; + kphi_function_type kphi_function_value( &::SireMM::BoreschRestraint::kphi ); + + BoreschRestraint_exposer.def( + "kphi" + , kphi_function_value + , bp::release_gil_policy() + , "Return the force constant for the three dihedral restraints" ); + + } + { //::SireMM::BoreschRestraint::kr + + typedef ::SireUnits::Dimension::HarmonicBondConstant ( ::SireMM::BoreschRestraint::*kr_function_type)( ) const; + kr_function_type kr_function_value( &::SireMM::BoreschRestraint::kr ); + + BoreschRestraint_exposer.def( + "kr" + , kr_function_value + , bp::release_gil_policy() + , "Return the force constant for the bond restraint" ); + + } + { //::SireMM::BoreschRestraint::ktheta + + typedef ::QVector< SireUnits::Dimension::PhysUnit< 1, 2, -2, 0, 0, -1, -2 > > ( ::SireMM::BoreschRestraint::*ktheta_function_type)( ) const; + ktheta_function_type ktheta_function_value( &::SireMM::BoreschRestraint::ktheta ); + + BoreschRestraint_exposer.def( + "ktheta" + , ktheta_function_value + , bp::release_gil_policy() + , "Return the force constant for the two angle restraints" ); + + } + { //::SireMM::BoreschRestraint::ligandAtoms + + typedef ::QVector< long long > ( ::SireMM::BoreschRestraint::*ligandAtoms_function_type)( ) const; + ligandAtoms_function_type ligandAtoms_function_value( &::SireMM::BoreschRestraint::ligandAtoms ); + + BoreschRestraint_exposer.def( + "ligandAtoms" + , ligandAtoms_function_value + , bp::release_gil_policy() + , "Return the indexes of the three ligand atoms" ); + + } + BoreschRestraint_exposer.def( bp::self != bp::self ); + BoreschRestraint_exposer.def( bp::self + bp::self ); + BoreschRestraint_exposer.def( bp::self + bp::other< SireMM::BoreschRestraints >() ); + { //::SireMM::BoreschRestraint::operator= + + typedef ::SireMM::BoreschRestraint & ( ::SireMM::BoreschRestraint::*assign_function_type)( ::SireMM::BoreschRestraint const & ) ; + assign_function_type assign_function_value( &::SireMM::BoreschRestraint::operator= ); + + BoreschRestraint_exposer.def( + "assign" + , assign_function_value + , ( bp::arg("other") ) + , bp::return_self< >() + , "" ); + + } + BoreschRestraint_exposer.def( bp::self == bp::self ); + { //::SireMM::BoreschRestraint::phi0 + + typedef ::QVector< SireUnits::Dimension::PhysUnit< 0, 0, 0, 0, 0, 0, 1 > > ( ::SireMM::BoreschRestraint::*phi0_function_type)( ) const; + phi0_function_type phi0_function_value( &::SireMM::BoreschRestraint::phi0 ); + + BoreschRestraint_exposer.def( + "phi0" + , phi0_function_value + , bp::release_gil_policy() + , "Return the equilibium size of the three dihedral restraints" ); + + } + { //::SireMM::BoreschRestraint::r0 + + typedef ::SireUnits::Dimension::Length ( ::SireMM::BoreschRestraint::*r0_function_type)( ) const; + r0_function_type r0_function_value( &::SireMM::BoreschRestraint::r0 ); + + BoreschRestraint_exposer.def( + "r0" + , r0_function_value + , bp::release_gil_policy() + , "Return the equilibrium length of the bond restraint" ); + + } + { //::SireMM::BoreschRestraint::receptorAtoms + + typedef ::QVector< long long > ( ::SireMM::BoreschRestraint::*receptorAtoms_function_type)( ) const; + receptorAtoms_function_type receptorAtoms_function_value( &::SireMM::BoreschRestraint::receptorAtoms ); + + BoreschRestraint_exposer.def( + "receptorAtoms" + , receptorAtoms_function_value + , bp::release_gil_policy() + , "Return the indexes of the three receptor atoms" ); + + } + { //::SireMM::BoreschRestraint::theta0 + + typedef ::QVector< SireUnits::Dimension::PhysUnit< 0, 0, 0, 0, 0, 0, 1 > > ( ::SireMM::BoreschRestraint::*theta0_function_type)( ) const; + theta0_function_type theta0_function_value( &::SireMM::BoreschRestraint::theta0 ); + + BoreschRestraint_exposer.def( + "theta0" + , theta0_function_value + , bp::release_gil_policy() + , "Return the equilibrium size of the two angle restraints" ); + + } + { //::SireMM::BoreschRestraint::toString + + typedef ::QString ( ::SireMM::BoreschRestraint::*toString_function_type)( ) const; + toString_function_type toString_function_value( &::SireMM::BoreschRestraint::toString ); + + BoreschRestraint_exposer.def( + "toString" + , toString_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMM::BoreschRestraint::typeName + + typedef char const * ( *typeName_function_type )( ); + typeName_function_type typeName_function_value( &::SireMM::BoreschRestraint::typeName ); + + BoreschRestraint_exposer.def( + "typeName" + , typeName_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMM::BoreschRestraint::what + + typedef char const * ( ::SireMM::BoreschRestraint::*what_function_type)( ) const; + what_function_type what_function_value( &::SireMM::BoreschRestraint::what ); + + BoreschRestraint_exposer.def( + "what" + , what_function_value + , bp::release_gil_policy() + , "" ); + + } + BoreschRestraint_exposer.staticmethod( "typeName" ); + BoreschRestraint_exposer.def( "__copy__", &__copy__); + BoreschRestraint_exposer.def( "__deepcopy__", &__copy__); + BoreschRestraint_exposer.def( "clone", &__copy__); + BoreschRestraint_exposer.def( "__rlshift__", &__rlshift__QDataStream< ::SireMM::BoreschRestraint >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + BoreschRestraint_exposer.def( "__rrshift__", &__rrshift__QDataStream< ::SireMM::BoreschRestraint >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + BoreschRestraint_exposer.def_pickle(sire_pickle_suite< ::SireMM::BoreschRestraint >()); + BoreschRestraint_exposer.def( "__str__", &__str__< ::SireMM::BoreschRestraint > ); + BoreschRestraint_exposer.def( "__repr__", &__str__< ::SireMM::BoreschRestraint > ); + } + +} diff --git a/wrapper/MM/BoreschRestraint.pypp.hpp b/wrapper/MM/BoreschRestraint.pypp.hpp new file mode 100644 index 000000000..8328558a7 --- /dev/null +++ b/wrapper/MM/BoreschRestraint.pypp.hpp @@ -0,0 +1,10 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#ifndef BoreschRestraint_hpp__pyplusplus_wrapper +#define BoreschRestraint_hpp__pyplusplus_wrapper + +void register_BoreschRestraint_class(); + +#endif//BoreschRestraint_hpp__pyplusplus_wrapper diff --git a/wrapper/MM/BoreschRestraints.pypp.cpp b/wrapper/MM/BoreschRestraints.pypp.cpp new file mode 100644 index 000000000..9f45de923 --- /dev/null +++ b/wrapper/MM/BoreschRestraints.pypp.cpp @@ -0,0 +1,240 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#include "boost/python.hpp" +#include "Helpers/clone_const_reference.hpp" +#include "BoreschRestraints.pypp.hpp" + +namespace bp = boost::python; + +#include "SireError/errors.h" + +#include "SireID/index.h" + +#include "SireStream/datastream.h" + +#include "SireStream/shareddatastream.h" + +#include "SireUnits/units.h" + +#include "boreschrestraints.h" + +#include + +#include "boreschrestraints.h" + +SireMM::BoreschRestraints __copy__(const SireMM::BoreschRestraints &other){ return SireMM::BoreschRestraints(other); } + +#include "Qt/qdatastream.hpp" + +#include "Helpers/str.hpp" + +#include "Helpers/release_gil_policy.hpp" + +#include "Helpers/len.hpp" + +void register_BoreschRestraints_class(){ + + { //::SireMM::BoreschRestraints + typedef bp::class_< SireMM::BoreschRestraints, bp::bases< SireMM::Restraints, SireBase::Property > > BoreschRestraints_exposer_t; + BoreschRestraints_exposer_t BoreschRestraints_exposer = BoreschRestraints_exposer_t( "BoreschRestraints", "This class provides the information for a collection of positional\nrestraints that can be added to a collection of molecues. Each\nrestraint can act on a particle or the centroid of a collection\nof particles. The restaints are spherically symmetric, and\nare either flat-bottom harmonics or harmonic potentials\n", bp::init< >("Null constructor") ); + bp::scope BoreschRestraints_scope( BoreschRestraints_exposer ); + BoreschRestraints_exposer.def( bp::init< QString const & >(( bp::arg("name") ), "") ); + BoreschRestraints_exposer.def( bp::init< SireMM::BoreschRestraint const & >(( bp::arg("restraint") ), "") ); + BoreschRestraints_exposer.def( bp::init< QList< SireMM::BoreschRestraint > const & >(( bp::arg("restraints") ), "") ); + BoreschRestraints_exposer.def( bp::init< QString const &, SireMM::BoreschRestraint const & >(( bp::arg("name"), bp::arg("restraint") ), "") ); + BoreschRestraints_exposer.def( bp::init< QString const &, QList< SireMM::BoreschRestraint > const & >(( bp::arg("name"), bp::arg("restraints") ), "") ); + BoreschRestraints_exposer.def( bp::init< SireMM::BoreschRestraints const & >(( bp::arg("other") ), "") ); + { //::SireMM::BoreschRestraints::add + + typedef void ( ::SireMM::BoreschRestraints::*add_function_type)( ::SireMM::BoreschRestraint const & ) ; + add_function_type add_function_value( &::SireMM::BoreschRestraints::add ); + + BoreschRestraints_exposer.def( + "add" + , add_function_value + , ( bp::arg("restraint") ) + , bp::release_gil_policy() + , "Add a restraint onto the list" ); + + } + { //::SireMM::BoreschRestraints::add + + typedef void ( ::SireMM::BoreschRestraints::*add_function_type)( ::SireMM::BoreschRestraints const & ) ; + add_function_type add_function_value( &::SireMM::BoreschRestraints::add ); + + BoreschRestraints_exposer.def( + "add" + , add_function_value + , ( bp::arg("restraints") ) + , bp::release_gil_policy() + , "Add a restraint onto the list" ); + + } + { //::SireMM::BoreschRestraints::at + + typedef ::SireMM::BoreschRestraint const & ( ::SireMM::BoreschRestraints::*at_function_type)( int ) const; + at_function_type at_function_value( &::SireMM::BoreschRestraints::at ); + + BoreschRestraints_exposer.def( + "at" + , at_function_value + , ( bp::arg("i") ) + , bp::return_value_policy() + , "Return the ith restraint" ); + + } + { //::SireMM::BoreschRestraints::count + + typedef int ( ::SireMM::BoreschRestraints::*count_function_type)( ) const; + count_function_type count_function_value( &::SireMM::BoreschRestraints::count ); + + BoreschRestraints_exposer.def( + "count" + , count_function_value + , bp::release_gil_policy() + , "Return the number of restraints" ); + + } + { //::SireMM::BoreschRestraints::isEmpty + + typedef bool ( ::SireMM::BoreschRestraints::*isEmpty_function_type)( ) const; + isEmpty_function_type isEmpty_function_value( &::SireMM::BoreschRestraints::isEmpty ); + + BoreschRestraints_exposer.def( + "isEmpty" + , isEmpty_function_value + , bp::release_gil_policy() + , "Return whether or not this is empty" ); + + } + { //::SireMM::BoreschRestraints::isNull + + typedef bool ( ::SireMM::BoreschRestraints::*isNull_function_type)( ) const; + isNull_function_type isNull_function_value( &::SireMM::BoreschRestraints::isNull ); + + BoreschRestraints_exposer.def( + "isNull" + , isNull_function_value + , bp::release_gil_policy() + , "Return whether or not this is empty" ); + + } + { //::SireMM::BoreschRestraints::nRestraints + + typedef int ( ::SireMM::BoreschRestraints::*nRestraints_function_type)( ) const; + nRestraints_function_type nRestraints_function_value( &::SireMM::BoreschRestraints::nRestraints ); + + BoreschRestraints_exposer.def( + "nRestraints" + , nRestraints_function_value + , bp::release_gil_policy() + , "Return the number of restraints" ); + + } + BoreschRestraints_exposer.def( bp::self != bp::self ); + BoreschRestraints_exposer.def( bp::self + bp::other< SireMM::BoreschRestraint >() ); + BoreschRestraints_exposer.def( bp::self + bp::self ); + { //::SireMM::BoreschRestraints::operator= + + typedef ::SireMM::BoreschRestraints & ( ::SireMM::BoreschRestraints::*assign_function_type)( ::SireMM::BoreschRestraints const & ) ; + assign_function_type assign_function_value( &::SireMM::BoreschRestraints::operator= ); + + BoreschRestraints_exposer.def( + "assign" + , assign_function_value + , ( bp::arg("other") ) + , bp::return_self< >() + , "" ); + + } + BoreschRestraints_exposer.def( bp::self == bp::self ); + { //::SireMM::BoreschRestraints::operator[] + + typedef ::SireMM::BoreschRestraint const & ( ::SireMM::BoreschRestraints::*__getitem___function_type)( int ) const; + __getitem___function_type __getitem___function_value( &::SireMM::BoreschRestraints::operator[] ); + + BoreschRestraints_exposer.def( + "__getitem__" + , __getitem___function_value + , ( bp::arg("i") ) + , bp::return_value_policy() + , "" ); + + } + { //::SireMM::BoreschRestraints::restraints + + typedef ::QList< SireMM::BoreschRestraint > ( ::SireMM::BoreschRestraints::*restraints_function_type)( ) const; + restraints_function_type restraints_function_value( &::SireMM::BoreschRestraints::restraints ); + + BoreschRestraints_exposer.def( + "restraints" + , restraints_function_value + , bp::release_gil_policy() + , "Return all of the restraints" ); + + } + { //::SireMM::BoreschRestraints::size + + typedef int ( ::SireMM::BoreschRestraints::*size_function_type)( ) const; + size_function_type size_function_value( &::SireMM::BoreschRestraints::size ); + + BoreschRestraints_exposer.def( + "size" + , size_function_value + , bp::release_gil_policy() + , "Return the number of restraints" ); + + } + { //::SireMM::BoreschRestraints::toString + + typedef ::QString ( ::SireMM::BoreschRestraints::*toString_function_type)( ) const; + toString_function_type toString_function_value( &::SireMM::BoreschRestraints::toString ); + + BoreschRestraints_exposer.def( + "toString" + , toString_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMM::BoreschRestraints::typeName + + typedef char const * ( *typeName_function_type )( ); + typeName_function_type typeName_function_value( &::SireMM::BoreschRestraints::typeName ); + + BoreschRestraints_exposer.def( + "typeName" + , typeName_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMM::BoreschRestraints::what + + typedef char const * ( ::SireMM::BoreschRestraints::*what_function_type)( ) const; + what_function_type what_function_value( &::SireMM::BoreschRestraints::what ); + + BoreschRestraints_exposer.def( + "what" + , what_function_value + , bp::release_gil_policy() + , "" ); + + } + BoreschRestraints_exposer.staticmethod( "typeName" ); + BoreschRestraints_exposer.def( "__copy__", &__copy__); + BoreschRestraints_exposer.def( "__deepcopy__", &__copy__); + BoreschRestraints_exposer.def( "clone", &__copy__); + BoreschRestraints_exposer.def( "__rlshift__", &__rlshift__QDataStream< ::SireMM::BoreschRestraints >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + BoreschRestraints_exposer.def( "__rrshift__", &__rrshift__QDataStream< ::SireMM::BoreschRestraints >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + BoreschRestraints_exposer.def_pickle(sire_pickle_suite< ::SireMM::BoreschRestraints >()); + BoreschRestraints_exposer.def( "__str__", &__str__< ::SireMM::BoreschRestraints > ); + BoreschRestraints_exposer.def( "__repr__", &__str__< ::SireMM::BoreschRestraints > ); + BoreschRestraints_exposer.def( "__len__", &__len_size< ::SireMM::BoreschRestraints > ); + } + +} diff --git a/wrapper/MM/BoreschRestraints.pypp.hpp b/wrapper/MM/BoreschRestraints.pypp.hpp new file mode 100644 index 000000000..cabb2dcc4 --- /dev/null +++ b/wrapper/MM/BoreschRestraints.pypp.hpp @@ -0,0 +1,10 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#ifndef BoreschRestraints_hpp__pyplusplus_wrapper +#define BoreschRestraints_hpp__pyplusplus_wrapper + +void register_BoreschRestraints_class(); + +#endif//BoreschRestraints_hpp__pyplusplus_wrapper diff --git a/wrapper/MM/CMakeAutogenFile.txt b/wrapper/MM/CMakeAutogenFile.txt index eb9b0c867..407e1a27d 100644 --- a/wrapper/MM/CMakeAutogenFile.txt +++ b/wrapper/MM/CMakeAutogenFile.txt @@ -23,7 +23,11 @@ set ( PYPP_SOURCES Bond.pypp.cpp BondComponent.pypp.cpp BondParameterName.pypp.cpp + BondRestraint.pypp.cpp + BondRestraints.pypp.cpp BondSymbols.pypp.cpp + BoreschRestraint.pypp.cpp + BoreschRestraints.pypp.cpp CHARMMSwitchingFunction.pypp.cpp CLJ14Group.pypp.cpp CLJAtom.pypp.cpp @@ -164,10 +168,13 @@ set ( PYPP_SOURCES NoCutoff.pypp.cpp NullCLJFunction.pypp.cpp NullRestraint.pypp.cpp + PositionalRestraint.pypp.cpp + PositionalRestraints.pypp.cpp Restraint.pypp.cpp Restraint3D.pypp.cpp RestraintComponent.pypp.cpp RestraintFF.pypp.cpp + Restraints.pypp.cpp ScaledCLJParameterNames3D.pypp.cpp ScaledChargeParameterNames3D.pypp.cpp ScaledLJParameterNames3D.pypp.cpp diff --git a/wrapper/MM/DistanceRestraint.pypp.cpp b/wrapper/MM/DistanceRestraint.pypp.cpp index cdd0d300a..1f59e2873 100644 --- a/wrapper/MM/DistanceRestraint.pypp.cpp +++ b/wrapper/MM/DistanceRestraint.pypp.cpp @@ -148,7 +148,7 @@ void register_DistanceRestraint_class(){ } { //::SireMM::DistanceRestraint::halfHarmonic - typedef ::SireMM::DistanceRestraint ( *halfHarmonic_function_type )( ::SireFF::PointRef const &,::SireFF::PointRef const &,::SireUnits::Dimension::Length const &,::SireMM::HarmonicDistanceForceConstant const & ); + typedef ::SireMM::DistanceRestraint ( *halfHarmonic_function_type )( ::SireFF::PointRef const &,::SireFF::PointRef const &,::SireUnits::Dimension::Length const &,::SireUnits::Dimension::HarmonicBondConstant const & ); halfHarmonic_function_type halfHarmonic_function_value( &::SireMM::DistanceRestraint::halfHarmonic ); DistanceRestraint_exposer.def( @@ -161,7 +161,7 @@ void register_DistanceRestraint_class(){ } { //::SireMM::DistanceRestraint::harmonic - typedef ::SireMM::DistanceRestraint ( *harmonic_function_type )( ::SireFF::PointRef const &,::SireFF::PointRef const &,::SireMM::HarmonicDistanceForceConstant const & ); + typedef ::SireMM::DistanceRestraint ( *harmonic_function_type )( ::SireFF::PointRef const &,::SireFF::PointRef const &,::SireUnits::Dimension::HarmonicBondConstant const & ); harmonic_function_type harmonic_function_value( &::SireMM::DistanceRestraint::harmonic ); DistanceRestraint_exposer.def( diff --git a/wrapper/MM/PositionalRestraint.pypp.cpp b/wrapper/MM/PositionalRestraint.pypp.cpp new file mode 100644 index 000000000..dbae9b973 --- /dev/null +++ b/wrapper/MM/PositionalRestraint.pypp.cpp @@ -0,0 +1,205 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#include "boost/python.hpp" +#include "PositionalRestraint.pypp.hpp" + +namespace bp = boost::python; + +#include "SireError/errors.h" + +#include "SireID/index.h" + +#include "SireStream/datastream.h" + +#include "SireStream/shareddatastream.h" + +#include "SireUnits/units.h" + +#include "positionalrestraints.h" + +#include + +#include "positionalrestraints.h" + +SireMM::PositionalRestraint __copy__(const SireMM::PositionalRestraint &other){ return SireMM::PositionalRestraint(other); } + +#include "Qt/qdatastream.hpp" + +#include "Helpers/str.hpp" + +#include "Helpers/release_gil_policy.hpp" + +void register_PositionalRestraint_class(){ + + { //::SireMM::PositionalRestraint + typedef bp::class_< SireMM::PositionalRestraint, bp::bases< SireBase::Property > > PositionalRestraint_exposer_t; + PositionalRestraint_exposer_t PositionalRestraint_exposer = PositionalRestraint_exposer_t( "PositionalRestraint", "This class provides information about a single positional restraint.\nThis is spherically symmetric and can act on either a single particle,\nor the centroid of a group of particles. The restraints are either\nflat-bottom harmonics or harmonic potentials\n", bp::init< >("Null constructor") ); + bp::scope PositionalRestraint_scope( PositionalRestraint_exposer ); + PositionalRestraint_exposer.def( bp::init< qint64, SireMaths::Vector const &, SireUnits::Dimension::HarmonicBondConstant const &, SireUnits::Dimension::Length const & >(( bp::arg("atom"), bp::arg("position"), bp::arg("k"), bp::arg("r0") ), "Construct to restrain the atom at index atom to the specified position\n using the specified force constant and flat-bottom well-width\n") ); + PositionalRestraint_exposer.def( bp::init< QList< long long > const &, SireMaths::Vector const &, SireUnits::Dimension::HarmonicBondConstant const &, SireUnits::Dimension::Length const & >(( bp::arg("atoms"), bp::arg("position"), bp::arg("k"), bp::arg("r0") ), "Construct to restrain the centroid of the atoms whose indicies are\n in atoms to the specified position using the specified force constant\n and flat-bottom well width\n") ); + PositionalRestraint_exposer.def( bp::init< SireMM::PositionalRestraint const & >(( bp::arg("other") ), "Copy constructor") ); + { //::SireMM::PositionalRestraint::atom + + typedef ::qint64 ( ::SireMM::PositionalRestraint::*atom_function_type)( ) const; + atom_function_type atom_function_value( &::SireMM::PositionalRestraint::atom ); + + PositionalRestraint_exposer.def( + "atom" + , atom_function_value + , bp::release_gil_policy() + , "Return the index of the atom if this is a single-atom restraint" ); + + } + { //::SireMM::PositionalRestraint::atoms + + typedef ::QVector< long long > ( ::SireMM::PositionalRestraint::*atoms_function_type)( ) const; + atoms_function_type atoms_function_value( &::SireMM::PositionalRestraint::atoms ); + + PositionalRestraint_exposer.def( + "atoms" + , atoms_function_value + , bp::release_gil_policy() + , "Return the indexes of the atoms whose centroid is to be restrained" ); + + } + { //::SireMM::PositionalRestraint::isAtomRestraint + + typedef bool ( ::SireMM::PositionalRestraint::*isAtomRestraint_function_type)( ) const; + isAtomRestraint_function_type isAtomRestraint_function_value( &::SireMM::PositionalRestraint::isAtomRestraint ); + + PositionalRestraint_exposer.def( + "isAtomRestraint" + , isAtomRestraint_function_value + , bp::release_gil_policy() + , "Return whether this is a single-atom restraint" ); + + } + { //::SireMM::PositionalRestraint::isCentroidRestraint + + typedef bool ( ::SireMM::PositionalRestraint::*isCentroidRestraint_function_type)( ) const; + isCentroidRestraint_function_type isCentroidRestraint_function_value( &::SireMM::PositionalRestraint::isCentroidRestraint ); + + PositionalRestraint_exposer.def( + "isCentroidRestraint" + , isCentroidRestraint_function_value + , bp::release_gil_policy() + , "Return whether this restraint acts on the centroid of a group\n of atoms" ); + + } + { //::SireMM::PositionalRestraint::isNull + + typedef bool ( ::SireMM::PositionalRestraint::*isNull_function_type)( ) const; + isNull_function_type isNull_function_value( &::SireMM::PositionalRestraint::isNull ); + + PositionalRestraint_exposer.def( + "isNull" + , isNull_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMM::PositionalRestraint::k + + typedef ::SireUnits::Dimension::HarmonicBondConstant ( ::SireMM::PositionalRestraint::*k_function_type)( ) const; + k_function_type k_function_value( &::SireMM::PositionalRestraint::k ); + + PositionalRestraint_exposer.def( + "k" + , k_function_value + , bp::release_gil_policy() + , "Return the force constant for the restraint" ); + + } + PositionalRestraint_exposer.def( bp::self != bp::self ); + PositionalRestraint_exposer.def( bp::self + bp::self ); + PositionalRestraint_exposer.def( bp::self + bp::other< SireMM::PositionalRestraints >() ); + { //::SireMM::PositionalRestraint::operator= + + typedef ::SireMM::PositionalRestraint & ( ::SireMM::PositionalRestraint::*assign_function_type)( ::SireMM::PositionalRestraint const & ) ; + assign_function_type assign_function_value( &::SireMM::PositionalRestraint::operator= ); + + PositionalRestraint_exposer.def( + "assign" + , assign_function_value + , ( bp::arg("other") ) + , bp::return_self< >() + , "" ); + + } + PositionalRestraint_exposer.def( bp::self == bp::self ); + { //::SireMM::PositionalRestraint::position + + typedef ::SireMaths::Vector ( ::SireMM::PositionalRestraint::*position_function_type)( ) const; + position_function_type position_function_value( &::SireMM::PositionalRestraint::position ); + + PositionalRestraint_exposer.def( + "position" + , position_function_value + , bp::release_gil_policy() + , "Return the position in space where the restraint is placed" ); + + } + { //::SireMM::PositionalRestraint::r0 + + typedef ::SireUnits::Dimension::Length ( ::SireMM::PositionalRestraint::*r0_function_type)( ) const; + r0_function_type r0_function_value( &::SireMM::PositionalRestraint::r0 ); + + PositionalRestraint_exposer.def( + "r0" + , r0_function_value + , bp::release_gil_policy() + , "Return the width of the flat-bottom well. This is zero for a\n pure harmonic restraint\n" ); + + } + { //::SireMM::PositionalRestraint::toString + + typedef ::QString ( ::SireMM::PositionalRestraint::*toString_function_type)( ) const; + toString_function_type toString_function_value( &::SireMM::PositionalRestraint::toString ); + + PositionalRestraint_exposer.def( + "toString" + , toString_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMM::PositionalRestraint::typeName + + typedef char const * ( *typeName_function_type )( ); + typeName_function_type typeName_function_value( &::SireMM::PositionalRestraint::typeName ); + + PositionalRestraint_exposer.def( + "typeName" + , typeName_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMM::PositionalRestraint::what + + typedef char const * ( ::SireMM::PositionalRestraint::*what_function_type)( ) const; + what_function_type what_function_value( &::SireMM::PositionalRestraint::what ); + + PositionalRestraint_exposer.def( + "what" + , what_function_value + , bp::release_gil_policy() + , "" ); + + } + PositionalRestraint_exposer.staticmethod( "typeName" ); + PositionalRestraint_exposer.def( "__copy__", &__copy__); + PositionalRestraint_exposer.def( "__deepcopy__", &__copy__); + PositionalRestraint_exposer.def( "clone", &__copy__); + PositionalRestraint_exposer.def( "__rlshift__", &__rlshift__QDataStream< ::SireMM::PositionalRestraint >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + PositionalRestraint_exposer.def( "__rrshift__", &__rrshift__QDataStream< ::SireMM::PositionalRestraint >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + PositionalRestraint_exposer.def_pickle(sire_pickle_suite< ::SireMM::PositionalRestraint >()); + PositionalRestraint_exposer.def( "__str__", &__str__< ::SireMM::PositionalRestraint > ); + PositionalRestraint_exposer.def( "__repr__", &__str__< ::SireMM::PositionalRestraint > ); + } + +} diff --git a/wrapper/MM/PositionalRestraint.pypp.hpp b/wrapper/MM/PositionalRestraint.pypp.hpp new file mode 100644 index 000000000..0915fefe6 --- /dev/null +++ b/wrapper/MM/PositionalRestraint.pypp.hpp @@ -0,0 +1,10 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#ifndef PositionalRestraint_hpp__pyplusplus_wrapper +#define PositionalRestraint_hpp__pyplusplus_wrapper + +void register_PositionalRestraint_class(); + +#endif//PositionalRestraint_hpp__pyplusplus_wrapper diff --git a/wrapper/MM/PositionalRestraints.pypp.cpp b/wrapper/MM/PositionalRestraints.pypp.cpp new file mode 100644 index 000000000..ade356fbb --- /dev/null +++ b/wrapper/MM/PositionalRestraints.pypp.cpp @@ -0,0 +1,312 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#include "boost/python.hpp" +#include "Helpers/clone_const_reference.hpp" +#include "PositionalRestraints.pypp.hpp" + +namespace bp = boost::python; + +#include "SireError/errors.h" + +#include "SireID/index.h" + +#include "SireStream/datastream.h" + +#include "SireStream/shareddatastream.h" + +#include "SireUnits/units.h" + +#include "positionalrestraints.h" + +#include + +#include "positionalrestraints.h" + +SireMM::PositionalRestraints __copy__(const SireMM::PositionalRestraints &other){ return SireMM::PositionalRestraints(other); } + +#include "Qt/qdatastream.hpp" + +#include "Helpers/str.hpp" + +#include "Helpers/release_gil_policy.hpp" + +#include "Helpers/len.hpp" + +void register_PositionalRestraints_class(){ + + { //::SireMM::PositionalRestraints + typedef bp::class_< SireMM::PositionalRestraints, bp::bases< SireMM::Restraints, SireBase::Property > > PositionalRestraints_exposer_t; + PositionalRestraints_exposer_t PositionalRestraints_exposer = PositionalRestraints_exposer_t( "PositionalRestraints", "This class provides the information for a collection of positional\nrestraints that can be added to a collection of molecues. Each\nrestraint can act on a particle or the centroid of a collection\nof particles. The restaints are spherically symmetric, and\nare either flat-bottom harmonics or harmonic potentials\n", bp::init< >("Null constructor") ); + bp::scope PositionalRestraints_scope( PositionalRestraints_exposer ); + PositionalRestraints_exposer.def( bp::init< QString const & >(( bp::arg("name") ), "") ); + PositionalRestraints_exposer.def( bp::init< SireMM::PositionalRestraint const & >(( bp::arg("restraint") ), "") ); + PositionalRestraints_exposer.def( bp::init< QList< SireMM::PositionalRestraint > const & >(( bp::arg("restraints") ), "") ); + PositionalRestraints_exposer.def( bp::init< QString const &, SireMM::PositionalRestraint const & >(( bp::arg("name"), bp::arg("restraint") ), "") ); + PositionalRestraints_exposer.def( bp::init< QString const &, QList< SireMM::PositionalRestraint > const & >(( bp::arg("name"), bp::arg("restraints") ), "") ); + PositionalRestraints_exposer.def( bp::init< SireMM::PositionalRestraints const & >(( bp::arg("other") ), "") ); + { //::SireMM::PositionalRestraints::add + + typedef void ( ::SireMM::PositionalRestraints::*add_function_type)( ::SireMM::PositionalRestraint const & ) ; + add_function_type add_function_value( &::SireMM::PositionalRestraints::add ); + + PositionalRestraints_exposer.def( + "add" + , add_function_value + , ( bp::arg("restraint") ) + , bp::release_gil_policy() + , "Add a restraint onto the list" ); + + } + { //::SireMM::PositionalRestraints::add + + typedef void ( ::SireMM::PositionalRestraints::*add_function_type)( ::SireMM::PositionalRestraints const & ) ; + add_function_type add_function_value( &::SireMM::PositionalRestraints::add ); + + PositionalRestraints_exposer.def( + "add" + , add_function_value + , ( bp::arg("restraints") ) + , bp::release_gil_policy() + , "Add a restraint onto the list" ); + + } + { //::SireMM::PositionalRestraints::at + + typedef ::SireMM::PositionalRestraint const & ( ::SireMM::PositionalRestraints::*at_function_type)( int ) const; + at_function_type at_function_value( &::SireMM::PositionalRestraints::at ); + + PositionalRestraints_exposer.def( + "at" + , at_function_value + , ( bp::arg("i") ) + , bp::return_value_policy() + , "Return the ith restraint" ); + + } + { //::SireMM::PositionalRestraints::atomRestraints + + typedef ::QList< SireMM::PositionalRestraint > ( ::SireMM::PositionalRestraints::*atomRestraints_function_type)( ) const; + atomRestraints_function_type atomRestraints_function_value( &::SireMM::PositionalRestraints::atomRestraints ); + + PositionalRestraints_exposer.def( + "atomRestraints" + , atomRestraints_function_value + , bp::release_gil_policy() + , "Return all of the atom restraints" ); + + } + { //::SireMM::PositionalRestraints::centroidRestraints + + typedef ::QList< SireMM::PositionalRestraint > ( ::SireMM::PositionalRestraints::*centroidRestraints_function_type)( ) const; + centroidRestraints_function_type centroidRestraints_function_value( &::SireMM::PositionalRestraints::centroidRestraints ); + + PositionalRestraints_exposer.def( + "centroidRestraints" + , centroidRestraints_function_value + , bp::release_gil_policy() + , "Return all of the centroid restraints" ); + + } + { //::SireMM::PositionalRestraints::count + + typedef int ( ::SireMM::PositionalRestraints::*count_function_type)( ) const; + count_function_type count_function_value( &::SireMM::PositionalRestraints::count ); + + PositionalRestraints_exposer.def( + "count" + , count_function_value + , bp::release_gil_policy() + , "Return the number of restraints" ); + + } + { //::SireMM::PositionalRestraints::hasAtomRestraints + + typedef bool ( ::SireMM::PositionalRestraints::*hasAtomRestraints_function_type)( ) const; + hasAtomRestraints_function_type hasAtomRestraints_function_value( &::SireMM::PositionalRestraints::hasAtomRestraints ); + + PositionalRestraints_exposer.def( + "hasAtomRestraints" + , hasAtomRestraints_function_value + , bp::release_gil_policy() + , "Return whether or not there are any atom restraints" ); + + } + { //::SireMM::PositionalRestraints::hasCentroidRestraints + + typedef bool ( ::SireMM::PositionalRestraints::*hasCentroidRestraints_function_type)( ) const; + hasCentroidRestraints_function_type hasCentroidRestraints_function_value( &::SireMM::PositionalRestraints::hasCentroidRestraints ); + + PositionalRestraints_exposer.def( + "hasCentroidRestraints" + , hasCentroidRestraints_function_value + , bp::release_gil_policy() + , "Return whether or not there are any centroid restraints" ); + + } + { //::SireMM::PositionalRestraints::isEmpty + + typedef bool ( ::SireMM::PositionalRestraints::*isEmpty_function_type)( ) const; + isEmpty_function_type isEmpty_function_value( &::SireMM::PositionalRestraints::isEmpty ); + + PositionalRestraints_exposer.def( + "isEmpty" + , isEmpty_function_value + , bp::release_gil_policy() + , "Return whether or not this is empty" ); + + } + { //::SireMM::PositionalRestraints::isNull + + typedef bool ( ::SireMM::PositionalRestraints::*isNull_function_type)( ) const; + isNull_function_type isNull_function_value( &::SireMM::PositionalRestraints::isNull ); + + PositionalRestraints_exposer.def( + "isNull" + , isNull_function_value + , bp::release_gil_policy() + , "Return whether or not this is empty" ); + + } + { //::SireMM::PositionalRestraints::nAtomRestraints + + typedef int ( ::SireMM::PositionalRestraints::*nAtomRestraints_function_type)( ) const; + nAtomRestraints_function_type nAtomRestraints_function_value( &::SireMM::PositionalRestraints::nAtomRestraints ); + + PositionalRestraints_exposer.def( + "nAtomRestraints" + , nAtomRestraints_function_value + , bp::release_gil_policy() + , "Return the number of atom restraints" ); + + } + { //::SireMM::PositionalRestraints::nCentroidRestraints + + typedef int ( ::SireMM::PositionalRestraints::*nCentroidRestraints_function_type)( ) const; + nCentroidRestraints_function_type nCentroidRestraints_function_value( &::SireMM::PositionalRestraints::nCentroidRestraints ); + + PositionalRestraints_exposer.def( + "nCentroidRestraints" + , nCentroidRestraints_function_value + , bp::release_gil_policy() + , "Return the number of centroid restraints" ); + + } + { //::SireMM::PositionalRestraints::nRestraints + + typedef int ( ::SireMM::PositionalRestraints::*nRestraints_function_type)( ) const; + nRestraints_function_type nRestraints_function_value( &::SireMM::PositionalRestraints::nRestraints ); + + PositionalRestraints_exposer.def( + "nRestraints" + , nRestraints_function_value + , bp::release_gil_policy() + , "Return the number of restraints" ); + + } + PositionalRestraints_exposer.def( bp::self != bp::self ); + PositionalRestraints_exposer.def( bp::self + bp::other< SireMM::PositionalRestraint >() ); + PositionalRestraints_exposer.def( bp::self + bp::self ); + { //::SireMM::PositionalRestraints::operator= + + typedef ::SireMM::PositionalRestraints & ( ::SireMM::PositionalRestraints::*assign_function_type)( ::SireMM::PositionalRestraints const & ) ; + assign_function_type assign_function_value( &::SireMM::PositionalRestraints::operator= ); + + PositionalRestraints_exposer.def( + "assign" + , assign_function_value + , ( bp::arg("other") ) + , bp::return_self< >() + , "" ); + + } + PositionalRestraints_exposer.def( bp::self == bp::self ); + { //::SireMM::PositionalRestraints::operator[] + + typedef ::SireMM::PositionalRestraint const & ( ::SireMM::PositionalRestraints::*__getitem___function_type)( int ) const; + __getitem___function_type __getitem___function_value( &::SireMM::PositionalRestraints::operator[] ); + + PositionalRestraints_exposer.def( + "__getitem__" + , __getitem___function_value + , ( bp::arg("i") ) + , bp::return_value_policy() + , "" ); + + } + { //::SireMM::PositionalRestraints::restraints + + typedef ::QList< SireMM::PositionalRestraint > ( ::SireMM::PositionalRestraints::*restraints_function_type)( ) const; + restraints_function_type restraints_function_value( &::SireMM::PositionalRestraints::restraints ); + + PositionalRestraints_exposer.def( + "restraints" + , restraints_function_value + , bp::release_gil_policy() + , "Return all of the restraints" ); + + } + { //::SireMM::PositionalRestraints::size + + typedef int ( ::SireMM::PositionalRestraints::*size_function_type)( ) const; + size_function_type size_function_value( &::SireMM::PositionalRestraints::size ); + + PositionalRestraints_exposer.def( + "size" + , size_function_value + , bp::release_gil_policy() + , "Return the number of restraints" ); + + } + { //::SireMM::PositionalRestraints::toString + + typedef ::QString ( ::SireMM::PositionalRestraints::*toString_function_type)( ) const; + toString_function_type toString_function_value( &::SireMM::PositionalRestraints::toString ); + + PositionalRestraints_exposer.def( + "toString" + , toString_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMM::PositionalRestraints::typeName + + typedef char const * ( *typeName_function_type )( ); + typeName_function_type typeName_function_value( &::SireMM::PositionalRestraints::typeName ); + + PositionalRestraints_exposer.def( + "typeName" + , typeName_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMM::PositionalRestraints::what + + typedef char const * ( ::SireMM::PositionalRestraints::*what_function_type)( ) const; + what_function_type what_function_value( &::SireMM::PositionalRestraints::what ); + + PositionalRestraints_exposer.def( + "what" + , what_function_value + , bp::release_gil_policy() + , "" ); + + } + PositionalRestraints_exposer.staticmethod( "typeName" ); + PositionalRestraints_exposer.def( "__copy__", &__copy__); + PositionalRestraints_exposer.def( "__deepcopy__", &__copy__); + PositionalRestraints_exposer.def( "clone", &__copy__); + PositionalRestraints_exposer.def( "__rlshift__", &__rlshift__QDataStream< ::SireMM::PositionalRestraints >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + PositionalRestraints_exposer.def( "__rrshift__", &__rrshift__QDataStream< ::SireMM::PositionalRestraints >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + PositionalRestraints_exposer.def_pickle(sire_pickle_suite< ::SireMM::PositionalRestraints >()); + PositionalRestraints_exposer.def( "__str__", &__str__< ::SireMM::PositionalRestraints > ); + PositionalRestraints_exposer.def( "__repr__", &__str__< ::SireMM::PositionalRestraints > ); + PositionalRestraints_exposer.def( "__len__", &__len_size< ::SireMM::PositionalRestraints > ); + } + +} diff --git a/wrapper/MM/PositionalRestraints.pypp.hpp b/wrapper/MM/PositionalRestraints.pypp.hpp new file mode 100644 index 000000000..a49e8f922 --- /dev/null +++ b/wrapper/MM/PositionalRestraints.pypp.hpp @@ -0,0 +1,10 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#ifndef PositionalRestraints_hpp__pyplusplus_wrapper +#define PositionalRestraints_hpp__pyplusplus_wrapper + +void register_PositionalRestraints_class(); + +#endif//PositionalRestraints_hpp__pyplusplus_wrapper diff --git a/wrapper/MM/Restraints.pypp.cpp b/wrapper/MM/Restraints.pypp.cpp new file mode 100644 index 000000000..ea1ce76c7 --- /dev/null +++ b/wrapper/MM/Restraints.pypp.cpp @@ -0,0 +1,89 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#include "boost/python.hpp" +#include "Restraints.pypp.hpp" + +namespace bp = boost::python; + +#include "SireStream/datastream.h" + +#include "SireStream/shareddatastream.h" + +#include "restraints.h" + +#include "restraints.h" + +#include "Qt/qdatastream.hpp" + +#include "Helpers/str.hpp" + +#include "Helpers/release_gil_policy.hpp" + +void register_Restraints_class(){ + + { //::SireMM::Restraints + typedef bp::class_< SireMM::Restraints, bp::bases< SireBase::Property >, boost::noncopyable > Restraints_exposer_t; + Restraints_exposer_t Restraints_exposer = Restraints_exposer_t( "Restraints", "This is the base class of all of the `Restraints` collections,\ne.g. PositionalRestraints, BoreschRestraints etc.\n\nThese are not related to the legacy SireMM::Restraint classes,\nwhich calculate restraints directly. Instead, these classes\nprovide information about the restraints that can be picked\nup and used by different engines (e.g. OpenMM)\n", bp::no_init ); + bp::scope Restraints_scope( Restraints_exposer ); + { //::SireMM::Restraints::isRestraints + + typedef bool ( ::SireMM::Restraints::*isRestraints_function_type)( ) const; + isRestraints_function_type isRestraints_function_value( &::SireMM::Restraints::isRestraints ); + + Restraints_exposer.def( + "isRestraints" + , isRestraints_function_value + , bp::release_gil_policy() + , "Return whether or not this is a Restraints object (or derived\nfrom this object)" ); + + } + { //::SireMM::Restraints::name + + typedef ::QString ( ::SireMM::Restraints::*name_function_type)( ) const; + name_function_type name_function_value( &::SireMM::Restraints::name ); + + Restraints_exposer.def( + "name" + , name_function_value + , bp::release_gil_policy() + , "Return the name given to this group of restraints" ); + + } + { //::SireMM::Restraints::setName + + typedef void ( ::SireMM::Restraints::*setName_function_type)( ::QString const & ) ; + setName_function_type setName_function_value( &::SireMM::Restraints::setName ); + + Restraints_exposer.def( + "setName" + , setName_function_value + , ( bp::arg("name") ) + , bp::release_gil_policy() + , "Set the name for this group of restraints" ); + + } + { //::SireMM::Restraints::typeName + + typedef char const * ( *typeName_function_type )( ); + typeName_function_type typeName_function_value( &::SireMM::Restraints::typeName ); + + Restraints_exposer.def( + "typeName" + , typeName_function_value + , bp::release_gil_policy() + , "" ); + + } + Restraints_exposer.staticmethod( "typeName" ); + Restraints_exposer.def( "__rlshift__", &__rlshift__QDataStream< ::SireMM::Restraints >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + Restraints_exposer.def( "__rrshift__", &__rrshift__QDataStream< ::SireMM::Restraints >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + Restraints_exposer.def_pickle(sire_pickle_suite< ::SireMM::Restraints >()); + Restraints_exposer.def( "__str__", &__str__< ::SireMM::Restraints > ); + Restraints_exposer.def( "__repr__", &__str__< ::SireMM::Restraints > ); + } + +} diff --git a/wrapper/MM/Restraints.pypp.hpp b/wrapper/MM/Restraints.pypp.hpp new file mode 100644 index 000000000..5394b39b7 --- /dev/null +++ b/wrapper/MM/Restraints.pypp.hpp @@ -0,0 +1,10 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#ifndef Restraints_hpp__pyplusplus_wrapper +#define Restraints_hpp__pyplusplus_wrapper + +void register_Restraints_class(); + +#endif//Restraints_hpp__pyplusplus_wrapper diff --git a/wrapper/MM/SireMM_registrars.cpp b/wrapper/MM/SireMM_registrars.cpp index 2c28a793d..5e5d01da8 100644 --- a/wrapper/MM/SireMM_registrars.cpp +++ b/wrapper/MM/SireMM_registrars.cpp @@ -8,6 +8,8 @@ #include "anglerestraint.h" #include "atomljs.h" #include "bond.h" +#include "bondrestraints.h" +#include "boreschrestraints.h" #include "clj14group.h" #include "cljatoms.h" #include "cljboxes.h" @@ -55,6 +57,7 @@ #include "ljperturbation.h" #include "mmdetail.h" #include "multicljcomponent.h" +#include "positionalrestraints.h" #include "restraint.h" #include "restraintcomponent.h" #include "restraintff.h" @@ -89,6 +92,10 @@ void register_SireMM_objects() ObjectRegistry::registerConverterFor< SireMM::AtomLJs >(); ObjectRegistry::registerConverterFor< SireMM::Bond >(); ObjectRegistry::registerConverterFor< SireMol::Mover >(); + ObjectRegistry::registerConverterFor< SireMM::BondRestraint >(); + ObjectRegistry::registerConverterFor< SireMM::BondRestraints >(); + ObjectRegistry::registerConverterFor< SireMM::BoreschRestraint >(); + ObjectRegistry::registerConverterFor< SireMM::BoreschRestraints >(); ObjectRegistry::registerConverterFor< SireMM::CLJ14Group >(); ObjectRegistry::registerConverterFor< SireMM::CLJAtom >(); ObjectRegistry::registerConverterFor< SireMM::CLJAtoms >(); @@ -131,7 +138,6 @@ void register_SireMM_objects() ObjectRegistry::registerConverterFor< SireMM::DistanceRestraint >(); ObjectRegistry::registerConverterFor< SireMM::DoubleDistanceRestraint >(); ObjectRegistry::registerConverterFor< SireMM::TripleDistanceRestraint >(); - ObjectRegistry::registerConverterFor< SireMM::HarmonicDistanceForceConstant >(); ObjectRegistry::registerConverterFor< SireMM::ExcludedPairs >(); ObjectRegistry::registerConverterFor< SireMM::FourAtomFunctions >(); ObjectRegistry::registerConverterFor< SireMM::GridFF >(); @@ -203,6 +209,8 @@ void register_SireMM_objects() ObjectRegistry::registerConverterFor< SireMM::LJPerturbation >(); ObjectRegistry::registerConverterFor< SireMM::MMDetail >(); ObjectRegistry::registerConverterFor< SireMM::MultiCLJComponent >(); + ObjectRegistry::registerConverterFor< SireMM::PositionalRestraint >(); + ObjectRegistry::registerConverterFor< SireMM::PositionalRestraints >(); ObjectRegistry::registerConverterFor< SireMM::NullRestraint >(); ObjectRegistry::registerConverterFor< SireMM::RestraintComponent >(); ObjectRegistry::registerConverterFor< SireMM::RestraintFF >(); diff --git a/wrapper/MM/_MM.main.cpp b/wrapper/MM/_MM.main.cpp index 30a4d394a..8c41b429e 100644 --- a/wrapper/MM/_MM.main.cpp +++ b/wrapper/MM/_MM.main.cpp @@ -53,8 +53,16 @@ #include "BondParameterName.pypp.hpp" +#include "BondRestraint.pypp.hpp" + +#include "BondRestraints.pypp.hpp" + #include "BondSymbols.pypp.hpp" +#include "BoreschRestraint.pypp.hpp" + +#include "BoreschRestraints.pypp.hpp" + #include "CHARMMSwitchingFunction.pypp.hpp" #include "CLJ14Group.pypp.hpp" @@ -335,6 +343,10 @@ #include "NullRestraint.pypp.hpp" +#include "PositionalRestraint.pypp.hpp" + +#include "PositionalRestraints.pypp.hpp" + #include "Restraint.pypp.hpp" #include "Restraint3D.pypp.hpp" @@ -343,6 +355,8 @@ #include "RestraintFF.pypp.hpp" +#include "Restraints.pypp.hpp" + #include "ScaledCLJParameterNames3D.pypp.hpp" #include "ScaledChargeParameterNames3D.pypp.hpp" @@ -564,8 +578,18 @@ BOOST_PYTHON_MODULE(_MM){ register_BondParameterName_class(); + register_BondRestraint_class(); + + register_Restraints_class(); + + register_BondRestraints_class(); + register_BondSymbols_class(); + register_BoreschRestraint_class(); + + register_BoreschRestraints_class(); + register_SwitchingFunction_class(); register_CHARMMSwitchingFunction_class(); @@ -752,6 +776,10 @@ BOOST_PYTHON_MODULE(_MM){ register_NullRestraint_class(); + register_PositionalRestraint_class(); + + register_PositionalRestraints_class(); + register_RestraintComponent_class(); register_RestraintFF_class(); diff --git a/wrapper/MM/active_headers.h b/wrapper/MM/active_headers.h index 33fa2e88f..8659b5355 100644 --- a/wrapper/MM/active_headers.h +++ b/wrapper/MM/active_headers.h @@ -9,6 +9,8 @@ #include "atomfunctions.h" #include "atomljs.h" #include "bond.h" +#include "bondrestraints.h" +#include "boreschrestraints.h" #include "clj14group.h" #include "cljatoms.h" #include "cljboxes.h" @@ -59,9 +61,11 @@ #include "ljpotential.h" #include "mmdetail.h" #include "multicljcomponent.h" +#include "positionalrestraints.h" #include "restraint.h" #include "restraintcomponent.h" #include "restraintff.h" +#include "restraints.h" #include "selectorangle.h" #include "selectorbond.h" #include "selectordihedral.h" diff --git a/wrapper/Maths/CMakeAutogenFile.txt b/wrapper/Maths/CMakeAutogenFile.txt index ee3efe4c5..a525364c8 100644 --- a/wrapper/Maths/CMakeAutogenFile.txt +++ b/wrapper/Maths/CMakeAutogenFile.txt @@ -12,6 +12,7 @@ set ( PYPP_SOURCES BennettsFreeEnergyAverage.pypp.cpp Complex.pypp.cpp DistVector.pypp.cpp + EnergyTrajectory.pypp.cpp ExpAverage.pypp.cpp FreeEnergyAverage.pypp.cpp Histogram.pypp.cpp diff --git a/wrapper/Maths/EnergyTrajectory.pypp.cpp b/wrapper/Maths/EnergyTrajectory.pypp.cpp new file mode 100644 index 000000000..c0cc4410d --- /dev/null +++ b/wrapper/Maths/EnergyTrajectory.pypp.cpp @@ -0,0 +1,268 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#include "boost/python.hpp" +#include "EnergyTrajectory.pypp.hpp" + +namespace bp = boost::python; + +#include "SireError/errors.h" + +#include "SireID/index.h" + +#include "SireStream/datastream.h" + +#include "SireStream/shareddatastream.h" + +#include "SireUnits/units.h" + +#include "energytrajectory.h" + +#include "energytrajectory.h" + +SireMaths::EnergyTrajectory __copy__(const SireMaths::EnergyTrajectory &other){ return SireMaths::EnergyTrajectory(other); } + +#include "Qt/qdatastream.hpp" + +#include "Helpers/str.hpp" + +#include "Helpers/release_gil_policy.hpp" + +#include "Helpers/len.hpp" + +void register_EnergyTrajectory_class(){ + + { //::SireMaths::EnergyTrajectory + typedef bp::class_< SireMaths::EnergyTrajectory, bp::bases< SireBase::Property > > EnergyTrajectory_exposer_t; + EnergyTrajectory_exposer_t EnergyTrajectory_exposer = EnergyTrajectory_exposer_t( "EnergyTrajectory", "This class holds the trajectory of energies, organised by\ntimestep the energy was recorded and the types of energy\n(e.g. kinetic, potential, values at different lambda windows)\n", bp::init< >("") ); + bp::scope EnergyTrajectory_scope( EnergyTrajectory_exposer ); + EnergyTrajectory_exposer.def( bp::init< SireMaths::EnergyTrajectory const & >(( bp::arg("other") ), "") ); + { //::SireMaths::EnergyTrajectory::count + + typedef int ( ::SireMaths::EnergyTrajectory::*count_function_type)( ) const; + count_function_type count_function_value( &::SireMaths::EnergyTrajectory::count ); + + EnergyTrajectory_exposer.def( + "count" + , count_function_value + , bp::release_gil_policy() + , "Return the number of time values (number of rows)" ); + + } + { //::SireMaths::EnergyTrajectory::energies + + typedef ::QVector< double > ( ::SireMaths::EnergyTrajectory::*energies_function_type)( ::QString const & ) const; + energies_function_type energies_function_value( &::SireMaths::EnergyTrajectory::energies ); + + EnergyTrajectory_exposer.def( + "energies" + , energies_function_value + , ( bp::arg("key") ) + , bp::release_gil_policy() + , "Return all of the energy values for the passed key (the energy-key column).\n This is in the same order as the times, and is in the default internal\n unit\n" ); + + } + { //::SireMaths::EnergyTrajectory::energies + + typedef ::QVector< double > ( ::SireMaths::EnergyTrajectory::*energies_function_type)( ::QString const &,::SireUnits::Dimension::GeneralUnit const & ) const; + energies_function_type energies_function_value( &::SireMaths::EnergyTrajectory::energies ); + + EnergyTrajectory_exposer.def( + "energies" + , energies_function_value + , ( bp::arg("key"), bp::arg("energy_unit") ) + , bp::release_gil_policy() + , "Return all of the energies fro the passed key converted to the\n passed unit\n" ); + + } + { //::SireMaths::EnergyTrajectory::get + + typedef ::QHash< QString, double > ( ::SireMaths::EnergyTrajectory::*get_function_type)( int ) const; + get_function_type get_function_value( &::SireMaths::EnergyTrajectory::get ); + + EnergyTrajectory_exposer.def( + "get" + , get_function_value + , ( bp::arg("i") ) + , bp::release_gil_policy() + , "" ); + + } + { //::SireMaths::EnergyTrajectory::get + + typedef ::QHash< QString, double > ( ::SireMaths::EnergyTrajectory::*get_function_type)( int,::SireUnits::Dimension::GeneralUnit const &,::SireUnits::Dimension::GeneralUnit const & ) const; + get_function_type get_function_value( &::SireMaths::EnergyTrajectory::get ); + + EnergyTrajectory_exposer.def( + "get" + , get_function_value + , ( bp::arg("i"), bp::arg("time_unit"), bp::arg("energy_unit") ) + , bp::release_gil_policy() + , "" ); + + } + { //::SireMaths::EnergyTrajectory::isEmpty + + typedef bool ( ::SireMaths::EnergyTrajectory::*isEmpty_function_type)( ) const; + isEmpty_function_type isEmpty_function_value( &::SireMaths::EnergyTrajectory::isEmpty ); + + EnergyTrajectory_exposer.def( + "isEmpty" + , isEmpty_function_value + , bp::release_gil_policy() + , "Return whether or not this is empty" ); + + } + { //::SireMaths::EnergyTrajectory::isNull + + typedef bool ( ::SireMaths::EnergyTrajectory::*isNull_function_type)( ) const; + isNull_function_type isNull_function_value( &::SireMaths::EnergyTrajectory::isNull ); + + EnergyTrajectory_exposer.def( + "isNull" + , isNull_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMaths::EnergyTrajectory::keys + + typedef ::QStringList ( ::SireMaths::EnergyTrajectory::*keys_function_type)( ) const; + keys_function_type keys_function_value( &::SireMaths::EnergyTrajectory::keys ); + + EnergyTrajectory_exposer.def( + "keys" + , keys_function_value + , bp::release_gil_policy() + , "Return all of the energy keys" ); + + } + EnergyTrajectory_exposer.def( bp::self != bp::self ); + { //::SireMaths::EnergyTrajectory::operator= + + typedef ::SireMaths::EnergyTrajectory & ( ::SireMaths::EnergyTrajectory::*assign_function_type)( ::SireMaths::EnergyTrajectory const & ) ; + assign_function_type assign_function_value( &::SireMaths::EnergyTrajectory::operator= ); + + EnergyTrajectory_exposer.def( + "assign" + , assign_function_value + , ( bp::arg("other") ) + , bp::return_self< >() + , "" ); + + } + EnergyTrajectory_exposer.def( bp::self == bp::self ); + { //::SireMaths::EnergyTrajectory::operator[] + + typedef ::QHash< QString, double > ( ::SireMaths::EnergyTrajectory::*__getitem___function_type)( int ) const; + __getitem___function_type __getitem___function_value( &::SireMaths::EnergyTrajectory::operator[] ); + + EnergyTrajectory_exposer.def( + "__getitem__" + , __getitem___function_value + , ( bp::arg("i") ) + , "" ); + + } + { //::SireMaths::EnergyTrajectory::set + + typedef void ( ::SireMaths::EnergyTrajectory::*set_function_type)( ::SireUnits::Dimension::GeneralUnit const &,::QHash< QString, SireUnits::Dimension::GeneralUnit > const & ) ; + set_function_type set_function_value( &::SireMaths::EnergyTrajectory::set ); + + EnergyTrajectory_exposer.def( + "set" + , set_function_value + , ( bp::arg("time"), bp::arg("energies") ) + , bp::release_gil_policy() + , "Set the energies at time time to the components contained\n in energies\n" ); + + } + { //::SireMaths::EnergyTrajectory::size + + typedef int ( ::SireMaths::EnergyTrajectory::*size_function_type)( ) const; + size_function_type size_function_value( &::SireMaths::EnergyTrajectory::size ); + + EnergyTrajectory_exposer.def( + "size" + , size_function_value + , bp::release_gil_policy() + , "Return the number of time values (number of rows)" ); + + } + { //::SireMaths::EnergyTrajectory::times + + typedef ::QVector< double > ( ::SireMaths::EnergyTrajectory::*times_function_type)( ) const; + times_function_type times_function_value( &::SireMaths::EnergyTrajectory::times ); + + EnergyTrajectory_exposer.def( + "times" + , times_function_value + , bp::release_gil_policy() + , "Return all of the time values (the time column). This is\n sorted from earliest to latest time, and is in the default internal\n unit\n" ); + + } + { //::SireMaths::EnergyTrajectory::times + + typedef ::QVector< double > ( ::SireMaths::EnergyTrajectory::*times_function_type)( ::SireUnits::Dimension::GeneralUnit const & ) const; + times_function_type times_function_value( &::SireMaths::EnergyTrajectory::times ); + + EnergyTrajectory_exposer.def( + "times" + , times_function_value + , ( bp::arg("time_unit") ) + , bp::release_gil_policy() + , "Return all of the times converted to the passed unit" ); + + } + { //::SireMaths::EnergyTrajectory::toString + + typedef ::QString ( ::SireMaths::EnergyTrajectory::*toString_function_type)( ) const; + toString_function_type toString_function_value( &::SireMaths::EnergyTrajectory::toString ); + + EnergyTrajectory_exposer.def( + "toString" + , toString_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMaths::EnergyTrajectory::typeName + + typedef char const * ( *typeName_function_type )( ); + typeName_function_type typeName_function_value( &::SireMaths::EnergyTrajectory::typeName ); + + EnergyTrajectory_exposer.def( + "typeName" + , typeName_function_value + , bp::release_gil_policy() + , "" ); + + } + { //::SireMaths::EnergyTrajectory::what + + typedef char const * ( ::SireMaths::EnergyTrajectory::*what_function_type)( ) const; + what_function_type what_function_value( &::SireMaths::EnergyTrajectory::what ); + + EnergyTrajectory_exposer.def( + "what" + , what_function_value + , bp::release_gil_policy() + , "" ); + + } + EnergyTrajectory_exposer.staticmethod( "typeName" ); + EnergyTrajectory_exposer.def( "__copy__", &__copy__); + EnergyTrajectory_exposer.def( "__deepcopy__", &__copy__); + EnergyTrajectory_exposer.def( "clone", &__copy__); + EnergyTrajectory_exposer.def( "__rlshift__", &__rlshift__QDataStream< ::SireMaths::EnergyTrajectory >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + EnergyTrajectory_exposer.def( "__rrshift__", &__rrshift__QDataStream< ::SireMaths::EnergyTrajectory >, + bp::return_internal_reference<1, bp::with_custodian_and_ward<1,2> >() ); + EnergyTrajectory_exposer.def_pickle(sire_pickle_suite< ::SireMaths::EnergyTrajectory >()); + EnergyTrajectory_exposer.def( "__str__", &__str__< ::SireMaths::EnergyTrajectory > ); + EnergyTrajectory_exposer.def( "__repr__", &__str__< ::SireMaths::EnergyTrajectory > ); + EnergyTrajectory_exposer.def( "__len__", &__len_size< ::SireMaths::EnergyTrajectory > ); + } + +} diff --git a/wrapper/Maths/EnergyTrajectory.pypp.hpp b/wrapper/Maths/EnergyTrajectory.pypp.hpp new file mode 100644 index 000000000..4cb6ca9d3 --- /dev/null +++ b/wrapper/Maths/EnergyTrajectory.pypp.hpp @@ -0,0 +1,10 @@ +// This file has been generated by Py++. + +// (C) Christopher Woods, GPL >= 3 License + +#ifndef EnergyTrajectory_hpp__pyplusplus_wrapper +#define EnergyTrajectory_hpp__pyplusplus_wrapper + +void register_EnergyTrajectory_class(); + +#endif//EnergyTrajectory_hpp__pyplusplus_wrapper diff --git a/wrapper/Maths/SireMaths_registrars.cpp b/wrapper/Maths/SireMaths_registrars.cpp index 3a578f378..7b4d37c7b 100644 --- a/wrapper/Maths/SireMaths_registrars.cpp +++ b/wrapper/Maths/SireMaths_registrars.cpp @@ -8,6 +8,7 @@ #include "axisset.h" #include "complex.h" #include "distvector.h" +#include "energytrajectory.h" #include "freeenergyaverage.h" #include "histogram.h" #include "line.h" @@ -41,6 +42,7 @@ void register_SireMaths_objects() ObjectRegistry::registerConverterFor< SireMaths::AxisSet >(); ObjectRegistry::registerConverterFor< SireMaths::Complex >(); ObjectRegistry::registerConverterFor< SireMaths::DistVector >(); + ObjectRegistry::registerConverterFor< SireMaths::EnergyTrajectory >(); ObjectRegistry::registerConverterFor< SireMaths::FreeEnergyAverage >(); ObjectRegistry::registerConverterFor< SireMaths::BennettsFreeEnergyAverage >(); ObjectRegistry::registerConverterFor< SireMaths::Histogram >(); diff --git a/wrapper/Maths/_Maths.main.cpp b/wrapper/Maths/_Maths.main.cpp index 54ba52949..94710fd2f 100644 --- a/wrapper/Maths/_Maths.main.cpp +++ b/wrapper/Maths/_Maths.main.cpp @@ -31,6 +31,8 @@ #include "DistVector.pypp.hpp" +#include "EnergyTrajectory.pypp.hpp" + #include "ExpAverage.pypp.hpp" #include "FreeEnergyAverage.pypp.hpp" @@ -158,6 +160,8 @@ BOOST_PYTHON_MODULE(_Maths){ register_DistVector_class(); + register_EnergyTrajectory_class(); + register_Histogram_class(); register_HistogramBin_class(); diff --git a/wrapper/Maths/active_headers.h b/wrapper/Maths/active_headers.h index 82e4da4d1..cba4b9e76 100644 --- a/wrapper/Maths/active_headers.h +++ b/wrapper/Maths/active_headers.h @@ -9,6 +9,7 @@ #include "boys.h" #include "complex.h" #include "distvector.h" +#include "energytrajectory.h" #include "freeenergyaverage.h" #include "gamma.h" #include "histogram.h" diff --git a/wrapper/Mol/FrameTransform.pypp.cpp b/wrapper/Mol/FrameTransform.pypp.cpp index 755f39b1c..5868988da 100644 --- a/wrapper/Mol/FrameTransform.pypp.cpp +++ b/wrapper/Mol/FrameTransform.pypp.cpp @@ -141,6 +141,18 @@ void register_FrameTransform_class(){ , bp::release_gil_policy() , "" ); + } + { //::SireMol::FrameTransform::isNull + + typedef bool ( ::SireMol::FrameTransform::*isNull_function_type)( ) const; + isNull_function_type isNull_function_value( &::SireMol::FrameTransform::isNull ); + + FrameTransform_exposer.def( + "isNull" + , isNull_function_value + , bp::release_gil_policy() + , "" ); + } { //::SireMol::FrameTransform::nSmooth @@ -280,6 +292,19 @@ void register_FrameTransform_class(){ , bp::release_gil_policy() , "" ); + } + { //::SireMol::FrameTransform::wouldCreateTransform + + typedef bool ( *wouldCreateTransform_function_type )( ::SireBase::PropertyMap const & ); + wouldCreateTransform_function_type wouldCreateTransform_function_value( &::SireMol::FrameTransform::wouldCreateTransform ); + + FrameTransform_exposer.def( + "wouldCreateTransform" + , wouldCreateTransform_function_value + , ( bp::arg("map") ) + , bp::release_gil_policy() + , "" ); + } { //::SireMol::FrameTransform::wrap @@ -294,6 +319,7 @@ void register_FrameTransform_class(){ } FrameTransform_exposer.staticmethod( "typeName" ); + FrameTransform_exposer.staticmethod( "wouldCreateTransform" ); FrameTransform_exposer.def( "__copy__", &__copy__); FrameTransform_exposer.def( "__deepcopy__", &__copy__); FrameTransform_exposer.def( "clone", &__copy__); diff --git a/wrapper/Mol/GeometryPerturbations.pypp.cpp b/wrapper/Mol/GeometryPerturbations.pypp.cpp index 9ba0728ef..5f577f436 100644 --- a/wrapper/Mol/GeometryPerturbations.pypp.cpp +++ b/wrapper/Mol/GeometryPerturbations.pypp.cpp @@ -46,6 +46,19 @@ void register_GeometryPerturbations_class(){ GeometryPerturbations_exposer.def( bp::init< SireMol::GeometryPerturbation const & >(( bp::arg("perturbation") ), "Construct to hold just a single perturbation") ); GeometryPerturbations_exposer.def( bp::init< QList< SireBase::PropPtr< SireMol::GeometryPerturbation > > const & >(( bp::arg("perturbations") ), "Construct to hold just a single perturbation") ); GeometryPerturbations_exposer.def( bp::init< SireMol::GeometryPerturbations const & >(( bp::arg("other") ), "Copy constructor") ); + { //::SireMol::GeometryPerturbations::append + + typedef void ( ::SireMol::GeometryPerturbations::*append_function_type)( ::SireMol::GeometryPerturbation const & ) ; + append_function_type append_function_value( &::SireMol::GeometryPerturbations::append ); + + GeometryPerturbations_exposer.def( + "append" + , append_function_value + , ( bp::arg("perturbation") ) + , bp::release_gil_policy() + , "Append the passed perturbation onto the list" ); + + } { //::SireMol::GeometryPerturbations::children typedef ::QList< SireBase::PropPtr< SireMol::Perturbation > > ( ::SireMol::GeometryPerturbations::*children_function_type)( ) const; diff --git a/wrapper/Mol/MolEditor.pypp.cpp b/wrapper/Mol/MolEditor.pypp.cpp index 77676f684..105683b9c 100644 --- a/wrapper/Mol/MolEditor.pypp.cpp +++ b/wrapper/Mol/MolEditor.pypp.cpp @@ -147,6 +147,19 @@ void register_MolEditor_class(){ , bp::release_gil_policy() , "Add a segment called name and return an editor that can\nbe used to edit it" ); + } + { //::SireMol::MolEditor::addLink + + typedef ::SireMol::MolEditor & ( ::SireMol::MolEditor::*addLink_function_type)( ::QString const &,::QString const & ) ; + addLink_function_type addLink_function_value( &::SireMol::MolEditor::addLink ); + + MolEditor_exposer.def( + "addLink" + , addLink_function_value + , ( bp::arg("key"), bp::arg("linked_property") ) + , bp::return_self< >() + , "Add a link from the property key to the property linked_property.\n The linked_property will be returned if there is no property\n called key in this set.\n\n Note that the linked property must already be contained in this set.\n" ); + } { //::SireMol::MolEditor::commit @@ -286,6 +299,18 @@ void register_MolEditor_class(){ , bp::release_gil_policy() , "Remove all CutGroups from this molecule. This returns an editor that\ncan be used to further edit the structure of this molecule" ); + } + { //::SireMol::MolEditor::removeAllLinks + + typedef ::SireMol::MolEditor & ( ::SireMol::MolEditor::*removeAllLinks_function_type)( ) ; + removeAllLinks_function_type removeAllLinks_function_value( &::SireMol::MolEditor::removeAllLinks ); + + MolEditor_exposer.def( + "removeAllLinks" + , removeAllLinks_function_value + , bp::return_self< >() + , "Remove all property links from this set" ); + } { //::SireMol::MolEditor::removeAllResidues @@ -310,6 +335,19 @@ void register_MolEditor_class(){ , bp::release_gil_policy() , "Remove all segments from this molecule. This returns an editor that\ncan be used to further edit the structure of this molecule" ); + } + { //::SireMol::MolEditor::removeLink + + typedef ::SireMol::MolEditor & ( ::SireMol::MolEditor::*removeLink_function_type)( ::QString const & ) ; + removeLink_function_type removeLink_function_value( &::SireMol::MolEditor::removeLink ); + + MolEditor_exposer.def( + "removeLink" + , removeLink_function_value + , ( bp::arg("key") ) + , bp::return_self< >() + , "Remove the link associated with the key key" ); + } { //::SireMol::MolEditor::rename @@ -411,6 +449,18 @@ void register_MolEditor_class(){ , bp::release_gil_policy() , "" ); + } + { //::SireMol::MolEditor::updateProperty + + typedef bool ( ::SireMol::MolEditor::*updateProperty_function_type)( ::QString const &,::SireBase::Property const &,bool ) ; + updateProperty_function_type updateProperty_function_value( &::SireMol::MolEditor::updateProperty ); + + MolEditor_exposer.def( + "updateProperty" + , updateProperty_function_value + , ( bp::arg("key"), bp::arg("value"), bp::arg("auto_add")=(bool)(true) ) + , "Update the passed property to have the value value. This does\n an in-place update on the existing property (which must have\n a compatible type). If auto-add is true, then this will add\n the property if it doesnt exist. This returns whether or not\n a property was updated (or added)\n" ); + } MolEditor_exposer.staticmethod( "typeName" ); MolEditor_exposer.def( "__copy__", &__copy__); diff --git a/wrapper/Mol/MoleculeView.pypp.cpp b/wrapper/Mol/MoleculeView.pypp.cpp index 8c8462f14..c4f7778fc 100644 --- a/wrapper/Mol/MoleculeView.pypp.cpp +++ b/wrapper/Mol/MoleculeView.pypp.cpp @@ -675,6 +675,43 @@ void register_MoleculeView_class(){ , bp::release_gil_policy() , "Extract a copy of this view which contains only the currently\nselected atoms. This allows the used to pull out parts of a larger molecule,\ne.g. if they want to have only selected residues in a protein and do not\nwant to have to store or manipulate the larger protein molecule" ); + } + { //::SireMol::MoleculeView::getLink + + typedef ::QString ( ::SireMol::MoleculeView::*getLink_function_type)( ::SireBase::PropertyName const & ) const; + getLink_function_type getLink_function_value( &::SireMol::MoleculeView::getLink ); + + MoleculeView_exposer.def( + "getLink" + , getLink_function_value + , ( bp::arg("key") ) + , bp::release_gil_policy() + , "\n Return the link for the given property name. This will throw\n an exception if there is no link for the given property name.\n" ); + + } + { //::SireMol::MoleculeView::getLinks + + typedef ::QHash< QString, QString > ( ::SireMol::MoleculeView::*getLinks_function_type)( ) const; + getLinks_function_type getLinks_function_value( &::SireMol::MoleculeView::getLinks ); + + MoleculeView_exposer.def( + "getLinks" + , getLinks_function_value + , bp::release_gil_policy() + , "\n Return the property links for this molecule. This returns an\n empty dictionary if there are no links.\n" ); + + } + { //::SireMol::MoleculeView::hasLinks + + typedef bool ( ::SireMol::MoleculeView::*hasLinks_function_type)( ) const; + hasLinks_function_type hasLinks_function_value( &::SireMol::MoleculeView::hasLinks ); + + MoleculeView_exposer.def( + "hasLinks" + , hasLinks_function_value + , bp::release_gil_policy() + , "\n Return whether or not this molecule has any property links\n" ); + } { //::SireMol::MoleculeView::hasMetadata @@ -726,6 +763,19 @@ void register_MoleculeView_class(){ , bp::release_gil_policy() , "Return whether or not this molecule view is empty" ); + } + { //::SireMol::MoleculeView::isLink + + typedef bool ( ::SireMol::MoleculeView::*isLink_function_type)( ::SireBase::PropertyName const & ) const; + isLink_function_type isLink_function_value( &::SireMol::MoleculeView::isLink ); + + MoleculeView_exposer.def( + "isLink" + , isLink_function_value + , ( bp::arg("key") ) + , bp::release_gil_policy() + , "\n Return whether or not this molecule has a link for the given\n property name\n" ); + } { //::SireMol::MoleculeView::isNull diff --git a/wrapper/Mol/SireMol_containers.cpp b/wrapper/Mol/SireMol_containers.cpp index 8a7da7eac..f6ac584ef 100644 --- a/wrapper/Mol/SireMol_containers.cpp +++ b/wrapper/Mol/SireMol_containers.cpp @@ -186,7 +186,6 @@ void register_SireMol_containers() register_dict>>(); -#if QT_VERSION >= QT_VERSION_CHECK(4, 2, 0) register_dict>>(); register_dict>>(); register_dict>>(); @@ -194,27 +193,11 @@ void register_SireMol_containers() register_dict>>(); register_dict>(); register_dict>(); + register_dict>(); register_set>(); register_set>(); register_set>(); register_set>(); - -#else - register_dict>, MolNum, Selector>(); - register_dict>, MolNum, Selector>(); - register_dict>, MolNum, Selector>(); - register_dict>, MolNum, Selector>(); - register_dict>, MolNum, Selector>(); - register_dict, MolNum, double>(); - register_dict, AtomIdx, AtomIdx>(); - - register_set, AtomIdx>(); - register_set, ResIdx>(); - - register_set, MolNum>(); - register_set, MolName>(); - -#endif } diff --git a/wrapper/Mol/special_code.py b/wrapper/Mol/special_code.py index 4d35de7c0..87e6e390b 100644 --- a/wrapper/Mol/special_code.py +++ b/wrapper/Mol/special_code.py @@ -265,13 +265,18 @@ def fix_ResStructureEditor(c): c.decls("transferAll").call_policies = call_policies.return_self() -def fix_MolEditor(c): +def fix_MolEditor(c, include_links=True): c.decls("renumber").call_policies = call_policies.return_self() c.decls("rename").call_policies = call_policies.return_self() + if include_links: + c.decls("addLink").call_policies = call_policies.return_self() + c.decls("removeLink").call_policies = call_policies.return_self() + c.decls("removeAllLinks").call_policies = call_policies.return_self() + def fix_MolStructureEditor(c): - fix_MolEditor(c) + fix_MolEditor(c, include_links=False) c.decls("remove").call_policies = call_policies.return_self() c.decls("removeAllAtoms").call_policies = call_policies.return_self() diff --git a/wrapper/Units/Celsius.pypp.cpp b/wrapper/Units/Celsius.pypp.cpp index a0ce9d00e..3a8c32273 100644 --- a/wrapper/Units/Celsius.pypp.cpp +++ b/wrapper/Units/Celsius.pypp.cpp @@ -7,6 +7,8 @@ namespace bp = boost::python; +#include "generalunit.h" + #include "temperature.h" #include "temperature.h" diff --git a/wrapper/Units/Fahrenheit.pypp.cpp b/wrapper/Units/Fahrenheit.pypp.cpp index 6a0712189..1ac08bbea 100644 --- a/wrapper/Units/Fahrenheit.pypp.cpp +++ b/wrapper/Units/Fahrenheit.pypp.cpp @@ -7,6 +7,8 @@ namespace bp = boost::python; +#include "generalunit.h" + #include "temperature.h" #include "temperature.h" diff --git a/wrapper/Units/GeneralUnit.pypp.cpp b/wrapper/Units/GeneralUnit.pypp.cpp index 2eb42691d..b29d1e003 100644 --- a/wrapper/Units/GeneralUnit.pypp.cpp +++ b/wrapper/Units/GeneralUnit.pypp.cpp @@ -15,6 +15,8 @@ namespace bp = boost::python; #include "SireUnits/temperature.h" +#include "SireUnits/units.h" + #include "generalunit.h" #include "tostring.h" @@ -198,7 +200,7 @@ void register_GeneralUnit_class(){ "dimensions" , dimensions_function_value , bp::release_gil_policy() - , "" ); + , "Return the physical dimensions of this unit, in the order\n (M,L,T,C,t,Q,A)\n" ); } { //::SireUnits::Dimension::GeneralUnit::fromString diff --git a/wrapper/Units/SireUnits_containers.cpp b/wrapper/Units/SireUnits_containers.cpp index 7f2a4a822..399351bcd 100644 --- a/wrapper/Units/SireUnits_containers.cpp +++ b/wrapper/Units/SireUnits_containers.cpp @@ -50,30 +50,36 @@ using boost::python::register_tuple; void register_SireUnits_containers() { - register_list< QVector >(); - register_list< QVector >(); - register_list< QVector >(); - register_list< QVector > (); - register_list< QVector > (); - register_list< QVector >(); - register_list< QVector >(); - register_list< QVector >(); + register_list>(); + register_list>(); + register_list>(); + register_list>(); + register_list>(); + register_list>(); + register_list>(); + register_list>(); - register_list< QList >(); - register_list< QList >(); - register_list< QList >(); - register_list< QList > (); - register_list< QList > (); - register_list< QList > (); - register_list< QList >(); - register_list< QList >(); + register_list>(); + register_list>(); - register_list< QList >(); - register_list< QVector >(); - register_list< QVector< QVector > >(); - register_dict< QHash >(); + register_list>(); + register_list>(); + register_list>(); + register_list>(); + register_list>(); + register_list>(); + register_list>(); + register_list>(); - register_PackedArray< SireBase::PackedArray2D >(); - register_PackedArray< SireBase::PackedArray2D >(); - register_PackedArray< SireBase::PackedArray2D >(); + register_list>(); + register_list>(); + + register_list>(); + register_list>(); + register_list>>(); + register_dict>(); + + register_PackedArray>(); + register_PackedArray>(); + register_PackedArray>(); } diff --git a/wrapper/Units/TempBase.pypp.cpp b/wrapper/Units/TempBase.pypp.cpp index b5aa0bc7d..80b121b4f 100644 --- a/wrapper/Units/TempBase.pypp.cpp +++ b/wrapper/Units/TempBase.pypp.cpp @@ -7,6 +7,8 @@ namespace bp = boost::python; +#include "generalunit.h" + #include "temperature.h" #include "temperature.h" diff --git a/wrapper/Units/_Units_global_variables.pyman.cpp b/wrapper/Units/_Units_global_variables.pyman.cpp index 7aefe573b..53aa9345b 100644 --- a/wrapper/Units/_Units_global_variables.pyman.cpp +++ b/wrapper/Units/_Units_global_variables.pyman.cpp @@ -194,6 +194,10 @@ void register_man_global_variables() scope().attr("int_cal") = int_cal; + scope().attr("kcal_per_mol_per_A2") = kcal_per_mol_per_A2; + + scope().attr("kcal_per_mol_per_rad2") = kcal_per_mol_per_rad2; + scope().attr("hartree") = hartree; scope().attr("akma_time") = akma_time; diff --git a/wrapper/Units/sireunits_dimensions.cpp b/wrapper/Units/sireunits_dimensions.cpp index c676e28e5..c10408d38 100644 --- a/wrapper/Units/sireunits_dimensions.cpp +++ b/wrapper/Units/sireunits_dimensions.cpp @@ -37,7 +37,7 @@ using namespace SireUnits::Dimension; using namespace boost::python; -template +template struct from_general_unit { /** Constructor - register the conversion functions @@ -47,22 +47,22 @@ struct from_general_unit boost::python::converter::registry::push_back( &convertible, &construct, - type_id< D >()); + type_id()); } /** Test whether or not it is possible to convert the PyObject holding a GeneralUnit to this specific PhysUnit*/ - static void* convertible(PyObject* obj_ptr) + static void *convertible(PyObject *obj_ptr) { - object obj( handle<>(borrowed(obj_ptr)) ); + object obj(handle<>(borrowed(obj_ptr))); - extract x(obj); + extract x(obj); - //is this a GeneralUnit? - if ( x.check() ) + // is this a GeneralUnit? + if (x.check()) { - //it is ;-) Get a reference to it and make sure - //that it is of the right dimension + // it is ;-) Get a reference to it and make sure + // that it is of the right dimension const Dimension::GeneralUnit &gen_unit = x(); if (gen_unit.isZero()) @@ -70,64 +70,64 @@ struct from_general_unit // zero is compatible with everything return obj_ptr; } - else if ( gen_unit.MASS() == D::MASS() and - gen_unit.LENGTH() == D::LENGTH() and - gen_unit.TIME() == D::TIME() and - gen_unit.CHARGE() == D::CHARGE() and - gen_unit.TEMPERATURE() == D::TEMPERATURE() and - gen_unit.QUANTITY() == D::QUANTITY() and - gen_unit.ANGLE() == D::ANGLE() ) + else if (gen_unit.MASS() == D::MASS() and + gen_unit.LENGTH() == D::LENGTH() and + gen_unit.TIME() == D::TIME() and + gen_unit.CHARGE() == D::CHARGE() and + gen_unit.TEMPERATURE() == D::TEMPERATURE() and + gen_unit.QUANTITY() == D::QUANTITY() and + gen_unit.ANGLE() == D::ANGLE()) { - //this has the right dimension :-) + // this has the right dimension :-) return obj_ptr; } } - //could not recognise the type or the dimension was wrong + // could not recognise the type or the dimension was wrong return 0; } /** Construct a PhysUnit from the passed GeneralUnit */ static void construct( - PyObject* obj_ptr, - boost::python::converter::rvalue_from_python_stage1_data* data) + PyObject *obj_ptr, + boost::python::converter::rvalue_from_python_stage1_data *data) { - object obj( handle<>(borrowed(obj_ptr)) ); + object obj(handle<>(borrowed(obj_ptr))); - extract x(obj); + extract x(obj); - //is this a GeneralUnit? - if ( x.check() ) + // is this a GeneralUnit? + if (x.check()) { - //it is ;-) Get a reference to it and make sure - //that it is of the right dimension + // it is ;-) Get a reference to it and make sure + // that it is of the right dimension const Dimension::GeneralUnit &gen_unit = x(); - if ( gen_unit.isZero() ) + if (gen_unit.isZero()) { // zero is compatible with everything - void* storage = - ( (converter::rvalue_from_python_storage*)data )->storage.bytes; + void *storage = + ((converter::rvalue_from_python_storage *)data)->storage.bytes; - //create the T container + // create the T container new (storage) D(0.0); data->convertible = storage; } - else if ( gen_unit.MASS() == D::MASS() and - gen_unit.LENGTH() == D::LENGTH() and - gen_unit.TIME() == D::TIME() and - gen_unit.CHARGE() == D::CHARGE() and - gen_unit.TEMPERATURE() == D::TEMPERATURE() and - gen_unit.QUANTITY() == D::QUANTITY() and - gen_unit.ANGLE() == D::ANGLE() ) + else if (gen_unit.MASS() == D::MASS() and + gen_unit.LENGTH() == D::LENGTH() and + gen_unit.TIME() == D::TIME() and + gen_unit.CHARGE() == D::CHARGE() and + gen_unit.TEMPERATURE() == D::TEMPERATURE() and + gen_unit.QUANTITY() == D::QUANTITY() and + gen_unit.ANGLE() == D::ANGLE()) { - //locate the storage space for the result - void* storage = - ( (converter::rvalue_from_python_storage*)data )->storage.bytes; + // locate the storage space for the result + void *storage = + ((converter::rvalue_from_python_storage *)data)->storage.bytes; - //create the T container - new (storage) D( gen_unit.scaleFactor() ); + // create the T container + new (storage) D(gen_unit.scaleFactor()); data->convertible = storage; } @@ -135,57 +135,61 @@ struct from_general_unit } }; -template +template struct to_general_unit { - static PyObject* convert(const D &unit) + static PyObject *convert(const D &unit) { - return incref( object(Dimension::GeneralUnit(unit)).ptr() ); + return incref(object(Dimension::GeneralUnit(unit)).ptr()); } }; -template +template void register_dimension() { - to_python_converter< D, to_general_unit >(); + to_python_converter>(); converter::registry::push_back( - &from_general_unit::convertible, - &from_general_unit::construct, - type_id() ); + &from_general_unit::convertible, + &from_general_unit::construct, + type_id()); } void register_SireUnits_dimensions() { - register_dimension< Dimensionless >(); - register_dimension< Mass >(); - register_dimension< MolarMass >(); - register_dimension< Length >(); - register_dimension< Time >(); - register_dimension< Charge >(); - register_dimension< MolarCharge >(); - register_dimension< Temperature >(); - register_dimension< Quantity >(); - register_dimension< Angle >(); - register_dimension< Area >(); - register_dimension< Volume >(); - register_dimension< MolarVolume >(); - register_dimension< Velocity >(); - register_dimension< Acceleration >(); - register_dimension< AngularVelocity >(); - register_dimension< AngularAcceleration >(); - register_dimension< Energy >(); - register_dimension< MolarEnergy >(); - register_dimension< Power >(); - register_dimension< MolarPower >(); - register_dimension< Density >(); - register_dimension< MolarDensity >(); - register_dimension< Force >(); - register_dimension< Pressure >(); - register_dimension< Current >(); - register_dimension< Potential >(); - register_dimension< Capacitance >(); - - register_dimension< SireMM::HarmonicDistanceForceConstant >(); - + register_dimension(); + register_dimension(); + register_dimension(); + register_dimension(); + register_dimension