Add nblib backend: Part 1 of 2
authorJoe Jordan <ejjordan12@gmail.com>
Mon, 28 Sep 2020 17:21:12 +0000 (17:21 +0000)
committerJoe Jordan <ejjordan12@gmail.com>
Mon, 28 Sep 2020 17:21:12 +0000 (17:21 +0000)
21 files changed:
api/nblib/CMakeLists.txt
api/nblib/interactions.cpp [new file with mode: 0644]
api/nblib/molecules.cpp [new file with mode: 0644]
api/nblib/particletype.cpp [new file with mode: 0644]
api/nblib/tests/CMakeLists.txt
api/nblib/tests/box.cpp
api/nblib/tests/interactions.cpp [new file with mode: 0644]
api/nblib/tests/molecules.cpp [new file with mode: 0644]
api/nblib/tests/particletype.cpp [new file with mode: 0644]
api/nblib/tests/testsystems.cpp [new file with mode: 0644]
api/nblib/tests/testsystems.h [new file with mode: 0644]
api/nblib/tests/topology.cpp [new file with mode: 0644]
api/nblib/topology.cpp [new file with mode: 0644]
api/nblib/topology.h
api/nblib/topologyhelpers.cpp [new file with mode: 0644]
api/nblib/util/CMakeLists.txt
api/nblib/util/internal.h [new file with mode: 0644]
api/nblib/util/tests/CMakeLists.txt [new file with mode: 0644]
api/nblib/util/tests/refdata/NBlibTest_GeneratedVelocitiesAreCorrect.xml [new file with mode: 0644]
api/nblib/util/tests/user.cpp [new file with mode: 0644]
api/nblib/util/user.cpp [new file with mode: 0644]

index d92950b4de44d66d13fe73f73131497ec4006ddd..3d307976af0633a71006cb8fad0d2cb4ddc5d356 100644 (file)
@@ -88,8 +88,14 @@ add_library(nblib SHARED "")
 target_sources(nblib
         PRIVATE
         box.cpp
+        interactions.cpp
+        molecules.cpp
+        particletype.cpp
+        topologyhelpers.cpp
+        topology.cpp
         )
 
+
 set_target_properties(nblib
         PROPERTIES
         LINKER_LANGUAGE CXX
diff --git a/api/nblib/interactions.cpp b/api/nblib/interactions.cpp
new file mode 100644 (file)
index 0000000..b63ec9e
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2020, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * Implements nblib particle-types interactions
+ *
+ * \author Victor Holanda <victor.holanda@cscs.ch>
+ * \author Joe Jordan <ejjordan@kth.se>
+ * \author Prashanth Kanduri <kanduri@cscs.ch>
+ * \author Sebastian Keller <keller@cscs.ch>
+ * \author Artem Zhmurov <zhmurov@gmail.com>
+ */
+#include <cmath>
+#include <set>
+
+#include "nblib/exception.h"
+#include "nblib/interactions.h"
+#include "nblib/util/internal.h"
+
+namespace nblib
+{
+
+C6 NonBondedInteractionMap::getC6(const ParticleTypeName& first, const ParticleTypeName& second) const
+{
+    return std::get<0>(interactionMap_.at(std::make_tuple(first, second)));
+}
+
+C12 NonBondedInteractionMap::getC12(const ParticleTypeName& first, const ParticleTypeName& second) const
+{
+    return std::get<1>(interactionMap_.at(std::make_tuple(first, second)));
+}
+
+void NonBondedInteractionMap::setInteractions(const ParticleTypeName& first,
+                                              const ParticleTypeName& second,
+                                              const C6                c6_combo,
+                                              const C12               c12_combo)
+{
+    auto interactionKey             = std::make_tuple(first, second);
+    interactionMap_[interactionKey] = std::make_tuple(c6_combo, c12_combo);
+}
+
+size_t NonBondedInteractionMap::count(const NonBondedInteractionMap::NamePairTuple& namePairTuple)
+{
+    return interactionMap_.count(namePairTuple);
+}
+
+namespace detail
+{
+
+//! Combines the non-bonded parameters from two particles for pairwise interactions
+real combineNonbondedParameters(real v, real w, CombinationRule combinationRule)
+{
+    if (combinationRule == CombinationRule::Geometric)
+    {
+        return std::sqrt(v * w);
+    }
+    else
+    {
+        throw InputException("unknown LJ Combination rule specified\n");
+    }
+}
+
+} // namespace detail
+
+ParticleTypesInteractions::ParticleTypesInteractions(CombinationRule cr) : combinationRule_(cr) {}
+
+ParticleTypesInteractions& ParticleTypesInteractions::add(const ParticleTypeName& particleTypeName,
+                                                          C6                      c6,
+                                                          C12                     c12)
+{
+    auto insertLocation = singleParticleInteractionsMap_.insert(
+            std::make_pair(particleTypeName, std::make_tuple(c6, c12)));
+
+    if (!insertLocation.second) // if particleTypeName already existed
+    {
+        if (std::get<0>(insertLocation.first->second) != c6
+            || std::get<1>(insertLocation.first->second) != c12)
+        {
+            std::string message = formatString(
+                    "Attempting to add nonbonded interaction parameters for particle "
+                    "type {} twice",
+                    particleTypeName.value());
+            throw InputException(message);
+        }
+    }
+    return *this;
+}
+
+ParticleTypesInteractions& ParticleTypesInteractions::add(const ParticleTypeName& particleTypeName1,
+                                                          const ParticleTypeName& particleTypeName2,
+                                                          C6                      c6,
+                                                          C12                     c12)
+{
+    auto interactionKey         = std::make_tuple(particleTypeName1, particleTypeName2);
+    auto possibleInteractionKey = std::make_tuple(particleTypeName2, particleTypeName1);
+
+    auto insertLocation = twoParticlesInteractionsMap_.insert(
+            std::make_pair(interactionKey, std::make_tuple(c6, c12)));
+    twoParticlesInteractionsMap_.insert(std::make_pair(possibleInteractionKey, std::make_tuple(c6, c12)));
+
+    if (!insertLocation.second) // if particleTypeName already existed
+    {
+        if (std::get<0>(insertLocation.first->second) != c6
+            || std::get<1>(insertLocation.first->second) != c12)
+        {
+            std::string message = formatString(
+                    "Attempting to add nonbonded interaction parameters between the particle types "
+                    "{} {} twice",
+                    particleTypeName1.value(), particleTypeName2.value());
+            throw InputException(message);
+        }
+    }
+    return *this;
+}
+
+NonBondedInteractionMap ParticleTypesInteractions::generateTable() const
+{
+    NonBondedInteractionMap nonbondedParameters_;
+
+    // creating the combination rule based interaction matrix
+    for (const auto& particleType1 : singleParticleInteractionsMap_)
+    {
+        C6  c6_1  = std::get<0>(particleType1.second);
+        C12 c12_1 = std::get<1>(particleType1.second);
+
+        for (const auto& particleType2 : singleParticleInteractionsMap_)
+        {
+            C6  c6_2  = std::get<0>(particleType2.second);
+            C12 c12_2 = std::get<1>(particleType2.second);
+
+            C6  c6_combo{ detail::combineNonbondedParameters(c6_1, c6_2, combinationRule_) };
+            C12 c12_combo{ detail::combineNonbondedParameters(c12_1, c12_2, combinationRule_) };
+
+            nonbondedParameters_.setInteractions(particleType1.first, particleType2.first, c6_combo,
+                                                 c12_combo);
+        }
+    }
+
+    // updating the interaction matrix based on the user fine tuned parameters
+    for (const auto& particleTypeTuple : twoParticlesInteractionsMap_)
+    {
+        const auto& first  = std::get<0>(particleTypeTuple.first);
+        const auto& second = std::get<1>(particleTypeTuple.first);
+
+        C6  c6_combo  = std::get<0>(particleTypeTuple.second);
+        C12 c12_combo = std::get<1>(particleTypeTuple.second);
+
+        nonbondedParameters_.setInteractions(first, second, c6_combo, c12_combo);
+    }
+
+    std::set<ParticleTypeName> particleTypes;
+    for (auto const& typeKey : nonbondedParameters_)
+    { // we don't need to get<1> because the list is guaranteed to be symmetric
+        particleTypes.insert(std::get<0>(typeKey.first));
+    }
+
+    // check whether there is any missing interaction
+    for (const ParticleTypeName& particleTypeName1 : particleTypes)
+    {
+        for (const ParticleTypeName& particleTypeName2 : particleTypes)
+        {
+            auto interactionKey = std::make_tuple(particleTypeName1, particleTypeName2);
+            if (nonbondedParameters_.count(interactionKey) == 0)
+            {
+                std::string message = formatString("Missing interaction between {} {}",
+                                                   particleTypeName1.value(), particleTypeName2.value());
+                throw InputException(message);
+            }
+        }
+    }
+    return nonbondedParameters_;
+}
+
+CombinationRule ParticleTypesInteractions::getCombinationRule() const
+{
+    return combinationRule_;
+}
+
+void ParticleTypesInteractions::merge(const ParticleTypesInteractions& other)
+{
+    for (const auto& keyval : other.singleParticleInteractionsMap_)
+    {
+        add(keyval.first, std::get<0>(keyval.second), std::get<1>(keyval.second));
+    }
+
+    for (const auto& keyval : other.twoParticlesInteractionsMap_)
+    {
+        add(std::get<0>(keyval.first), std::get<1>(keyval.first), std::get<0>(keyval.second),
+            std::get<1>(keyval.second));
+    }
+}
+
+} // namespace nblib
diff --git a/api/nblib/molecules.cpp b/api/nblib/molecules.cpp
new file mode 100644 (file)
index 0000000..b8f70ee
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2020, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * Implements nblib Molecule
+ *
+ * \author Victor Holanda <victor.holanda@cscs.ch>
+ * \author Joe Jordan <ejjordan@kth.se>
+ * \author Prashanth Kanduri <kanduri@cscs.ch>
+ * \author Sebastian Keller <keller@cscs.ch>
+ * \author Artem Zhmurov <zhmurov@gmail.com>
+ */
+#include <algorithm>
+#include <tuple>
+
+#include "nblib/exception.h"
+#include "nblib/molecules.h"
+#include "nblib/particletype.h"
+#include "nblib/util/internal.h"
+
+namespace nblib
+{
+
+
+Molecule::Molecule(MoleculeName moleculeName) : name_(std::move(moleculeName)) {}
+
+MoleculeName Molecule::name() const
+{
+    return name_;
+}
+
+Molecule& Molecule::addParticle(const ParticleName& particleName,
+                                const ResidueName&  residueName,
+                                const Charge&       charge,
+                                ParticleType const& particleType)
+{
+    auto found = particleTypes_.find(particleType.name());
+    if (found == particleTypes_.end())
+    {
+        particleTypes_.insert(std::make_pair(particleType.name(), particleType));
+    }
+    else
+    {
+        if (!(found->second == particleType))
+        {
+            throw InputException(
+                    "Differing ParticleTypes with identical names encountered in the same "
+                    "molecule.");
+        }
+    }
+
+    particles_.emplace_back(ParticleData{ particleName, residueName, particleType.name(), charge });
+
+    // Add self exclusion. We just added the particle, so we know its index and that the exclusion doesn't exist yet
+    std::size_t id = particles_.size() - 1;
+    exclusions_.emplace_back(id, id);
+
+    return *this;
+}
+
+Molecule& Molecule::addParticle(const ParticleName& particleName,
+                                const ResidueName&  residueName,
+                                ParticleType const& particleType)
+{
+    addParticle(particleName, residueName, Charge(0), particleType);
+
+    return *this;
+}
+
+Molecule& Molecule::addParticle(const ParticleName& particleName,
+                                const Charge&       charge,
+                                ParticleType const& particleType)
+{
+    addParticle(particleName, ResidueName(name_), charge, particleType);
+
+    return *this;
+}
+
+Molecule& Molecule::addParticle(const ParticleName& particleName, const ParticleType& particleType)
+{
+    addParticle(particleName, ResidueName(name_), Charge(0), particleType);
+
+    return *this;
+}
+
+int Molecule::numParticlesInMolecule() const
+{
+    return particles_.size();
+}
+
+void Molecule::addExclusion(const int particleIndex, const int particleIndexToExclude)
+{
+    // We do not need to add exclusion in case the particle indexes are the same
+    // because self exclusion are added by addParticle
+    if (particleIndex != particleIndexToExclude)
+    {
+        exclusions_.emplace_back(particleIndex, particleIndexToExclude);
+        exclusions_.emplace_back(particleIndexToExclude, particleIndex);
+    }
+}
+
+void Molecule::addExclusion(std::tuple<std::string, std::string> particle,
+                            std::tuple<std::string, std::string> particleToExclude)
+{
+    // duplication for the swapped pair happens in getExclusions()
+    exclusionsByName_.emplace_back(std::make_tuple(std::get<0>(particle), std::get<1>(particle),
+                                                   std::get<0>(particleToExclude),
+                                                   std::get<1>(particleToExclude)));
+}
+
+void Molecule::addExclusion(const std::string& particleName, const std::string& particleNameToExclude)
+{
+    addExclusion(std::make_tuple(particleName, name_), std::make_tuple(particleNameToExclude, name_));
+}
+
+const ParticleType& Molecule::at(const std::string& particleTypeName) const
+{
+    return particleTypes_.at(particleTypeName);
+}
+
+ParticleName Molecule::particleName(int i) const
+{
+    return ParticleName(particles_[i].particleName_);
+}
+
+ResidueName Molecule::residueName(int i) const
+{
+    return ResidueName(particles_[i].residueName_);
+}
+
+std::vector<std::tuple<int, int>> Molecule::getExclusions() const
+{
+    // tuples of (particleName, residueName, index)
+    std::vector<std::tuple<std::string, std::string, int>> indexKey;
+    indexKey.reserve(numParticlesInMolecule());
+
+    for (int i = 0; i < numParticlesInMolecule(); ++i)
+    {
+        indexKey.emplace_back(particles_[i].particleName_, particles_[i].residueName_, i);
+    }
+
+    std::sort(std::begin(indexKey), std::end(indexKey));
+
+    std::vector<std::tuple<int, int>> ret = exclusions_;
+    ret.reserve(exclusions_.size() + exclusionsByName_.size());
+
+    // normal operator<, except ignore third element
+    auto sortKey = [](const auto& tup1, const auto& tup2) {
+        if (std::get<0>(tup1) < std::get<0>(tup2))
+        {
+            return true;
+        }
+        else
+        {
+            return std::get<1>(tup1) < std::get<1>(tup2);
+        }
+    };
+
+    // convert exclusions given by names to indices and append
+    for (auto& tup : exclusionsByName_)
+    {
+        const std::string& particleName1 = std::get<0>(tup);
+        const std::string& residueName1  = std::get<1>(tup);
+        const std::string& particleName2 = std::get<2>(tup);
+        const std::string& residueName2  = std::get<3>(tup);
+
+        // look up first index (binary search)
+        auto it1 = std::lower_bound(std::begin(indexKey), std::end(indexKey),
+                                    std::make_tuple(particleName1, residueName2, 0), sortKey);
+
+        // make sure we have the (particleName,residueName) combo
+        if (it1 == std::end(indexKey) or std::get<0>(*it1) != particleName1 or std::get<1>(*it1) != residueName1)
+        {
+            throw std::runtime_error(
+                    (std::string("Particle ") += particleName1 + std::string(" in residue ") +=
+                     residueName1 + std::string(" not found in list of particles\n")));
+        }
+
+        int firstIndex = std::get<2>(*it1);
+
+        // look up second index (binary search)
+        auto it2 = std::lower_bound(std::begin(indexKey), std::end(indexKey),
+                                    std::make_tuple(particleName2, residueName2, 0), sortKey);
+
+        // make sure we have the (particleName,residueName) combo
+        if (it2 == std::end(indexKey) or std::get<0>(*it2) != particleName2 or std::get<1>(*it2) != residueName2)
+        {
+            throw std::runtime_error(
+                    (std::string("Particle ") += particleName2 + std::string(" in residue ") +=
+                     residueName2 + std::string(" not found in list of particles\n")));
+        }
+
+        int secondIndex = std::get<2>(*it2);
+
+        ret.emplace_back(firstIndex, secondIndex);
+        ret.emplace_back(secondIndex, firstIndex);
+    }
+
+    std::sort(std::begin(ret), std::end(ret));
+
+    auto uniqueEnd = std::unique(std::begin(ret), std::end(ret));
+    if (uniqueEnd != std::end(ret))
+    {
+        printf("[nblib] Warning: exclusionList for molecule %s contained duplicates",
+               name_.value().c_str());
+    }
+
+    ret.erase(uniqueEnd, std::end(ret));
+    return ret;
+}
+
+std::unordered_map<std::string, ParticleType> Molecule::particleTypesMap() const
+{
+    return particleTypes_;
+}
+
+std::vector<ParticleData> Molecule::particleData() const
+{
+    return particles_;
+}
+
+} // namespace nblib
diff --git a/api/nblib/particletype.cpp b/api/nblib/particletype.cpp
new file mode 100644 (file)
index 0000000..c14db11
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2020, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * Implements nblib ParticleType
+ *
+ * \author Victor Holanda <victor.holanda@cscs.ch>
+ * \author Joe Jordan <ejjordan@kth.se>
+ * \author Prashanth Kanduri <kanduri@cscs.ch>
+ * \author Sebastian Keller <keller@cscs.ch>
+ * \author Artem Zhmurov <zhmurov@gmail.com>
+ */
+#include "nblib/particletype.h"
+
+namespace nblib
+{
+
+ParticleType::ParticleType(ParticleTypeName name, Mass mass) : name_(std::move(name)), mass_(mass)
+{
+}
+
+ParticleTypeName ParticleType::name() const
+{
+    return name_;
+}
+
+Mass ParticleType::mass() const
+{
+    return mass_;
+}
+
+bool operator==(const ParticleType& a, const ParticleType& b)
+{
+    return a.name() == b.name() && a.mass() == b.mass();
+}
+
+} // namespace nblib
index 7646a52da1798f76c770521c5c89f339b1aece38..f7b17dd26dcf68d17af32392310d729447a3d62a 100644 (file)
 # in multiple test executables across the repository.
 gmx_add_unit_test_library(nblib_test_infrastructure
         testhelpers.cpp
-        )
+        testsystems.cpp
+    )
 target_include_directories(nblib_test_infrastructure PRIVATE ${PROJECT_SOURCE_DIR}/api)
 target_include_directories(nblib_test_infrastructure SYSTEM PRIVATE ${PROJECT_SOURCE_DIR}/src/external)
 
-
 set(testname "NbLibSetupTests")
 set(exename "nblib-setup-test")
 
@@ -54,7 +54,11 @@ gmx_add_gtest_executable(
     ${exename}
     CPP_SOURCE_FILES
     # files with code for tests
-    box.cpp
+        box.cpp
+        interactions.cpp
+        particletype.cpp
+        molecules.cpp
+        topology.cpp
     )
 target_link_libraries(${exename} PRIVATE nblib_test_infrastructure nblib)
 target_include_directories(${exename} PRIVATE ${PROJECT_SOURCE_DIR}/api)
index 4527033c6f0ee3a5c9fda0208cf8e838f8e6891f..45900e7af84dfd2cb48ef0ff7e283c762178fbae 100644 (file)
@@ -85,8 +85,12 @@ TEST(NBlibTest, CubicBoxWorks)
     Box               test   = Box(length);
 
     for (int i = 0; i < dimSize; ++i)
+    {
         for (int j = 0; j < dimSize; ++j)
+        {
             EXPECT_REAL_EQ_TOL(ref[i][j], test.legacyMatrix()[i][j], defaultRealTolerance());
+        }
+    }
 }
 
 } // namespace nblib
diff --git a/api/nblib/tests/interactions.cpp b/api/nblib/tests/interactions.cpp
new file mode 100644 (file)
index 0000000..bb93f5d
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2020, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * This implements molecule setup tests
+ *
+ * \author Victor Holanda <victor.holanda@cscs.ch>
+ * \author Joe Jordan <ejjordan@kth.se>
+ * \author Prashanth Kanduri <kanduri@cscs.ch>
+ * \author Sebastian Keller <keller@cscs.ch>
+ * \author Artem Zhmurov <zhmurov@gmail.com>
+ */
+#include "nblib/interactions.h"
+#include "nblib/exception.h"
+#include "nblib/particletype.h"
+#include "nblib/tests/testsystems.h"
+
+#include "testutils/testasserts.h"
+
+namespace nblib
+{
+namespace test
+{
+namespace
+{
+
+TEST(NBlibTest, NonBondedForceParamsCorrect)
+{
+    ParticleType atom1(ParticleTypeName("a1"), Mass(1));
+    ParticleType atom2(ParticleTypeName("a2"), Mass(1));
+    ParticleType atom3(ParticleTypeName("a3"), Mass(1));
+
+    ParticleTypesInteractions interactions;
+
+    auto c6_1  = C6(1.6);
+    auto c12_1 = C12(1.12);
+    C6   c6_2{ 2.6 };
+    C12  c12_2{ 2.12 };
+    C6   c6_3  = C6(3.6);
+    C12  c12_3 = C12(3.12);
+
+    auto c6comb  = C6{ 40 };
+    auto c12comb = C12{ 41 };
+
+    interactions.add(atom1.name(), c6_1, c12_1);
+    interactions.add(atom2.name(), c6_2, c12_2);
+    interactions.add(atom3.name(), c6_3, c12_3);
+    interactions.add(atom2.name(), atom3.name(), c6comb, c12comb);
+
+    auto nbfp = interactions.generateTable();
+
+    //! self interaction for c6
+    EXPECT_REAL_EQ_TOL(c6_1, nbfp.getC6(atom1.name(), atom1.name()), gmx::test::defaultRealTolerance());
+    //! + symmetric pair
+    EXPECT_REAL_EQ_TOL(c12_1, nbfp.getC12(atom1.name(), atom1.name()), gmx::test::defaultRealTolerance());
+
+    //! geometric comb rule for c6
+    EXPECT_REAL_EQ_TOL(std::sqrt(c6_1 * c6_2), nbfp.getC6(atom1.name(), atom2.name()),
+                       gmx::test::defaultRealTolerance());
+    //! + symmetric pair
+    EXPECT_REAL_EQ_TOL(std::sqrt(c6_1 * c6_2), nbfp.getC6(atom2.name(), atom1.name()),
+                       gmx::test::defaultRealTolerance());
+
+    //! geometric comb rule for c12
+    EXPECT_REAL_EQ_TOL(std::sqrt(c12_1 * c12_2), nbfp.getC12(atom1.name(), atom2.name()),
+                       gmx::test::defaultRealTolerance());
+
+    //! + symmetric par
+    EXPECT_REAL_EQ_TOL(std::sqrt(c12_1 * c12_2), nbfp.getC12(atom2.name(), atom1.name()),
+                       gmx::test::defaultRealTolerance());
+
+    //! explicit pairwise interaction c6
+    EXPECT_REAL_EQ_TOL(c6comb, nbfp.getC6(atom2.name(), atom3.name()), gmx::test::defaultRealTolerance());
+    EXPECT_REAL_EQ_TOL(c6comb, nbfp.getC6(atom3.name(), atom2.name()), gmx::test::defaultRealTolerance());
+
+    //! explicit pairwise interaction c12
+    EXPECT_REAL_EQ_TOL(c12comb, nbfp.getC12(atom2.name(), atom3.name()),
+                       gmx::test::defaultRealTolerance());
+    EXPECT_REAL_EQ_TOL(c12comb, nbfp.getC12(atom3.name(), atom2.name()),
+                       gmx::test::defaultRealTolerance());
+
+    ParticleType atom4(ParticleTypeName("a4"), Mass(1));
+    interactions.add(atom3.name(), atom4.name(), C6(1), C12(2));
+    //! need to have self-interaction for all particles
+    EXPECT_THROW(auto interactionsTable = interactions.generateTable(), InputException);
+}
+
+TEST(NBlibTest, CanMergeInteractions)
+{
+    ParticleType atom1(ParticleTypeName("a1"), Mass(1));
+    ParticleType atom2(ParticleTypeName("a2"), Mass(1));
+    ParticleType atom3(ParticleTypeName("a3"), Mass(1));
+
+    ParticleTypesInteractions interactions;
+
+    auto c6_1  = C6(1.6);
+    auto c12_1 = C12(1.12);
+    C6   c6_2{ 2.6 };
+    C12  c12_2{ 2.12 };
+    C6   c6_3  = C6(3.6);
+    C12  c12_3 = C12(3.12);
+
+    auto c6comb  = C6{ 40 };
+    auto c12comb = C12{ 41 };
+
+    interactions.add(atom1.name(), c6_1, c12_1);
+    interactions.add(atom2.name(), c6_2, c12_2);
+    interactions.add(atom3.name(), c6_3, c12_3);
+    interactions.add(atom2.name(), atom3.name(), c6comb, c12comb);
+
+    ParticleType atom4(ParticleTypeName("a4"), Mass(1));
+    ParticleType atom5(ParticleTypeName("a5"), Mass(1));
+    auto         c6_4  = C6{ 4.6 };
+    auto         c12_4 = C12{ 4.12 };
+
+    C6  c6_5{ 5.6 };
+    C12 c12_5{ 5.12 };
+
+    C6  c6_override  = C6(45.6);
+    C12 c12_override = C12(45.12);
+
+    ParticleTypesInteractions otherInteractions;
+    otherInteractions.add(atom4.name(), c6_4, c12_4);
+    otherInteractions.add(atom5.name(), c6_5, c12_5);
+    otherInteractions.add(atom4.name(), atom5.name(), c6_override, c12_override);
+
+    interactions.merge(otherInteractions);
+
+    auto nbfp = interactions.generateTable();
+
+    EXPECT_REAL_EQ_TOL(std::sqrt(c6_3 * c6_4), nbfp.getC6(atom3.name(), atom4.name()),
+                       gmx::test::defaultRealTolerance());
+    EXPECT_REAL_EQ_TOL(std::sqrt(c12_3 * c12_4), nbfp.getC12(atom3.name(), atom4.name()),
+                       gmx::test::defaultRealTolerance());
+    EXPECT_REAL_EQ_TOL(c6_override, nbfp.getC6(atom4.name(), atom5.name()),
+                       gmx::test::defaultRealTolerance());
+    EXPECT_REAL_EQ_TOL(c12_override, nbfp.getC12(atom4.name(), atom5.name()),
+                       gmx::test::defaultRealTolerance());
+}
+
+} // namespace
+} // namespace test
+} // namespace nblib
diff --git a/api/nblib/tests/molecules.cpp b/api/nblib/tests/molecules.cpp
new file mode 100644 (file)
index 0000000..52e5248
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2020, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * This implements molecule setup tests
+ *
+ * \author Victor Holanda <victor.holanda@cscs.ch>
+ * \author Joe Jordan <ejjordan@kth.se>
+ * \author Prashanth Kanduri <kanduri@cscs.ch>
+ * \author Sebastian Keller <keller@cscs.ch>
+ * \author Artem Zhmurov <zhmurov@gmail.com>
+ */
+#include "nblib/molecules.h"
+#include "nblib/exception.h"
+#include "nblib/particletype.h"
+#include "nblib/tests/testsystems.h"
+
+#include "testutils/testasserts.h"
+
+namespace nblib
+{
+namespace test
+{
+namespace
+{
+
+TEST(NBlibTest, CanConstructMoleculeWithoutChargeOrResidueName)
+{
+    ArAtom       arAtom;
+    ParticleType Ar(arAtom.particleTypeName, arAtom.mass);
+    Molecule     argon(arAtom.moleculeName);
+    EXPECT_NO_THROW(argon.addParticle(arAtom.particleName, Ar));
+}
+
+TEST(NBlibTest, CanConstructMoleculeWithChargeWithoutResidueName)
+{
+    ArAtom       arAtom;
+    ParticleType Ar(arAtom.particleTypeName, arAtom.mass);
+    Molecule     argon(arAtom.moleculeName);
+    EXPECT_NO_THROW(argon.addParticle(arAtom.particleName, Charge(0), Ar));
+}
+
+TEST(NBlibTest, CanConstructMoleculeWithoutChargeWithResidueName)
+{
+    ArAtom       arAtom;
+    ParticleType Ar(arAtom.particleTypeName, arAtom.mass);
+    Molecule     argon(arAtom.moleculeName);
+    EXPECT_NO_THROW(argon.addParticle(arAtom.particleName, ResidueName("ar2"), Ar));
+}
+
+TEST(NBlibTest, CanConstructMoleculeWithChargeWithResidueName)
+{
+    ArAtom       arAtom;
+    ParticleType Ar(arAtom.particleTypeName, arAtom.mass);
+    Molecule     argon(arAtom.moleculeName);
+    EXPECT_NO_THROW(argon.addParticle(arAtom.particleName, ResidueName("ar2"), Charge(0), Ar));
+}
+
+TEST(NBlibTest, CanGetNumParticlesInMolecule)
+{
+    WaterMoleculeBuilder waterMolecule;
+    Molecule             water        = waterMolecule.waterMolecule();
+    auto                 numParticles = water.numParticlesInMolecule();
+
+    EXPECT_EQ(3, numParticles);
+}
+
+TEST(NBlibTest, CanConstructExclusionListFromNames)
+{
+    WaterMoleculeBuilder waterMolecule;
+    Molecule             water = waterMolecule.waterMolecule();
+
+    std::vector<std::tuple<int, int>> exclusions = water.getExclusions();
+
+    std::vector<std::tuple<int, int>> reference{ { 0, 0 }, { 0, 1 }, { 0, 2 }, { 1, 0 }, { 1, 1 },
+                                                 { 1, 2 }, { 2, 0 }, { 2, 1 }, { 2, 2 } };
+
+    ASSERT_EQ(exclusions.size(), 9);
+    for (std::size_t i = 0; i < exclusions.size(); ++i)
+    {
+        EXPECT_EQ(exclusions[i], reference[i]);
+    }
+}
+
+TEST(NBlibTest, CanConstructExclusionListFromIndices)
+{
+    WaterMoleculeBuilder waterMolecule;
+    Molecule             water = waterMolecule.waterMoleculeWithoutExclusions();
+
+    //! Add the exclusions
+    water.addExclusion(1, 0);
+    water.addExclusion(2, 0);
+    water.addExclusion(1, 2);
+
+    std::vector<std::tuple<int, int>> exclusions = water.getExclusions();
+
+    std::vector<std::tuple<int, int>> reference{ { 0, 0 }, { 0, 1 }, { 0, 2 }, { 1, 0 }, { 1, 1 },
+                                                 { 1, 2 }, { 2, 0 }, { 2, 1 }, { 2, 2 } };
+
+    ASSERT_EQ(exclusions.size(), 9);
+    for (std::size_t i = 0; i < exclusions.size(); ++i)
+    {
+        EXPECT_EQ(exclusions[i], reference[i]);
+    }
+}
+
+TEST(NBlibTest, CanConstructExclusionListFromNamesAndIndicesMixed)
+{
+    WaterMoleculeBuilder waterMolecule;
+    Molecule             water = waterMolecule.waterMoleculeWithoutExclusions();
+
+    //! Add the exclusions
+    water.addExclusion("H1", "Oxygen");
+    water.addExclusion("H2", "Oxygen");
+    water.addExclusion(1, 2);
+
+    std::vector<std::tuple<int, int>> exclusions = water.getExclusions();
+
+    std::vector<std::tuple<int, int>> reference{ { 0, 0 }, { 0, 1 }, { 0, 2 }, { 1, 0 }, { 1, 1 },
+                                                 { 1, 2 }, { 2, 0 }, { 2, 1 }, { 2, 2 } };
+
+    ASSERT_EQ(exclusions.size(), 9);
+    for (std::size_t i = 0; i < exclusions.size(); ++i)
+    {
+        EXPECT_EQ(exclusions[i], reference[i]);
+    }
+}
+
+TEST(NBlibTest, AtWorks)
+{
+    WaterMoleculeBuilder waterMolecule;
+    Molecule             water = waterMolecule.waterMolecule();
+    EXPECT_NO_THROW(water.at("Ow"));
+    EXPECT_NO_THROW(water.at("H"));
+}
+
+TEST(NBlibTest, AtThrows)
+{
+    WaterMoleculeBuilder waterMolecule;
+    Molecule             water = waterMolecule.waterMolecule();
+    EXPECT_THROW_GMX(water.at("Hw"), std::out_of_range);
+}
+
+TEST(NBlibTest, MoleculeThrowsSameParticleTypeNameDifferentMass)
+{
+    //! User error: Two different ParticleTypes with the same name
+    ParticleType atom1(ParticleTypeName("Atom"), Mass(1));
+    ParticleType atom2(ParticleTypeName("Atom"), Mass(2));
+
+    Molecule molecule(MoleculeName("UraniumDimer"));
+    EXPECT_NO_THROW(molecule.addParticle(ParticleName("U1"), atom1));
+    EXPECT_THROW(molecule.addParticle(ParticleName("U2"), atom2), InputException);
+}
+
+TEST(NBlibTest, MoleculeDontThrowsSameParticleTypeNameDifferentMass)
+{
+    //! User error: Two different ParticleTypes with the same name
+    ParticleType atom1(ParticleTypeName("Atom"), Mass(1));
+    ParticleType atom2(ParticleTypeName("Atom"), Mass(1));
+
+    Molecule molecule(MoleculeName("UraniumDimer"));
+    EXPECT_NO_THROW(molecule.addParticle(ParticleName("U1"), atom1));
+    EXPECT_NO_THROW(molecule.addParticle(ParticleName("U2"), atom2));
+}
+
+TEST(NBlibTest, MoleculeNoThrowsSameParticleTypeName)
+{
+    //! User error: Two different ParticleTypes with the same name
+    ParticleType atom1(ParticleTypeName("Atom"), Mass(1));
+    ParticleType atom2(ParticleTypeName("Atom"), Mass(1));
+
+    Molecule molecule(MoleculeName("UraniumDimer"));
+    EXPECT_NO_THROW(molecule.addParticle(ParticleName("U1"), atom1));
+    EXPECT_NO_THROW(molecule.addParticle(ParticleName("U2"), atom2));
+}
+
+} // namespace
+} // namespace test
+} // namespace nblib
diff --git a/api/nblib/tests/particletype.cpp b/api/nblib/tests/particletype.cpp
new file mode 100644 (file)
index 0000000..f9eaa39
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2020, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * This implements basic nblib AtomType tests
+ *
+ * \author Victor Holanda <victor.holanda@cscs.ch>
+ * \author Joe Jordan <ejjordan@kth.se>
+ * \author Prashanth Kanduri <kanduri@cscs.ch>
+ * \author Sebastian Keller <keller@cscs.ch>
+ * \author Artem Zhmurov <zhmurov@gmail.com>
+ *
+ */
+#include <cmath>
+
+#include "nblib/particletype.h"
+#include "nblib/tests/testsystems.h"
+
+#include "testutils/testasserts.h"
+
+namespace nblib
+{
+
+TEST(NBlibTest, ParticleTypeNameCanBeConstructed)
+{
+    ArAtom       arAtom;
+    ParticleType argonAtom(arAtom.particleTypeName, arAtom.mass);
+    EXPECT_EQ(ParticleTypeName(argonAtom.name()), arAtom.particleTypeName);
+}
+
+TEST(NBlibTest, ParticleTypeMassCanBeConstructed)
+{
+    ArAtom       arAtom;
+    ParticleType argonAtom(arAtom.particleTypeName, arAtom.mass);
+    EXPECT_EQ(argonAtom.mass(), arAtom.mass);
+}
+
+} // namespace nblib
diff --git a/api/nblib/tests/testsystems.cpp b/api/nblib/tests/testsystems.cpp
new file mode 100644 (file)
index 0000000..02abe3e
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2020, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * This implements nblib test systems
+ *
+ * \author Victor Holanda <victor.holanda@cscs.ch>
+ * \author Joe Jordan <ejjordan@kth.se>
+ * \author Prashanth Kanduri <kanduri@cscs.ch>
+ * \author Sebastian Keller <keller@cscs.ch>
+ * \author Artem Zhmurov <zhmurov@gmail.com>
+ */
+#include "nblib/tests/testsystems.h"
+#include "nblib/exception.h"
+
+namespace nblib
+{
+
+//! User class to hold ParticleTypes
+//! Note: this is not part of NBLIB, users should write their own
+class ParticleLibrary
+{
+public:
+    ParticleLibrary()
+    {
+        ParticleType Ow(ParticleTypeName("Ow"), Mass(15.99940));
+        ParticleType H(ParticleTypeName("H"), Mass(1.008));
+        ParticleType OMet(ParticleTypeName("OMet"), Mass(15.999));
+        ParticleType CMet(ParticleTypeName("CMet"), Mass(15.035));
+        ParticleType Ar(ParticleTypeName("Ar"), Mass(39.94800));
+
+        particles_.insert(std::make_pair(Ow.name(), Ow));
+        particles_.insert(std::make_pair(H.name(), H));
+        particles_.insert(std::make_pair(OMet.name(), OMet));
+        particles_.insert(std::make_pair(CMet.name(), CMet));
+        particles_.insert(std::make_pair(Ar.name(), Ar));
+
+        c6_[Ow.name()]   = 0.0026173456;
+        c6_[H.name()]    = 0;
+        c6_[OMet.name()] = 0.0022619536;
+        c6_[CMet.name()] = 0.0088755241;
+        c6_[Ar.name()]   = 0.0062647225;
+
+        c12_[Ow.name()]   = 2.634129e-06;
+        c12_[H.name()]    = 0;
+        c12_[OMet.name()] = 1.505529e-06;
+        c12_[CMet.name()] = 2.0852922e-05;
+        c12_[Ar.name()]   = 9.847044e-06;
+    }
+
+    //! Get particle type using the string identifier
+    [[nodiscard]] ParticleType type(const std::string& particleTypeName) const
+    {
+        return particles_.at(ParticleTypeName(particleTypeName));
+    }
+
+    //! Get C6 parameter of a given particle
+    [[nodiscard]] C6 c6(const ParticleName& particleName) const
+    {
+        return c6_.at(ParticleTypeName(particleName.value()));
+    }
+
+    //! Get C12 parameter of a given particle
+    [[nodiscard]] C12 c12(const ParticleName& particleName) const
+    {
+        return c12_.at(ParticleTypeName(particleName.value()));
+    }
+
+private:
+    std::map<ParticleTypeName, ParticleType> particles_;
+    std::map<ParticleTypeName, C6>           c6_;
+    std::map<ParticleTypeName, C12>          c12_;
+};
+
+std::unordered_map<std::string, Charge> Charges{ { "Ow", Charge(-0.82) },
+                                                 { "Hw", Charge(+0.41) },
+                                                 { "OMet", Charge(-0.574) },
+                                                 { "CMet", Charge(+0.176) },
+                                                 { "HMet", Charge(+0.398) } };
+
+WaterMoleculeBuilder::WaterMoleculeBuilder() : water_(MoleculeName("SOL"))
+{
+    ParticleLibrary plib;
+
+    //! Add the particles
+    water_.addParticle(ParticleName("Oxygen"), Charges.at("Ow"), plib.type("Ow"));
+    water_.addParticle(ParticleName("H1"), Charges.at("Hw"), plib.type("H"));
+    water_.addParticle(ParticleName("H2"), Charges.at("Hw"), plib.type("H"));
+}
+
+Molecule WaterMoleculeBuilder::waterMolecule()
+{
+    addExclusionsFromNames();
+    return water_;
+}
+
+Molecule WaterMoleculeBuilder::waterMoleculeWithoutExclusions()
+{
+    return water_;
+}
+
+void WaterMoleculeBuilder::addExclusionsFromNames()
+{
+    water_.addExclusion("H1", "Oxygen");
+    water_.addExclusion("H2", "Oxygen");
+    water_.addExclusion("H1", "H2");
+}
+
+MethanolMoleculeBuilder::MethanolMoleculeBuilder() : methanol_(MoleculeName("MeOH"))
+{
+    ParticleLibrary library;
+
+    //! Add the particles
+    methanol_.addParticle(ParticleName("Me1"), Charges.at("CMet"), library.type("CMet"));
+    methanol_.addParticle(ParticleName("O2"), Charges.at("OMet"), library.type("OMet"));
+    methanol_.addParticle(ParticleName("H3"), Charges.at("HMet"), library.type("H"));
+
+    // Add the exclusions
+    methanol_.addExclusion("Me1", "O2");
+    methanol_.addExclusion("Me1", "H3");
+    methanol_.addExclusion("H3", "O2");
+}
+
+Molecule MethanolMoleculeBuilder::methanolMolecule()
+{
+    return methanol_;
+}
+
+
+Topology WaterTopologyBuilder::buildTopology(int numMolecules)
+{
+    ParticleLibrary library;
+
+    ParticleTypesInteractions interactions;
+    std::vector<std::string>  typeNames = { "Ow", "H" };
+    for (const auto& name : typeNames)
+    {
+        interactions.add(ParticleTypeName(name), library.c6(ParticleName(name)),
+                         library.c12(ParticleName(name)));
+    }
+
+    // Add some molecules to the topology
+    TopologyBuilder topologyBuilder;
+    topologyBuilder.addMolecule(water(), numMolecules);
+
+    // Add non-bonded interaction information
+    topologyBuilder.addParticleTypesInteractions(interactions);
+
+    Topology topology = topologyBuilder.buildTopology();
+    return topology;
+}
+
+Molecule WaterTopologyBuilder::water()
+{
+    return waterMolecule_.waterMolecule();
+}
+
+Topology SpcMethanolTopologyBuilder::buildTopology(int numWater, int numMethanol)
+{
+    ParticleLibrary library;
+
+    ParticleTypesInteractions interactions;
+    std::vector<std::string>  typeNames = { "Ow", "H", "OMet", "CMet" };
+    for (const auto& name : typeNames)
+    {
+        interactions.add(ParticleTypeName(name), library.c6(ParticleName(name)),
+                         library.c12(ParticleName(name)));
+    }
+
+    // Add some molecules to the topology
+    TopologyBuilder topologyBuilder;
+    topologyBuilder.addMolecule(methanol(), numMethanol);
+    topologyBuilder.addMolecule(water(), numWater);
+
+    // Add non-bonded interaction information
+    topologyBuilder.addParticleTypesInteractions(interactions);
+
+    Topology topology = topologyBuilder.buildTopology();
+    return topology;
+}
+
+Molecule SpcMethanolTopologyBuilder::methanol()
+{
+    return methanolMolecule_.methanolMolecule();
+}
+
+Molecule SpcMethanolTopologyBuilder::water()
+{
+    return waterMolecule_.waterMolecule();
+}
+
+ArgonTopologyBuilder::ArgonTopologyBuilder(const int& numParticles)
+{
+    ParticleLibrary library;
+
+    ParticleTypesInteractions nbinteractions;
+    nbinteractions.add(ParticleTypeName("Ar"), library.c6(ParticleName("Ar")),
+                       library.c12(ParticleName("Ar")));
+
+    Molecule argonMolecule(MoleculeName("AR"));
+    argonMolecule.addParticle(ParticleName("AR"), library.type("Ar"));
+
+    topologyBuilder_.addMolecule(argonMolecule, numParticles);
+    topologyBuilder_.addParticleTypesInteractions((nbinteractions));
+}
+
+Topology ArgonTopologyBuilder::argonTopology()
+{
+    return topologyBuilder_.buildTopology();
+}
+
+
+} // namespace nblib
diff --git a/api/nblib/tests/testsystems.h b/api/nblib/tests/testsystems.h
new file mode 100644 (file)
index 0000000..e2f7cfe
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2020, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * This implements basic nblib test systems
+ *
+ * \author Victor Holanda <victor.holanda@cscs.ch>
+ * \author Joe Jordan <ejjordan@kth.se>
+ * \author Prashanth Kanduri <kanduri@cscs.ch>
+ * \author Sebastian Keller <keller@cscs.ch>
+ * \author Artem Zhmurov <zhmurov@gmail.com>
+ */
+#ifndef NBLIB_TESTSYSTEMS_H
+#define NBLIB_TESTSYSTEMS_H
+
+#include <cmath>
+
+#include "nblib/box.h"
+#include "nblib/molecules.h"
+#include "nblib/particletype.h"
+#include "nblib/simulationstate.h"
+#include "nblib/topology.h"
+
+namespace nblib
+{
+
+//! \internal \brief Parameters from gromos43A1
+struct ArAtom
+{
+    //! Argon Particle Name
+    ParticleName particleName = ParticleName("Ar");
+    //! Argon particle type name
+    ParticleTypeName particleTypeName = ParticleTypeName("Ar");
+    //! Argon molecule name
+    MoleculeName moleculeName = MoleculeName("Ar");
+    //! Argon Particle Mass
+    Mass mass = Mass(39.94800);
+    //! Argon C6 parameter
+    C6 c6{ 0.0062647225 };
+    //! Argon C12 parameter
+    C12 c12{ 9.847044e-06 };
+};
+
+//! Lookup table for charges needed for building topologies
+extern std::unordered_map<std::string, Charge> Charges;
+
+//! \internal \brief Make an SPC water molecule with parameters from gromos43A1
+class WaterMoleculeBuilder
+{
+public:
+    // There is no default ctor for a Molecule so it must be initialized
+    WaterMoleculeBuilder();
+
+    //! Return the initialized water Molecule, with exclusions
+    Molecule waterMolecule();
+
+    //! Return the initialized water Molecule, without exclusions
+    Molecule waterMoleculeWithoutExclusions();
+
+private:
+    //! The molecule
+    Molecule water_;
+
+    //! Add the exclusions from particle names. Private to prevent multiple calls
+    void addExclusionsFromNames();
+};
+
+//! \internal \brief Make a methanol molecule with parameters from gromos43A1
+class MethanolMoleculeBuilder
+{
+public:
+    // There is no default ctor for a Molecule so it must be initialized
+    MethanolMoleculeBuilder();
+
+    //! Return the initialized water Molecule, with exclusions
+    Molecule methanolMolecule();
+
+private:
+    //! The molecule
+    Molecule methanol_;
+};
+
+//! \internal \brief Build topology of water molecules of a specified number
+class WaterTopologyBuilder
+{
+public:
+    //! Return a topology with specified SPC water molecules
+    Topology buildTopology(int numMolecules);
+
+    //! Return the actual water Molecule used in the topology
+    Molecule water();
+
+private:
+    WaterMoleculeBuilder waterMolecule_;
+};
+
+//! \internal \brief Build topology of methanol+water molecules from specified numbers
+class SpcMethanolTopologyBuilder
+{
+public:
+    //! Return a topology with specified methanol molecules
+    Topology buildTopology(int numWater, int numMethanol);
+
+    //! Return the actual methanol Molecule used in the topology
+    Molecule methanol();
+
+    //! Return the actual water Molecule used in the topology
+    Molecule water();
+
+private:
+    MethanolMoleculeBuilder methanolMolecule_;
+    WaterMoleculeBuilder    waterMolecule_;
+};
+
+//! \internal \brief Build topology of argon molecules of a specified number
+class ArgonTopologyBuilder
+{
+public:
+    //! Build a topology with specified argon molecules
+    ArgonTopologyBuilder(const int& numParticles);
+
+    //! Get the topology with specified argon molecules
+    Topology argonTopology();
+
+private:
+    TopologyBuilder topologyBuilder_;
+};
+
+} // namespace nblib
+#endif // NBLIB_TESTSYSTEMS_H
diff --git a/api/nblib/tests/topology.cpp b/api/nblib/tests/topology.cpp
new file mode 100644 (file)
index 0000000..63c326a
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2020, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * This implements topology setup tests
+ *
+ * \author Victor Holanda <victor.holanda@cscs.ch>
+ * \author Joe Jordan <ejjordan@kth.se>
+ * \author Prashanth Kanduri <kanduri@cscs.ch>
+ * \author Sebastian Keller <keller@cscs.ch>
+ */
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "gromacs/topology/exclusionblocks.h"
+#include "nblib/exception.h"
+#include "nblib/particletype.h"
+#include "nblib/tests/testsystems.h"
+#include "nblib/topology.h"
+
+namespace nblib
+{
+namespace test
+{
+namespace
+{
+
+using ::testing::Eq;
+using ::testing::Pointwise;
+
+//! Compares all element between two lists of lists
+//! Todo: unify this with the identical function in nbkernelsystem test make this a method
+//!       of ListOfLists<>
+template<typename T>
+void compareLists(const gmx::ListOfLists<T>& list, const std::vector<std::vector<T>>& v)
+{
+    ASSERT_EQ(list.size(), v.size());
+    for (std::size_t i = 0; i < list.size(); i++)
+    {
+        ASSERT_EQ(list[i].size(), v[i].size());
+        EXPECT_THAT(list[i], Pointwise(Eq(), v[i]));
+    }
+}
+
+// This is defined in src/gromacs/mdtypes/forcerec.h but there is also a
+// legacy C6 macro defined there that conflicts with the nblib C6 type.
+// Todo: Once that C6 has been refactored into a regular function, this
+//       file can just include forcerec.h
+//! Macro to marks particles to have Van der Waals interactions
+#define SET_CGINFO_HAS_VDW(cgi) (cgi) = ((cgi) | (1 << 23))
+
+TEST(NBlibTest, TopologyHasNumParticles)
+{
+    WaterTopologyBuilder waters;
+    Topology             watersTopology = waters.buildTopology(2);
+    const int            test           = watersTopology.numParticles();
+    const int            ref            = 6;
+    EXPECT_EQ(ref, test);
+}
+
+TEST(NBlibTest, TopologyHasCharges)
+{
+    WaterTopologyBuilder     waters;
+    Topology                 watersTopology = waters.buildTopology(2);
+    const std::vector<real>& test           = watersTopology.getCharges();
+    const std::vector<real>& ref = { Charges.at("Ow"), Charges.at("Hw"), Charges.at("Hw"),
+                                     Charges.at("Ow"), Charges.at("Hw"), Charges.at("Hw") };
+    EXPECT_EQ(ref, test);
+}
+
+TEST(NBlibTest, TopologyHasMasses)
+{
+    WaterTopologyBuilder waters;
+    Topology             watersTopology = waters.buildTopology(2);
+
+    const Mass              refOwMass = waters.water().at("Ow").mass();
+    const Mass              refHwMass = waters.water().at("H").mass();
+    const std::vector<Mass> ref = { refOwMass, refHwMass, refHwMass, refOwMass, refHwMass, refHwMass };
+    const std::vector<Mass> test = expandQuantity(watersTopology, &ParticleType::mass);
+    EXPECT_EQ(ref, test);
+}
+
+TEST(NBlibTest, TopologyHasParticleTypes)
+{
+    WaterTopologyBuilder             waters;
+    Topology                         watersTopology = waters.buildTopology(2);
+    const std::vector<ParticleType>& test           = watersTopology.getParticleTypes();
+    const ParticleType               refOw          = waters.water().at("Ow");
+    const ParticleType               refHw          = waters.water().at("H");
+    const std::vector<ParticleType>& ref            = { refOw, refHw };
+    const std::vector<ParticleType>& ref2           = { refHw, refOw };
+    EXPECT_TRUE(ref == test || ref2 == test);
+}
+
+TEST(NBlibTest, TopologyHasParticleTypeIds)
+{
+    WaterTopologyBuilder waters;
+    Topology             watersTopology = waters.buildTopology(2);
+
+    const std::vector<int>&          testIds   = watersTopology.getParticleTypeIdOfAllParticles();
+    const std::vector<ParticleType>& testTypes = watersTopology.getParticleTypes();
+
+    std::vector<ParticleType> testTypesExpanded;
+    testTypesExpanded.reserve(testTypes.size());
+    for (int i : testIds)
+    {
+        testTypesExpanded.push_back(testTypes[i]);
+    }
+
+    const ParticleType              refOw = waters.water().at("Ow");
+    const ParticleType              refHw = waters.water().at("H");
+    const std::vector<ParticleType> ref   = { refOw, refHw, refHw, refOw, refHw, refHw };
+
+    EXPECT_TRUE(ref == testTypesExpanded);
+}
+
+TEST(NBlibTest, TopologyThrowsIdenticalParticleType)
+{
+    //! User error: Two different ParticleTypes with the same name
+    ParticleType U235(ParticleTypeName("Uranium"), Mass(235));
+    ParticleType U238(ParticleTypeName("Uranium"), Mass(238));
+
+    Molecule ud235(MoleculeName("UraniumDimer235"));
+    ud235.addParticle(ParticleName("U1"), U235);
+    ud235.addParticle(ParticleName("U2"), U235);
+
+    Molecule ud238(MoleculeName("UraniumDimer238"));
+    ud238.addParticle(ParticleName("U1"), U238);
+    ud238.addParticle(ParticleName("U2"), U238);
+
+    TopologyBuilder topologyBuilder;
+    topologyBuilder.addMolecule(ud235, 1);
+    EXPECT_THROW(topologyBuilder.addMolecule(ud238, 1), InputException);
+}
+
+TEST(NBlibTest, TopologyHasExclusions)
+{
+    WaterTopologyBuilder         waters;
+    Topology                     watersTopology = waters.buildTopology(2);
+    const gmx::ListOfLists<int>& testExclusions = watersTopology.getGmxExclusions();
+
+    const std::vector<std::vector<int>>& refExclusions = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
+                                                           { 3, 4, 5 }, { 3, 4, 5 }, { 3, 4, 5 } };
+
+    compareLists(testExclusions, refExclusions);
+}
+
+TEST(NBlibTest, TopologyHasSequencing)
+{
+    WaterTopologyBuilder waters;
+    Topology             watersTopology = waters.buildTopology(2);
+
+    EXPECT_EQ(0, watersTopology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"),
+                                           ParticleName("Oxygen")));
+    EXPECT_EQ(1, watersTopology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("H1")));
+    EXPECT_EQ(2, watersTopology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("H2")));
+    EXPECT_EQ(3, watersTopology.sequenceID(MoleculeName("SOL"), 1, ResidueName("SOL"),
+                                           ParticleName("Oxygen")));
+    EXPECT_EQ(4, watersTopology.sequenceID(MoleculeName("SOL"), 1, ResidueName("SOL"), ParticleName("H1")));
+    EXPECT_EQ(5, watersTopology.sequenceID(MoleculeName("SOL"), 1, ResidueName("SOL"), ParticleName("H2")));
+}
+
+TEST(NBlibTest, toGmxExclusionBlockWorks)
+{
+    std::vector<std::tuple<int, int>> testInput{ { 0, 0 }, { 0, 1 }, { 0, 2 }, { 1, 0 }, { 1, 1 },
+                                                 { 1, 2 }, { 2, 0 }, { 2, 1 }, { 2, 2 } };
+
+    std::vector<gmx::ExclusionBlock> reference;
+
+    gmx::ExclusionBlock localBlock;
+    localBlock.atomNumber.push_back(0);
+    localBlock.atomNumber.push_back(1);
+    localBlock.atomNumber.push_back(2);
+
+    reference.push_back(localBlock);
+    reference.push_back(localBlock);
+    reference.push_back(localBlock);
+
+    std::vector<gmx::ExclusionBlock> probe = detail::toGmxExclusionBlock(testInput);
+
+    ASSERT_EQ(reference.size(), probe.size());
+    for (size_t i = 0; i < reference.size(); ++i)
+    {
+        ASSERT_EQ(reference[i].atomNumber.size(), probe[i].atomNumber.size());
+        for (size_t j = 0; j < reference[i].atomNumber.size(); ++j)
+        {
+            EXPECT_EQ(reference[i].atomNumber[j], probe[i].atomNumber[j]);
+        }
+    }
+}
+
+} // namespace
+} // namespace test
+} // namespace nblib
diff --git a/api/nblib/topology.cpp b/api/nblib/topology.cpp
new file mode 100644 (file)
index 0000000..4e48107
--- /dev/null
@@ -0,0 +1,259 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2020, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * Implements nblib Topology and TopologyBuilder
+ *
+ * \author Victor Holanda <victor.holanda@cscs.ch>
+ * \author Joe Jordan <ejjordan@kth.se>
+ * \author Prashanth Kanduri <kanduri@cscs.ch>
+ * \author Sebastian Keller <keller@cscs.ch>
+ * \author Artem Zhmurov <zhmurov@gmail.com>
+ */
+#include <numeric>
+
+#include "gromacs/topology/exclusionblocks.h"
+#include "gromacs/utility/listoflists.h"
+#include "gromacs/utility/smalloc.h"
+#include "nblib/exception.h"
+#include "nblib/particletype.h"
+#include "nblib/topology.h"
+#include "nblib/util/internal.h"
+
+namespace nblib
+{
+
+TopologyBuilder::TopologyBuilder() : numParticles_(0) {}
+
+gmx::ListOfLists<int> TopologyBuilder::createExclusionsListOfLists() const
+{
+    const auto& moleculesList = molecules_;
+
+    std::vector<gmx::ExclusionBlock> exclusionBlockGlobal;
+    exclusionBlockGlobal.reserve(numParticles_);
+
+    size_t particleNumberOffset = 0;
+    for (const auto& molNumberTuple : moleculesList)
+    {
+        const Molecule& molecule   = std::get<0>(molNumberTuple);
+        size_t          numMols    = std::get<1>(molNumberTuple);
+        const auto&     exclusions = molecule.getExclusions();
+
+        assert((!exclusions.empty()
+                && std::string("No exclusions found in the " + molecule.name().value() + " molecule.")
+                           .c_str()));
+
+        std::vector<gmx::ExclusionBlock> exclusionBlockPerMolecule =
+                detail::toGmxExclusionBlock(exclusions);
+
+        // duplicate the exclusionBlockPerMolecule for the number of Molecules of (numMols)
+        for (size_t i = 0; i < numMols; ++i)
+        {
+            auto offsetExclusions =
+                    detail::offsetGmxBlock(exclusionBlockPerMolecule, particleNumberOffset);
+
+            std::copy(std::begin(offsetExclusions), std::end(offsetExclusions),
+                      std::back_inserter(exclusionBlockGlobal));
+
+            particleNumberOffset += molecule.numParticlesInMolecule();
+        }
+    }
+
+    gmx::ListOfLists<int> exclusionsListOfListsGlobal;
+    for (const auto& block : exclusionBlockGlobal)
+    {
+        exclusionsListOfListsGlobal.pushBack(block.atomNumber);
+    }
+
+    return exclusionsListOfListsGlobal;
+}
+
+template<typename T, class Extractor>
+std::vector<T> TopologyBuilder::extractParticleTypeQuantity(Extractor&& extractor)
+{
+    auto& moleculesList = molecules_;
+
+    // returned object
+    std::vector<T> ret;
+    ret.reserve(numParticles_);
+
+    for (auto& molNumberTuple : moleculesList)
+    {
+        Molecule& molecule = std::get<0>(molNumberTuple);
+        size_t    numMols  = std::get<1>(molNumberTuple);
+
+        for (size_t i = 0; i < numMols; ++i)
+        {
+            for (auto& particleData : molecule.particleData())
+            {
+                auto particleTypesMap = molecule.particleTypesMap();
+                ret.push_back(extractor(particleData, particleTypesMap));
+            }
+        }
+    }
+
+    return ret;
+}
+
+Topology TopologyBuilder::buildTopology()
+{
+    topology_.numParticles_ = numParticles_;
+
+    topology_.exclusions_ = createExclusionsListOfLists();
+    topology_.charges_    = extractParticleTypeQuantity<real>([](const auto& data, auto& map) {
+        ignore_unused(map);
+        return data.charge_;
+    });
+
+    // map unique ParticleTypes to IDs
+    std::unordered_map<std::string, int> nameToId;
+    for (auto& name_particleType_tuple : particleTypes_)
+    {
+        topology_.particleTypes_.push_back(name_particleType_tuple.second);
+        nameToId[name_particleType_tuple.first] = nameToId.size();
+    }
+
+    topology_.particleTypeIdOfAllParticles_ =
+            extractParticleTypeQuantity<int>([&nameToId](const auto& data, auto& map) {
+                ignore_unused(map);
+                return nameToId[data.particleTypeName_];
+            });
+
+    detail::ParticleSequencer particleSequencer;
+    particleSequencer.build(molecules_);
+    topology_.particleSequencer_ = std::move(particleSequencer);
+
+    topology_.combinationRule_         = particleTypesInteractions_.getCombinationRule();
+    topology_.nonBondedInteractionMap_ = particleTypesInteractions_.generateTable();
+
+    // Check whether there is any missing term in the particleTypesInteractions compared to the
+    // list of particletypes
+    for (const auto& particleType1 : particleTypes_)
+    {
+        for (const auto& particleType2 : particleTypes_)
+        {
+            auto interactionKey = std::make_tuple(ParticleTypeName(particleType1.first),
+                                                  ParticleTypeName(particleType2.first));
+            if (topology_.nonBondedInteractionMap_.count(interactionKey) == 0)
+            {
+                std::string message =
+                        formatString("Missing nonbonded interaction parameters for pair {} {}",
+                                     particleType1.first, particleType2.first);
+                throw InputException(message);
+            }
+        }
+    }
+
+    return topology_;
+}
+
+TopologyBuilder& TopologyBuilder::addMolecule(const Molecule& molecule, const int nMolecules)
+{
+    /*
+     * 1. Push-back a tuple of molecule type and nMolecules
+     * 2. Append exclusion list into the data structure
+     */
+
+    molecules_.emplace_back(molecule, nMolecules);
+    numParticles_ += nMolecules * molecule.numParticlesInMolecule();
+
+    auto particleTypesInMolecule = molecule.particleTypesMap();
+
+    for (const auto& name_type_tuple : particleTypesInMolecule)
+    {
+        // If we already have the particleType, we need to make
+        // sure that the type's parameters are actually the same
+        // otherwise we would overwrite them
+        if (particleTypes_.count(name_type_tuple.first) > 0)
+        {
+            if (!(particleTypes_.at(name_type_tuple.first) == name_type_tuple.second))
+            {
+                throw InputException("Differing ParticleTypes with identical names encountered");
+            }
+        }
+    }
+
+    // Note: insert does nothing if the key already exists
+    particleTypes_.insert(particleTypesInMolecule.begin(), particleTypesInMolecule.end());
+
+    return *this;
+}
+
+void TopologyBuilder::addParticleTypesInteractions(const ParticleTypesInteractions& particleTypesInteractions)
+{
+    particleTypesInteractions_.merge(particleTypesInteractions);
+}
+
+int Topology::numParticles() const
+{
+    return numParticles_;
+}
+
+std::vector<real> Topology::getCharges() const
+{
+    return charges_;
+}
+
+std::vector<ParticleType> Topology::getParticleTypes() const
+{
+    return particleTypes_;
+}
+
+std::vector<int> Topology::getParticleTypeIdOfAllParticles() const
+{
+    return particleTypeIdOfAllParticles_;
+}
+
+int Topology::sequenceID(MoleculeName moleculeName, int moleculeNr, ResidueName residueName, ParticleName particleName) const
+{
+    return particleSequencer_(moleculeName, moleculeNr, residueName, particleName);
+}
+
+NonBondedInteractionMap Topology::getNonBondedInteractionMap() const
+{
+    return nonBondedInteractionMap_;
+}
+
+CombinationRule Topology::getCombinationRule() const
+{
+    return combinationRule_;
+}
+
+gmx::ListOfLists<int> Topology::getGmxExclusions() const
+{
+    return exclusions_;
+}
+
+} // namespace nblib
index 17774c2f77e2173faff0fc4608c07650db8c453e..380daf79ac57e144475491779cfb36a48fcae729 100644 (file)
@@ -175,6 +175,26 @@ private:
     ParticleTypesInteractions particleTypesInteractions_;
 };
 
+//! utility function to extract Particle quantities and expand them to the full
+//! array of length numParticles()
+template<class F>
+inline auto expandQuantity(const Topology& topology, F&& particleTypeExtractor)
+{
+    using ValueType = decltype((std::declval<ParticleType>().*std::declval<F>())());
+
+    std::vector<ValueType> ret;
+    ret.reserve(topology.numParticles());
+
+    const std::vector<ParticleType>& particleTypes = topology.getParticleTypes();
+
+    for (size_t id : topology.getParticleTypeIdOfAllParticles())
+    {
+        ret.push_back((particleTypes[id].*particleTypeExtractor)());
+    }
+
+    return ret;
+}
+
 } // namespace nblib
 
 #endif // NBLIB_TOPOLOGY_H
diff --git a/api/nblib/topologyhelpers.cpp b/api/nblib/topologyhelpers.cpp
new file mode 100644 (file)
index 0000000..68b109a
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2020, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * Implements nblib Topology helpers
+ *
+ * \author Victor Holanda <victor.holanda@cscs.ch>
+ * \author Joe Jordan <ejjordan@kth.se>
+ * \author Prashanth Kanduri <kanduri@cscs.ch>
+ * \author Sebastian Keller <keller@cscs.ch>
+ * \author Artem Zhmurov <zhmurov@gmail.com>
+ */
+#include <numeric>
+
+#include "gromacs/topology/exclusionblocks.h"
+#include "gromacs/utility/smalloc.h"
+#include "nblib/exception.h"
+#include "nblib/topologyhelpers.h"
+#include "nblib/util/internal.h"
+
+namespace nblib
+{
+
+namespace detail
+{
+
+std::vector<gmx::ExclusionBlock> toGmxExclusionBlock(const std::vector<std::tuple<int, int>>& tupleList)
+{
+    std::vector<gmx::ExclusionBlock> ret;
+
+    auto firstLowerThan = [](auto const& tup1, auto const& tup2) {
+        return std::get<0>(tup1) < std::get<0>(tup2);
+    };
+
+    // initialize pair of iterators delimiting the range of exclusions for
+    // the first particle in the list
+    assert((!tupleList.empty() && "tupleList must not be empty\n"));
+    auto range = std::equal_range(std::begin(tupleList), std::end(tupleList), tupleList[0], firstLowerThan);
+    auto it1 = range.first;
+    auto it2 = range.second;
+
+    // loop over all exclusions in molecule, linear in tupleList.size()
+    while (it1 != std::end(tupleList))
+    {
+        gmx::ExclusionBlock localBlock;
+        // loop over all exclusions for current particle
+        for (; it1 != it2; ++it1)
+        {
+            localBlock.atomNumber.push_back(std::get<1>(*it1));
+        }
+
+        ret.push_back(localBlock);
+
+        // update the upper bound of the range for the next particle
+        if (it1 != end(tupleList))
+        {
+            it2 = std::upper_bound(it1, std::end(tupleList), *it1, firstLowerThan);
+        }
+    }
+
+    return ret;
+}
+
+std::vector<gmx::ExclusionBlock> offsetGmxBlock(std::vector<gmx::ExclusionBlock> inBlock, int offset)
+{
+    // shift particle numbers by offset
+    for (auto& localBlock : inBlock)
+    {
+        std::transform(std::begin(localBlock.atomNumber), std::end(localBlock.atomNumber),
+                       std::begin(localBlock.atomNumber), [offset](auto i) { return i + offset; });
+    }
+
+    return inBlock;
+}
+
+int ParticleSequencer::operator()(const MoleculeName& moleculeName,
+                                  int                 moleculeNr,
+                                  const ResidueName&  residueName,
+                                  const ParticleName& particleName) const
+{
+    try
+    {
+        return data_.at(moleculeName).at(moleculeNr).at(residueName).at(particleName);
+    }
+    catch (const std::out_of_range& outOfRange)
+    {
+        // TODO: use string format function once we have it
+        if (moleculeName.value() == residueName.value())
+        {
+            printf("No particle %s in residue %s in molecule %s found\n", particleName.value().c_str(),
+                   residueName.value().c_str(), moleculeName.value().c_str());
+        }
+        else
+        {
+            printf("No particle %s in molecule %s found\n", particleName.value().c_str(),
+                   moleculeName.value().c_str());
+        }
+
+        throw InputException(outOfRange.what());
+    };
+}
+
+void ParticleSequencer::build(const std::vector<std::tuple<Molecule, int>>& moleculesList)
+{
+    int currentID = 0;
+    for (auto& molNumberTuple : moleculesList)
+    {
+        const Molecule& molecule = std::get<0>(molNumberTuple);
+        const size_t    numMols  = std::get<1>(molNumberTuple);
+
+        auto& moleculeMap = data_[molecule.name()];
+
+        for (size_t i = 0; i < numMols; ++i)
+        {
+            auto& moleculeNrMap = moleculeMap[i];
+            for (int j = 0; j < molecule.numParticlesInMolecule(); ++j)
+            {
+                moleculeNrMap[molecule.residueName(j)][molecule.particleName(j)] = currentID++;
+            }
+        }
+    }
+}
+
+
+template<class I>
+std::tuple<std::vector<size_t>, std::vector<I>> eliminateDuplicateInteractions(const std::vector<I>& aggregatedInteractions)
+{
+    std::vector<size_t> uniqueIndices(aggregatedInteractions.size());
+    std::vector<I>      uniquInteractionsInstances;
+    // if there are no interactions of type B we're done now
+    if (aggregatedInteractions.empty())
+    {
+        return std::make_tuple(uniqueIndices, uniquInteractionsInstances);
+    }
+
+    // create 0,1,2,... sequence
+    std::iota(begin(uniqueIndices), end(uniqueIndices), 0);
+
+    std::vector<std::tuple<I, size_t>> enumeratedBonds(aggregatedInteractions.size());
+    // append each interaction with its index
+    std::transform(begin(aggregatedInteractions), end(aggregatedInteractions), begin(uniqueIndices),
+                   begin(enumeratedBonds), [](I b, size_t i) { return std::make_tuple(b, i); });
+
+    auto sortKey = [](const auto& t1, const auto& t2) { return std::get<0>(t1) < std::get<0>(t2); };
+    // sort w.r.t bonds. the result will contain contiguous segments of identical bond instances
+    // the associated int indicates the original index of each BondType instance in the input vector
+    std::sort(begin(enumeratedBonds), end(enumeratedBonds), sortKey);
+
+    // initialize it1 and it2 to delimit first range of equal BondType instances
+    auto range = std::equal_range(begin(enumeratedBonds), end(enumeratedBonds), enumeratedBonds[0], sortKey);
+    auto it1 = range.first;
+    auto it2 = range.second;
+
+    // number of unique instances of BondType B = number of contiguous segments in enumeratedBonds =
+    //         number of iterations in the outer while loop below
+    while (it1 != end(enumeratedBonds))
+    {
+        uniquInteractionsInstances.push_back(std::get<0>(*it1));
+
+        // loop over all identical BondType instances;
+        for (; it1 != it2; ++it1)
+        {
+            // we note down that the BondType instance at index <interactionIndex>
+            // can be found in the uniqueBondInstances container at index <uniqueBondInstances.size()>
+            int interactionIndex            = std::get<1>(*it1);
+            uniqueIndices[interactionIndex] = uniquInteractionsInstances.size() - 1;
+        }
+
+        // Note it1 has been incremented and is now equal to it2
+        if (it1 != end(enumeratedBonds))
+        {
+            it2 = std::upper_bound(it1, end(enumeratedBonds), *it1, sortKey);
+        }
+    }
+
+    return make_tuple(uniqueIndices, uniquInteractionsInstances);
+}
+
+} // namespace detail
+
+} // namespace nblib
index 5bed93690da092494d66a4873f58dd4e455677db..a695013688502ced21cd7d938f28226f99620979 100644 (file)
 # \author Sebastian Keller <keller@cscs.ch>
 #
 
+
+target_sources(nblib
+        PRIVATE
+        user.cpp
+        )
+
 if(GMX_INSTALL_NBLIB_API)
     install(FILES
             user.h
             DESTINATION include/nblib/util)
 endif()
+
+if(BUILD_TESTING)
+    add_subdirectory(tests)
+endif()
diff --git a/api/nblib/util/internal.h b/api/nblib/util/internal.h
new file mode 100644 (file)
index 0000000..63a8658
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2020, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \inpublicapi \file
+ * \brief
+ * Implements nblib utilities
+ *
+ * \author Victor Holanda <victor.holanda@cscs.ch>
+ * \author Joe Jordan <ejjordan@kth.se>
+ * \author Prashanth Kanduri <kanduri@cscs.ch>
+ * \author Sebastian Keller <keller@cscs.ch>
+ * \author Artem Zhmurov <zhmurov@gmail.com>
+ */
+
+#ifndef NBLIB_UTIL_INTERNAL_H
+#define NBLIB_UTIL_INTERNAL_H
+
+#include <cassert>
+
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <vector>
+
+#include "nblib/basicdefinitions.h"
+#include "nblib/vector.h"
+
+namespace nblib
+{
+
+namespace detail
+{
+//! Format strings for use in error messages
+std::string next_token(std::string& s, const std::string& delimiter);
+
+// Like std::void_t but for values
+template<auto...>
+using void_value_t = void;
+
+template<class T, class = void>
+struct HasValueMember : std::false_type
+{
+};
+
+template<class T>
+struct HasValueMember<T, void_value_t<T::value>> : std::true_type
+{
+};
+
+template<class T, class = void>
+struct AccessTypeMemberIfPresent
+{
+    typedef T type;
+};
+
+template<class T>
+struct AccessTypeMemberIfPresent<T, typename std::void_t<typename T::type>>
+{
+    typedef typename T::type type;
+};
+
+template<class T>
+using AccessTypeMemberIfPresent_t = typename AccessTypeMemberIfPresent<T>::type;
+
+//! this trait evaluates to std::true_type if T is the same as Tuple[N]
+//! OR if T is the same as the type member of Tuple[N]
+template<int N, typename T, typename Tuple>
+struct MatchTypeOrTypeMember :
+    std::disjunction<std::is_same<T, std::tuple_element_t<N, Tuple>>,
+                     std::is_same<T, AccessTypeMemberIfPresent_t<std::tuple_element_t<N, Tuple>>>>
+{
+};
+
+//! recursion to check the next field N+1
+template<int N, class T, class Tuple, template<int, class, class> class Comparison, bool Match = false>
+struct MatchField_ :
+    std::integral_constant<size_t, MatchField_<N + 1, T, Tuple, Comparison, Comparison<N + 1, T, Tuple>{}>{}>
+{
+};
+
+//! recursion stop when Comparison<N, T, Tuple>::value is true
+template<int N, class T, class Tuple, template<int, class, class> class Comparison>
+struct MatchField_<N, T, Tuple, Comparison, true> : std::integral_constant<size_t, N>
+{
+};
+
+} // namespace detail
+
+/*! \brief The value member of this struct evaluates to the integral constant N for which
+ *  the value member of Comparison<N, T, Tuple> is true
+ *  and generates a compiler error if there is no such N
+ */
+template<class T, class Tuple, template<int, class, class> class Comparison>
+struct MatchField : detail::MatchField_<0, T, Tuple, Comparison, Comparison<0, T, Tuple>{}>
+{
+};
+
+/*! \brief Function to return the index in Tuple whose type matches T
+ *  - If there are more than one, the first occurrence will be returned
+ *  - If there is no such type, a compiler error from accessing a tuple out of range is generated
+ *  Note that the default comparison operation supplied here also matches if the type member of Tuple[N] matches T
+ */
+template<typename T, typename Tuple, template<int, class, class> class Comparison = detail::MatchTypeOrTypeMember>
+struct FindIndex : std::integral_constant<size_t, MatchField<T, Tuple, Comparison>{}>
+{
+};
+
+//! Function to return the element in Tuple whose type matches T
+//! Note: if there are more than one, the first occurrence will be returned
+template<typename T, typename Tuple>
+decltype(auto) pickType(Tuple& tup)
+{
+    return std::get<FindIndex<T, Tuple>{}>(tup);
+}
+
+//! Utility to call function with each element in tuple_
+template<class F, class... Ts>
+void for_each_tuple(F&& func, std::tuple<Ts...>& tuple_)
+{
+    std::apply(
+            [f = func](auto&... args) {
+                [[maybe_unused]] auto list = std::initializer_list<int>{ (f(args), 0)... };
+            },
+            tuple_);
+}
+
+//! Utility to call function with each element in tuple_ with const guarantee
+template<class F, class... Ts>
+void for_each_tuple(F&& func, const std::tuple<Ts...>& tuple_)
+{
+    std::apply(
+            [f = func](auto&... args) {
+                [[maybe_unused]] auto list = std::initializer_list<int>{ (f(args), 0)... };
+            },
+            tuple_);
+}
+
+//! Format strings for use in error messages
+template<class... Args>
+std::string formatString(std::string fmt, Args... args)
+{
+    std::ostringstream os;
+    std::string        delimiter = "{}";
+
+    std::initializer_list<int> unused{ 0, (os << detail::next_token(fmt, delimiter) << args, 0)... };
+    static_cast<void>(unused); // unused is not actually used
+
+    os << detail::next_token(fmt, delimiter);
+
+    return os.str();
+}
+
+} // namespace nblib
+
+#endif // NBLIB_UTIL_INTERNAL_H
diff --git a/api/nblib/util/tests/CMakeLists.txt b/api/nblib/util/tests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..4169b07
--- /dev/null
@@ -0,0 +1,52 @@
+#
+# This file is part of the GROMACS molecular simulation package.
+#
+# Copyright (c) 2020, by the GROMACS development team, led by
+# Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+# and including many others, as listed in the AUTHORS file in the
+# top-level source directory and at http://www.gromacs.org.
+#
+# GROMACS is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation; either version 2.1
+# of the License, or (at your option) any later version.
+#
+# GROMACS 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with GROMACS; if not, see
+# http://www.gnu.org/licenses, or write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+#
+# If you want to redistribute modifications to GROMACS, please
+# consider that scientific software is very special. Version
+# control is crucial - bugs must be traceable. We will be happy to
+# consider code for inclusion in the official distribution, but
+# derived work must not be called official GROMACS. Details are found
+# in the README & COPYING files - if they are missing, get the
+# official version at http://www.gromacs.org.
+#
+# To help us fund GROMACS development, we humbly ask that you cite
+# the research papers on the package. Check out http://www.gromacs.org.
+#
+# \author Victor Holanda <victor.holanda@cscs.ch>
+# \author Joe Jordan <ejjordan@kth.se>
+# \author Prashanth Kanduri <kanduri@cscs.ch>
+# \author Sebastian Keller <keller@cscs.ch>
+#
+
+set(testname "NbLibUtilTests")
+set(exename "nblib-util-test")
+
+gmx_add_gtest_executable(
+        ${exename}
+        CPP_SOURCE_FILES
+        user.cpp
+)
+target_link_libraries(${exename} PRIVATE nblib_test_infrastructure nblib)
+gmx_register_gtest_test(${testname} ${exename})
+add_dependencies(check-nblib ${exename})
+
diff --git a/api/nblib/util/tests/refdata/NBlibTest_GeneratedVelocitiesAreCorrect.xml b/api/nblib/util/tests/refdata/NBlibTest_GeneratedVelocitiesAreCorrect.xml
new file mode 100644 (file)
index 0000000..a78f037
--- /dev/null
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+  <Sequence Name="generated-velocities">
+    <Int Name="Length">10</Int>
+    <Vector>
+      <Real Name="X">2.6981033066448674</Real>
+      <Real Name="Y">0.5539709822574701</Real>
+      <Real Name="Z">-0.66999586711644377</Real>
+    </Vector>
+    <Vector>
+      <Real Name="X">1.2082614830533767</Real>
+      <Real Name="Y">-0.94750311926855424</Real>
+      <Real Name="Z">-0.3939451998729408</Real>
+    </Vector>
+    <Vector>
+      <Real Name="X">-0.25639656387655385</Real>
+      <Real Name="Y">0.15094422478273684</Real>
+      <Real Name="Z">-1.9023008166840518</Real>
+    </Vector>
+    <Vector>
+      <Real Name="X">-2.6653384151271498</Real>
+      <Real Name="Y">1.0284873953993694</Real>
+      <Real Name="Z">1.8633553870427515</Real>
+    </Vector>
+    <Vector>
+      <Real Name="X">-0.51905872128588526</Real>
+      <Real Name="Y">-1.5808470201670317</Real>
+      <Real Name="Z">0.59660484580158024</Real>
+    </Vector>
+    <Vector>
+      <Real Name="X">-1.5358923264099191</Real>
+      <Real Name="Y">-4.0045494036796079</Real>
+      <Real Name="Z">2.3295413128004117</Real>
+    </Vector>
+    <Vector>
+      <Real Name="X">2.0461371078863495</Real>
+      <Real Name="Y">-0.65718768377791814</Real>
+      <Real Name="Z">-0.84789603041054018</Real>
+    </Vector>
+    <Vector>
+      <Real Name="X">0.52471564906472223</Real>
+      <Real Name="Y">2.0471783903038139</Real>
+      <Real Name="Z">1.0757782548241039</Real>
+    </Vector>
+    <Vector>
+      <Real Name="X">-0.53067549253173119</Real>
+      <Real Name="Y">1.0085627678362028</Real>
+      <Real Name="Z">1.5091817455269219</Real>
+    </Vector>
+    <Vector>
+      <Real Name="X">0.71045843460233404</Real>
+      <Real Name="Y">-1.4262273469861517</Real>
+      <Real Name="Z">2.2175722402808948</Real>
+    </Vector>
+  </Sequence>
+</ReferenceData>
diff --git a/api/nblib/util/tests/user.cpp b/api/nblib/util/tests/user.cpp
new file mode 100644 (file)
index 0000000..0fccd6e
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2020, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * This implements basic nblib utility tests
+ *
+ * \author Victor Holanda <victor.holanda@cscs.ch>
+ * \author Joe Jordan <ejjordan@kth.se>
+ * \author Prashanth Kanduri <kanduri@cscs.ch>
+ * \author Sebastian Keller <keller@cscs.ch>
+ */
+#include <vector>
+
+#include "nblib/tests/testhelpers.h"
+#include "nblib/util/user.h"
+
+#include "testutils/testasserts.h"
+
+
+namespace nblib
+{
+namespace test
+{
+namespace
+{
+
+TEST(NBlibTest, checkNumericValues)
+{
+    std::vector<Vec3> vec;
+    vec.emplace_back(1., 1., 1.);
+    vec.emplace_back(2., 2., 2.);
+
+    bool ret = checkNumericValues(vec);
+    EXPECT_EQ(ret, true);
+}
+
+TEST(NBlibTest, checkNumericValuesHasNan)
+{
+    std::vector<Vec3> vec;
+    vec.emplace_back(1., 1., 1.);
+    vec.emplace_back(2., 2., 2.);
+
+    vec.emplace_back(NAN, NAN, NAN);
+
+    bool ret = checkNumericValues(vec);
+    EXPECT_EQ(ret, false);
+}
+
+TEST(NBlibTest, checkNumericValuesHasInf)
+{
+    std::vector<Vec3> vec;
+    vec.emplace_back(1., 1., 1.);
+    vec.emplace_back(2., 2., 2.);
+
+    vec.emplace_back(INFINITY, INFINITY, INFINITY);
+
+    bool ret = checkNumericValues(vec);
+    EXPECT_EQ(ret, false);
+}
+
+
+TEST(NBlibTest, GeneratedVelocitiesAreCorrect)
+{
+    constexpr size_t  N = 10;
+    std::vector<real> masses(N, 1.0);
+    std::vector<Vec3> velocities;
+    velocities = generateVelocity(300.0, 1, masses);
+
+    Vector3DTest velocitiesTest;
+    velocitiesTest.testVectors(velocities, "generated-velocities");
+}
+TEST(NBlibTest, generateVelocitySize)
+{
+    constexpr int     N = 10;
+    std::vector<real> masses(N, 1.0);
+    auto              out = generateVelocity(300.0, 1, masses);
+    EXPECT_EQ(out.size(), N);
+}
+
+TEST(NBlibTest, generateVelocityCheckNumbers)
+{
+    constexpr int     N = 10;
+    std::vector<real> masses(N, 1.0);
+    auto              out = generateVelocity(300.0, 1, masses);
+    bool              ret = checkNumericValues(out);
+    EXPECT_EQ(ret, true);
+}
+
+} // namespace
+} // namespace test
+} // namespace nblib
diff --git a/api/nblib/util/user.cpp b/api/nblib/util/user.cpp
new file mode 100644 (file)
index 0000000..99f8501
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2020, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * Implements nblib utility functions
+ *
+ * \author Victor Holanda <victor.holanda@cscs.ch>
+ * \author Joe Jordan <ejjordan@kth.se>
+ * \author Prashanth Kanduri <kanduri@cscs.ch>
+ * \author Sebastian Keller <keller@cscs.ch>
+ * \author Artem Zhmurov <zhmurov@gmail.com>
+ */
+
+#include "nblib/util/user.h"
+#include "gromacs/random/tabulatednormaldistribution.h"
+#include "gromacs/random/threefry.h"
+#include "gromacs/utility/fatalerror.h"
+
+namespace nblib
+{
+
+namespace detail
+{
+
+std::string next_token(std::string& s, const std::string& delimiter)
+{
+    std::string token = s.substr(0, s.find(delimiter));
+
+    std::size_t next = s.find(delimiter);
+    if (next == std::string::npos)
+        s.clear();
+    else
+        s.erase(0, next + delimiter.length());
+
+    return token;
+}
+
+} // namespace detail
+
+//! Generates an array of particle velocities based on the Maxwell-Boltzmann distribution
+//! using temperature, masses and a random number generator
+static std::vector<Vec3> low_mspeed(real tempi, std::vector<real> const& masses, gmx::ThreeFry2x64<>* rng)
+{
+    int                                    nrdf;
+    real                                   boltz;
+    real                                   ekin, temp;
+    gmx::TabulatedNormalDistribution<real> normalDist;
+
+    std::vector<Vec3> velocities(masses.size());
+
+    boltz = BOLTZ * tempi;
+    ekin  = 0.0;
+    nrdf  = 0;
+    for (size_t i = 0; i < masses.size(); i++)
+    {
+        real mass = masses[i];
+        if (mass > 0)
+        {
+            rng->restart(i, 0);
+            real sd = std::sqrt(boltz / mass);
+            for (int m = 0; (m < dimSize); m++)
+            {
+                velocities[i][m] = sd * normalDist(*rng);
+                ekin += 0.5 * mass * velocities[i][m] * velocities[i][m];
+            }
+            nrdf += dimSize;
+        }
+    }
+    temp = (2.0 * ekin) / (nrdf * BOLTZ);
+    if (temp > 0)
+    {
+        real scal = std::sqrt(tempi / temp);
+        for (auto& vel : velocities)
+        {
+            for (int m = 0; (m < dimSize); m++)
+            {
+                vel[m] *= scal;
+            }
+        }
+    }
+    fprintf(stderr, "Velocities were taken from a Maxwell distribution at %g K\n", tempi);
+    if (debug)
+    {
+        fprintf(debug,
+                "Velocities were taken from a Maxwell distribution\n"
+                "Initial generated temperature: %12.5e (scaled to: %12.5e)\n",
+                temp, tempi);
+    }
+
+    return velocities;
+}
+
+//! Generate velocities from a Maxwell Boltzmann distribution, masses should be the
+//! same as the ones specified for the Topology object
+std::vector<Vec3> generateVelocity(real tempi, unsigned int seed, std::vector<real> const& masses)
+{
+
+    if (seed == 0)
+    {
+        seed = static_cast<int>(gmx::makeRandomSeed());
+        fprintf(stderr, "Using random seed %u for generating velocities\n", seed);
+    }
+    gmx::ThreeFry2x64<> rng(seed, gmx::RandomDomain::MaxwellVelocities);
+
+    return low_mspeed(tempi, masses, &rng);
+}
+
+//! Check within the container of gmx::RVecs for a NaN or inf
+bool checkNumericValues(const std::vector<Vec3>& values)
+{
+    for (auto val : values)
+    {
+        for (int m = 0; (m < dimSize); m++)
+        {
+            if (std::isnan(val[m]) or std::isinf(val[m]))
+            {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+} // namespace nblib