Update to nblib non-bonded backend
authorejjordan <ejjordan@kth.se>
Mon, 2 Nov 2020 11:05:11 +0000 (12:05 +0100)
committerejjordan <ejjordan@kth.se>
Mon, 2 Nov 2020 12:41:43 +0000 (13:41 +0100)
* added more documentation
* more consistent naming
* cleaner, more consistent interfaces
* add more checks
* expand test coverage
* fix bugs

21 files changed:
api/nblib/forcecalculator.cpp
api/nblib/forcecalculator.h
api/nblib/gmxcalculator.cpp
api/nblib/gmxcalculator.h
api/nblib/gmxsetup.cpp
api/nblib/gmxsetup.h
api/nblib/integrator.cpp
api/nblib/molecules.cpp
api/nblib/molecules.h
api/nblib/simulationstate.cpp
api/nblib/tests/molecules.cpp
api/nblib/tests/nbkernelsystem.cpp
api/nblib/tests/testsystems.cpp
api/nblib/topology.cpp
api/nblib/topologyhelpers.cpp
api/nblib/util/internal.h
api/nblib/util/tests/CMakeLists.txt
api/nblib/util/tests/internal.cpp [new file with mode: 0644]
api/nblib/util/tests/user.cpp
api/nblib/util/user.cpp
api/nblib/util/user.h

index 61dc95af31d8506933d52ddf40498d329a496ac5..5287d4af7de6b22610e7a5b1e343617b8dd88a71 100644 (file)
@@ -40,6 +40,7 @@
  * \author Prashanth Kanduri <kanduri@cscs.ch>
  * \author Sebastian Keller <keller@cscs.ch>
  */
+#include "nblib/exception.h"
 #include "nblib/forcecalculator.h"
 #include "nblib/gmxcalculator.h"
 #include "nblib/gmxsetup.h"
@@ -56,7 +57,12 @@ ForceCalculator::ForceCalculator(const SimulationState& system, const NBKernelOp
 
 void ForceCalculator::compute(gmx::ArrayRef<const Vec3> coordinates, gmx::ArrayRef<Vec3> forces)
 {
-    return gmxForceCalculator_->compute(coordinates, forces);
+    if (coordinates.size() != forces.size())
+    {
+        throw InputException("Coordinates array and force buffer size mismatch");
+    }
+
+    gmxForceCalculator_->compute(coordinates, forces);
 }
 
 void ForceCalculator::updatePairList(gmx::ArrayRef<const int> particleInfoAllVdW,
index 7083da541817db7f6a6c1b300814fb71da474d7a..ca7173aa2c3677a916bf9a1586c6efa1a0a93f2f 100644 (file)
@@ -66,7 +66,7 @@ class GmxForceCalculator;
  * costly to create this object since much of the SimulationState and NBKernelOptions has to be
  * passed to the gromacs backend. However, once constructed, compute can be called repeatedly only
  * paying the cost of the actual nonbonded force calculation. Repeated calls to compute on the same
- * coordinated will always return the same forces (within precision), so the user must update the
+ * coordinates will always return the same forces (within precision), so the user must update the
  * positions using the forces generated here to advance a simulation. If the coordinates move
  * sufficiently far from their positions at construction time, the efficiency of the calculation
  * will suffer. To alleviate this, the user can call updatePairList.
index 30e2594e4005ea3464f862603b433a2935268818..d49b1d1cb56cb692381338695dfbcdaa24e1ea22 100644 (file)
@@ -73,9 +73,6 @@ void GmxForceCalculator::compute(gmx::ArrayRef<const gmx::RVec> coordinateInput,
     // update the coordinates in the backend
     nbv_->convertCoordinates(gmx::AtomLocality::Local, false, coordinateInput);
 
-    // set forces to zero
-    std::fill(forceOutput.begin(), forceOutput.end(), gmx::RVec{ 0, 0, 0 });
-
     nbv_->dispatchNonbondedKernel(gmx::InteractionLocality::Local, *interactionConst_, *stepWork_,
                                   enbvClearFYes, *forcerec_, enerd_.get(), nrnb_.get());
 
index 610983ccf72acda9861c0416ed36c31834f0e938..b5cd04382d3d3eb78a86febbe5fbee9a0acd1879 100644 (file)
@@ -71,6 +71,18 @@ class NbvSetupUtil;
 class SimulationState;
 struct NBKernelOptions;
 
+/*! \brief GROMACS non-bonded force calculation backend
+ *
+ * This class encapsulates the various GROMACS data structures and their interplay
+ * from the NBLIB user. The class is a private member of the ForceCalculator and
+ * is not intended for the public interface.
+ *
+ * Handles the task of storing the simulation problem description using the internal
+ * representation used within GROMACS. It currently supports short range non-bonded
+ * interactions (PP) on a single node CPU.
+ *
+ */
+
 class GmxForceCalculator final
 {
 public:
@@ -87,6 +99,7 @@ public:
                             const Box&                     box);
 
 private:
+    //! Friend to allow setting up private members in this class
     friend class NbvSetupUtil;
 
     //! Non-Bonded Verlet object for force calculation
index 88299e552f247c2ab400875c924cb17a0f47085f..8501ff062a6ce9c22bf7cf02679a88fd60bc443c 100644 (file)
@@ -196,7 +196,8 @@ void NbvSetupUtil::setupNbnxmInstance(const size_t numParticleTypes, const NBKer
 
     // Put everything together
     auto nbv = std::make_unique<nonbonded_verlet_t>(std::move(pairlistSets), std::move(pairSearch),
-                                                    std::move(atomData), kernelSetup, nullptr, nullptr);
+                                                    std::move(atomData), kernelSetup, nullptr,
+                                                    nullWallcycle);
 
     // Needs to be called with the number of unique ParticleTypes
     nbnxn_atomdata_init(gmx::MDLogger(), nbv->nbat.get(), kernelSetup.kernelType, combinationRule,
index 628172ca99fe5a1cac30dc4b55620b0338a30693..1e5d81b25881dc4ba35f15e3bd75c2b3240d93d2 100644 (file)
@@ -59,6 +59,17 @@ struct KernelSetup;
 namespace nblib
 {
 
+/*! \brief Sets up the GROMACS data structures for the non-bonded force calculator
+ *
+ * This data structure initializes the GmxForceCalculator object which internally
+ * contains various objects needed to perform non-bonded force calculations using
+ * the internal representation for the problem as required for GROMACS.
+ *
+ * The public functions of this class basically translate the problem description
+ * specified by the user in NBLIB. This ultimately returns the GmxForceCalculator
+ * object which is used by the ForceCalculator object in the user-facing library.
+ *
+ */
 class NbvSetupUtil final
 {
 public:
@@ -99,6 +110,7 @@ public:
     //! Sets up t_forcerec object on the GmxForceCalculator
     void setupForceRec(const matrix& box);
 
+    //! Returns a unique pointer a GmxForceCalculator object
     std::unique_ptr<GmxForceCalculator> getGmxForceCalculator()
     {
         return std::move(gmxForceCalculator_);
@@ -115,6 +127,16 @@ private:
     std::unique_ptr<GmxForceCalculator> gmxForceCalculator_;
 };
 
+/*! \brief Calls the setup utilities needed to initialize a GmxForceCalculator object
+ *
+ * The GmxSetupDirector encapsulates the multi-stage setup of the GmxForceCalculator which
+ * is done using the public functions of the NbvSetupUtil. This separation ensures that the
+ * NbvSetupUtil object is temporary in scope. The function definition makes it easy for the
+ * developers to follow the sequence of calls and the dataflow involved in setting up
+ * the non-bonded force calculation backend. This is the only function needed to be called
+ * from the ForceCalculator during construction.
+ *
+ */
 class GmxSetupDirector
 {
 public:
index 970eea0c6a9a8acda040e23e64da5c80e6ca7eec..a80338d5ffe939b093b6c983d786dec750a5b7e6 100644 (file)
@@ -49,7 +49,7 @@
 
 namespace nblib
 {
-// NOLINTNEXTLINE(performance-unnecessary-value-param)
+
 LeapFrog::LeapFrog(const Topology& topology, const Box& box) : box_(box)
 {
     inverseMasses_.resize(topology.numParticles());
index b8f70ee797e1fdb11b901efca36fa04aa453e112..c2491e7b5541afc1a391d4ed1bd924c9e8d1f634 100644 (file)
@@ -131,8 +131,8 @@ void Molecule::addExclusion(const int particleIndex, const int particleIndexToEx
     }
 }
 
-void Molecule::addExclusion(std::tuple<std::string, std::string> particle,
-                            std::tuple<std::string, std::string> particleToExclude)
+void Molecule::addExclusion(std::tuple<ParticleName, ResidueName> particle,
+                            std::tuple<ParticleName, ResidueName> particleToExclude)
 {
     // duplication for the swapped pair happens in getExclusions()
     exclusionsByName_.emplace_back(std::make_tuple(std::get<0>(particle), std::get<1>(particle),
@@ -140,9 +140,10 @@ void Molecule::addExclusion(std::tuple<std::string, std::string> particle,
                                                    std::get<1>(particleToExclude)));
 }
 
-void Molecule::addExclusion(const std::string& particleName, const std::string& particleNameToExclude)
+void Molecule::addExclusion(const ParticleName& particleName, const ParticleName& particleNameToExclude)
 {
-    addExclusion(std::make_tuple(particleName, name_), std::make_tuple(particleNameToExclude, name_));
+    addExclusion(std::make_tuple(particleName, ResidueName(name_)),
+                 std::make_tuple(particleNameToExclude, ResidueName(name_)));
 }
 
 const ParticleType& Molecule::at(const std::string& particleTypeName) const
index afcae8ccbd0cbc66b411aeb28854ac5eaf724593..20ee18c78d098ed21c9326ac53193e2fbc04848f 100644 (file)
@@ -57,6 +57,8 @@
 
 namespace nblib
 {
+class TopologyBuilder;
+
 //! Named type for unique identifier for a particle in a molecule
 using ParticleName = StrongType<std::string, struct ParticleNameParameter>;
 
@@ -103,11 +105,11 @@ public:
     void addExclusion(int particleIndex, int particleIndexToExclude);
 
     //! Specify an exclusion with particle and residue names that have been added to molecule
-    void addExclusion(std::tuple<std::string, std::string> particle,
-                      std::tuple<std::string, std::string> particleToExclude);
+    void addExclusion(std::tuple<ParticleName, ResidueName> particle,
+                      std::tuple<ParticleName, ResidueName> particleToExclude);
 
     //! Specify an exclusion with particle names that have been added to molecule
-    void addExclusion(const std::string& particleName, const std::string& particleNameToExclude);
+    void addExclusion(const ParticleName& particleName, const ParticleName& particleNameToExclude);
 
     //! The number of molecules
     int numParticlesInMolecule() const;
index 9e38433644f24e6cb697e0f861748517945b0ce3..c326b828239cc4ed368808326730960ccbc03eee 100644 (file)
@@ -70,12 +70,29 @@ SimulationState::Impl::Impl(const std::vector<Vec3>& coordinates,
     box_(box),
     topology_(std::move(topology))
 {
-    if (!checkNumericValues(coordinates))
+    auto numParticles = topology_.numParticles();
+
+    if (int(coordinates.size()) != numParticles)
+    {
+        throw InputException("Coordinates array size mismatch");
+    }
+
+    if (int(velocities.size()) != numParticles)
+    {
+        throw InputException("Velocities array size mismatch");
+    }
+
+    if (int(forces.size()) != numParticles)
+    {
+        throw InputException("Force buffer array size mismatch");
+    }
+
+    if (!isRealValued(coordinates))
     {
         throw InputException("Input coordinates has at least one NaN");
     }
     coordinates_ = coordinates;
-    if (!checkNumericValues(velocities))
+    if (!isRealValued(velocities))
     {
         throw InputException("Input velocities has at least one NaN");
     }
index 52e5248cbcfd19ec6e8b15eaca75784da3599805..b906e097b56afbf577fcbfc6a6ed21ffc0d5f231 100644 (file)
@@ -142,8 +142,8 @@ TEST(NBlibTest, CanConstructExclusionListFromNamesAndIndicesMixed)
     Molecule             water = waterMolecule.waterMoleculeWithoutExclusions();
 
     //! Add the exclusions
-    water.addExclusion("H1", "Oxygen");
-    water.addExclusion("H2", "Oxygen");
+    water.addExclusion(ParticleName("H1"), ParticleName("Oxygen"));
+    water.addExclusion(ParticleName("H2"), ParticleName("Oxygen"));
     water.addExclusion(1, 2);
 
     std::vector<std::tuple<int, int>> exclusions = water.getExclusions();
index cf05e0ed6da28a28a7c2e77483a7c31558c0e62e..816a545dfb3067ecc7c9db643aed609818571c02 100644 (file)
@@ -156,9 +156,13 @@ TEST(NBlibTest, UpdateChangesForces)
     gmx::ArrayRef<Vec3> forces(simState.forces());
     forceCalculator.compute(simState.coordinates(), simState.forces());
 
+    // copy computed forces to another array
     std::vector<Vec3> forces_1(forces.size());
     std::copy(forces.begin(), forces.end(), begin(forces_1));
 
+    // zero original force buffer
+    zeroCartesianArray(forces);
+
     // check if forces change without update step
     forceCalculator.compute(simState.coordinates(), forces);
 
@@ -174,8 +178,11 @@ TEST(NBlibTest, UpdateChangesForces)
     // update
     integrator.integrate(1.0, simState.coordinates(), simState.velocities(), simState.forces());
 
+    // zero original force buffer
+    zeroCartesianArray(forces);
+
     // step 2
-    forceCalculator.compute(simState.coordinates(), simState.forces());
+    forceCalculator.compute(simState.coordinates(), forces);
     std::vector<Vec3> forces_2(forces.size());
     std::copy(forces.begin(), forces.end(), begin(forces_2));
 
index 70e86dd8135681f4be2d75a07a4d126906abb309..41045b08c42c45632bc06e365f7404b7b8ff45a9 100644 (file)
@@ -133,9 +133,9 @@ Molecule WaterMoleculeBuilder::waterMoleculeWithoutExclusions()
 
 void WaterMoleculeBuilder::addExclusionsFromNames()
 {
-    water_.addExclusion("H1", "Oxygen");
-    water_.addExclusion("H2", "Oxygen");
-    water_.addExclusion("H1", "H2");
+    water_.addExclusion(ParticleName("H1"), ParticleName("Oxygen"));
+    water_.addExclusion(ParticleName("H2"), ParticleName("Oxygen"));
+    water_.addExclusion(ParticleName("H1"), ParticleName("H2"));
 }
 
 MethanolMoleculeBuilder::MethanolMoleculeBuilder() : methanol_(MoleculeName("MeOH"))
@@ -148,9 +148,9 @@ MethanolMoleculeBuilder::MethanolMoleculeBuilder() : methanol_(MoleculeName("MeO
     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");
+    methanol_.addExclusion(ParticleName("Me1"), ParticleName("O2"));
+    methanol_.addExclusion(ParticleName("Me1"), ParticleName("H3"));
+    methanol_.addExclusion(ParticleName("H3"), ParticleName("O2"));
 }
 
 Molecule MethanolMoleculeBuilder::methanolMolecule()
index 4e4810765c55e2968b9abef596a9bbb2561f56d3..4e2e1e4d8c895a23dfe40d887b70b7ee4f74d7cc 100644 (file)
@@ -71,9 +71,10 @@ gmx::ListOfLists<int> TopologyBuilder::createExclusionsListOfLists() const
         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()));
+        // Note this is a programming error as all particles should exclude at least themselves and empty topologies are not allowed.
+        const std::string message =
+                "No exclusions found in the " + molecule.name().value() + " molecule.";
+        assert((!exclusions.empty() && message.c_str()));
 
         std::vector<gmx::ExclusionBlock> exclusionBlockPerMolecule =
                 detail::toGmxExclusionBlock(exclusions);
@@ -129,6 +130,11 @@ std::vector<T> TopologyBuilder::extractParticleTypeQuantity(Extractor&& extracto
 
 Topology TopologyBuilder::buildTopology()
 {
+    assert((!(numParticles_ < 0) && "It should not be possible to have negative particles"));
+    if (numParticles_ == 0)
+    {
+        throw InputException("You cannot build a topology with no particles");
+    }
     topology_.numParticles_ = numParticles_;
 
     topology_.exclusions_ = createExclusionsListOfLists();
index 68b109a7636ff819f6fcf57432ae608ca0d0465a..d2cdd63d66728f742a77b1ed5ef0601de7bd1052 100644 (file)
@@ -64,9 +64,12 @@ std::vector<gmx::ExclusionBlock> toGmxExclusionBlock(const std::vector<std::tupl
         return std::get<0>(tup1) < std::get<0>(tup2);
     };
 
+    // Note this is a programming error as all particles should exclude at least themselves and empty topologies are not allowed.
+    // Note also that this is also checked in the parent function with a more informative error message.
+    assert((!tupleList.empty() && "No exclusions found.\n"));
+
     // 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;
index 63a86586879c5bd65955c33aedb216d8ce9fc7ca..32b041a3e0ba1d6cc59bdb04bddb514b8a48b22c 100644 (file)
@@ -54,8 +54,6 @@
 #include <type_traits>
 #include <vector>
 
-#include "nblib/basicdefinitions.h"
-#include "nblib/vector.h"
 
 namespace nblib
 {
@@ -69,6 +67,9 @@ std::string next_token(std::string& s, const std::string& delimiter);
 template<auto...>
 using void_value_t = void;
 
+template<class... Tuples>
+using tuple_cat_t = decltype(std::tuple_cat(Tuples{}...));
+
 template<class T, class = void>
 struct HasValueMember : std::false_type
 {
@@ -94,8 +95,11 @@ struct AccessTypeMemberIfPresent<T, typename std::void_t<typename T::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]
+/*! \brief Comparison meta function that compares T to Tuple[N]
+ *
+ * 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>>,
@@ -103,48 +107,74 @@ struct MatchTypeOrTypeMember :
 {
 };
 
-//! 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>{}>{}>
+//! \brief Recursion to check the next field N+1
+template<int N, class T, class Tuple, template<int, class, class> class Comparison, class Match = void>
+struct MatchField_ : std::integral_constant<size_t, MatchField_<N + 1, T, Tuple, Comparison>{}>
 {
 };
 
-//! recursion stop when Comparison<N, T, Tuple>::value is true
+//! \brief 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>
+struct MatchField_<N, T, Tuple, Comparison, std::enable_if_t<Comparison<N, T, Tuple>{}>> :
+    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
+
+/*! \brief Meta function to return the first 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, the size of Tuple will be returned.
+ *  Note that the default comparison operation supplied here also matches if the type member Tuple[N]::type matches T
  */
-template<class T, class Tuple, template<int, class, class> class Comparison>
-struct MatchField : detail::MatchField_<0, T, Tuple, Comparison, Comparison<0, T, Tuple>{}>
+template<typename T, class TL, template<int, class, class> class Comparison = detail::MatchTypeOrTypeMember>
+struct FindIndex
 {
 };
 
-/*! \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
+/*! \brief Specialization to only enable this trait if TL has template parameters
+ *
+ * \tparam T          a type to look for in the template parameters of TL
+ * \tparam TL         a template template parameter, e.g. std::tuple or nblib::TypeList
+ * \tparam Ts         template parameters of TL
+ * \tparam Comparison comparison operation
+ *
+ *  Note that \a T is added to \a TL as a sentinel to terminate the recursion
+ *  and prevent an out of bounds tuple access compiler error.
  */
-template<typename T, typename Tuple, template<int, class, class> class Comparison = detail::MatchTypeOrTypeMember>
-struct FindIndex : std::integral_constant<size_t, MatchField<T, Tuple, Comparison>{}>
+template<typename T, template<class...> class TL, class... Ts, template<int, class, class> class Comparison>
+struct FindIndex<T, TL<Ts...>, Comparison> : detail::MatchField_<0, T, std::tuple<Ts..., T>, 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
+/*! \brief Meta function to return the element in Tuple whose type matches T
+ *
+ * If there are more than one, the first occurrence will be returned
+ * If there is no such that, a compiler error is generated due to accessing
+ * the tuple out of bounds
+ */
 template<typename T, typename Tuple>
 decltype(auto) pickType(Tuple& tup)
 {
-    return std::get<FindIndex<T, Tuple>{}>(tup);
+    return std::get<FindIndex<T, std::decay_t<Tuple>>{}>(tup);
 }
 
+//! \brief template meta function to determine whether T is contained in TL
+template<class T, class TL>
+struct Contains
+{
+};
+
+//! this formatting must be a bug in clang-format... should be:
+// struct Contains<T, TL<Ts...>> : std::bool_constant<FindIndex<T, TL<Ts...>>{} < sizeof...(Ts)>
+template<class T, template<class...> class TL, class... Ts>
+        struct Contains<T, TL<Ts...>> : std::bool_constant < FindIndex<T, TL<Ts...>>{}<sizeof...(Ts)>
+{
+};
+
+
 //! Utility to call function with each element in tuple_
 template<class F, class... Ts>
 void for_each_tuple(F&& func, std::tuple<Ts...>& tuple_)
index 4169b078f86c2bfac83cded716f6989b7140bd42..2cc78abdb9a0352529a57e0e092ab5c1c86bb339 100644 (file)
@@ -44,6 +44,7 @@ set(exename "nblib-util-test")
 gmx_add_gtest_executable(
         ${exename}
         CPP_SOURCE_FILES
+        internal.cpp
         user.cpp
 )
 target_link_libraries(${exename} PRIVATE nblib_test_infrastructure nblib)
diff --git a/api/nblib/util/tests/internal.cpp b/api/nblib/util/tests/internal.cpp
new file mode 100644 (file)
index 0000000..c8257ed
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * 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 "nblib/tests/testhelpers.h"
+#include "nblib/util/internal.h"
+#include "nblib/util/user.h"
+
+namespace nblib
+{
+
+TEST(NblibInternalUtils, FindIndexTuple1)
+{
+    using TupleType = std::tuple<float>;
+
+    constexpr int floatIndex = FindIndex<float, TupleType>{};
+
+    constexpr int outOfRange = FindIndex<unsigned, TupleType>{};
+
+    EXPECT_EQ(0, floatIndex);
+    EXPECT_EQ(1, outOfRange);
+}
+
+TEST(NblibInternalUtils, FindIndexTuple2)
+{
+    using TupleType = std::tuple<float, int>;
+
+    constexpr int floatIndex = FindIndex<float, TupleType>{};
+    constexpr int intIndex   = FindIndex<int, TupleType>{};
+
+    constexpr int outOfRange = FindIndex<unsigned, TupleType>{};
+
+    EXPECT_EQ(0, floatIndex);
+    EXPECT_EQ(1, intIndex);
+    EXPECT_EQ(2, outOfRange);
+}
+
+TEST(NblibInternalUtils, FindIndexTypeList1)
+{
+    using ListType = TypeList<float>;
+
+    constexpr int floatIndex = FindIndex<float, ListType>{};
+
+    constexpr int outOfRange = FindIndex<unsigned, ListType>{};
+
+    EXPECT_EQ(0, floatIndex);
+    EXPECT_EQ(1, outOfRange);
+}
+
+TEST(NblibInternalUtils, FindIndexTypeList2)
+{
+    using ListType = TypeList<float, int>;
+
+    constexpr int floatIndex = FindIndex<float, ListType>{};
+    constexpr int intIndex   = FindIndex<int, ListType>{};
+
+    constexpr int outOfRange = FindIndex<unsigned, ListType>{};
+
+    EXPECT_EQ(0, floatIndex);
+    EXPECT_EQ(1, intIndex);
+    EXPECT_EQ(2, outOfRange);
+}
+
+
+TEST(NblibInternalUtils, Contains)
+{
+    using ListType = TypeList<float, int>;
+
+    constexpr bool hasFloat = Contains<float, ListType>{};
+    constexpr bool hasInt   = Contains<int, ListType>{};
+    constexpr bool hasUint  = Contains<unsigned, ListType>{};
+
+    EXPECT_TRUE(hasFloat);
+    EXPECT_TRUE(hasInt);
+    EXPECT_FALSE(hasUint);
+}
+
+TEST(NblibInternalUtils, FindIndexTupleRepeated)
+{
+    using TupleType = std::tuple<float, float, int>;
+
+    constexpr int floatIndex = FindIndex<float, TupleType>{};
+
+    constexpr int intIndex = FindIndex<int, TupleType>{};
+
+    constexpr int outOfRange = FindIndex<unsigned, TupleType>{};
+
+    EXPECT_EQ(0, floatIndex);
+    EXPECT_EQ(2, intIndex);
+    EXPECT_EQ(3, outOfRange);
+}
+
+TEST(NblibInternalUtils, FindIndexTypeListRepeated)
+{
+    using TupleType = TypeList<float, float, int>;
+
+    constexpr int floatIndex = FindIndex<float, TupleType>{};
+
+    constexpr int intIndex = FindIndex<int, TupleType>{};
+
+    constexpr int outOfRange = FindIndex<unsigned, TupleType>{};
+
+    EXPECT_EQ(0, floatIndex);
+    EXPECT_EQ(2, intIndex);
+    EXPECT_EQ(3, outOfRange);
+}
+
+
+} // namespace nblib
index 0fccd6ec655f3296b393ae941ba82167c05075a4..b44f76f08da087c847b211c7eda84a13d80b7d89 100644 (file)
@@ -56,13 +56,13 @@ namespace test
 namespace
 {
 
-TEST(NBlibTest, checkNumericValues)
+TEST(NBlibTest, isRealValued)
 {
     std::vector<Vec3> vec;
     vec.emplace_back(1., 1., 1.);
     vec.emplace_back(2., 2., 2.);
 
-    bool ret = checkNumericValues(vec);
+    bool ret = isRealValued(vec);
     EXPECT_EQ(ret, true);
 }
 
@@ -74,7 +74,7 @@ TEST(NBlibTest, checkNumericValuesHasNan)
 
     vec.emplace_back(NAN, NAN, NAN);
 
-    bool ret = checkNumericValues(vec);
+    bool ret = isRealValued(vec);
     EXPECT_EQ(ret, false);
 }
 
@@ -86,7 +86,7 @@ TEST(NBlibTest, checkNumericValuesHasInf)
 
     vec.emplace_back(INFINITY, INFINITY, INFINITY);
 
-    bool ret = checkNumericValues(vec);
+    bool ret = isRealValued(vec);
     EXPECT_EQ(ret, false);
 }
 
@@ -114,7 +114,7 @@ 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);
+    bool              ret = isRealValued(out);
     EXPECT_EQ(ret, true);
 }
 
index 5090441b6857efea97bf9a526209f8251343b0d2..0019143f78494a1300a53bea1294f4e4f366c0b2 100644 (file)
@@ -47,6 +47,7 @@
 #include "nblib/util/user.h"
 #include "gromacs/random/tabulatednormaldistribution.h"
 #include "gromacs/random/threefry.h"
+#include "gromacs/utility/arrayref.h"
 #include "gromacs/utility/fatalerror.h"
 
 namespace nblib
@@ -139,7 +140,7 @@ std::vector<Vec3> generateVelocity(real tempi, unsigned int seed, std::vector<re
 }
 
 //! Check within the container of gmx::RVecs for a NaN or inf
-bool checkNumericValues(const std::vector<Vec3>& values)
+bool isRealValued(gmx::ArrayRef<const Vec3> values)
 {
     for (auto val : values)
     {
@@ -154,4 +155,9 @@ bool checkNumericValues(const std::vector<Vec3>& values)
     return true;
 }
 
+void zeroCartesianArray(gmx::ArrayRef<Vec3> cartesianArray)
+{
+    std::fill(cartesianArray.begin(), cartesianArray.end(), Vec3{ 0, 0, 0 });
+}
+
 } // namespace nblib
index 9d19189c4e8d2a7d55baf71a791196a8467c693a..6d782fb6bd99c62af25dc9c71f56ccd846f7e77a 100644 (file)
 #include "nblib/basicdefinitions.h"
 #include "nblib/vector.h"
 
+namespace gmx
+{
+template<typename T>
+class ArrayRef;
+} // namespace gmx
+
 namespace nblib
 {
 
@@ -65,7 +71,10 @@ namespace nblib
 std::vector<Vec3> generateVelocity(real Temperature, unsigned int seed, std::vector<real> const& masses);
 
 //! Check within the container of gmx::RVecs for a NaN or inf
-bool checkNumericValues(const std::vector<Vec3>& values);
+bool isRealValued(gmx::ArrayRef<const Vec3> values);
+
+//! Zero a cartesian buffer
+void zeroCartesianArray(gmx::ArrayRef<Vec3> cartesianArray);
 
 //! Used to ignore unused arguments of a lambda functions
 inline void ignore_unused() {}