Remove unused thole polarization rfac parameter
[alexxy/gromacs.git] / src / gromacs / listed_forces / tests / bonded.cpp
index 54515941f8b33cda1448c3890f32c51c00068f98..04bf2e1eb7a9adf4951edb18783b09fd838871c4 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2018,2019, by the GROMACS development team, led by
+ * Copyright (c) 2018,2019,2020,2021, 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.
  * \brief
  * Implements test of bonded force routines
  *
+ *
+ * \todo We should re-work this. For example, a harmonic bond
+ * has so few computations that force components should be
+ * accurate to a small *and computed* relative error.
+ *
  * \author David van der Spoel <david.vanderspoel@icm.uu.se>
  * \ingroup module_listed_forces
  */
@@ -51,6 +56,7 @@
 #include <gtest/gtest.h>
 
 #include "gromacs/listed_forces/listed_forces.h"
+#include "gromacs/math/paddedvector.h"
 #include "gromacs/math/units.h"
 #include "gromacs/math/vec.h"
 #include "gromacs/math/vectypes.h"
 #include "gromacs/pbcutil/ishift.h"
 #include "gromacs/pbcutil/pbc.h"
 #include "gromacs/topology/idef.h"
+#include "gromacs/utility/enumerationhelpers.h"
 #include "gromacs/utility/strconvert.h"
+#include "gromacs/utility/stringstream.h"
+#include "gromacs/utility/textwriter.h"
 
 #include "testutils/refdata.h"
 #include "testutils/testasserts.h"
 
 namespace gmx
 {
+namespace test
+{
 namespace
 {
 
@@ -81,22 +92,27 @@ struct OutputQuantities
     //! Derivative with respect to lambda
     real dvdlambda = 0;
     //! Shift vectors
-    rvec fshift[N_IVEC] = { { 0 } };
+    rvec fshift[c_numShiftVectors] = { { 0 } };
     //! Forces
-    rvec4 f[c_numAtoms] = { { 0 } };
+    alignas(GMX_REAL_MAX_SIMD_WIDTH * sizeof(real)) rvec4 f[c_numAtoms] = { { 0 } };
 };
 
 /*! \brief Utility to check the output from bonded tests
  *
  * \param[in] checker Reference checker
  * \param[in] output  The output from the test to check
+ * \param[in] bondedKernelFlavor  Flavor for determining what output to check
  */
-void checkOutput(test::TestReferenceChecker* checker, const OutputQuantities& output)
+void checkOutput(TestReferenceChecker*    checker,
+                 const OutputQuantities&  output,
+                 const BondedKernelFlavor bondedKernelFlavor)
 {
-    checker->checkReal(output.energy, "Epot ");
-    // Should still be zero if not doing FEP, so may as well test it.
-    checker->checkReal(output.dvdlambda, "dVdlambda ");
-    checker->checkVector(output.fshift[CENTRAL], "Central shift forces");
+    if (computeEnergy(bondedKernelFlavor))
+    {
+        checker->checkReal(output.energy, "Epot ");
+        // Should still be zero when not doing FEP, so may as well test it.
+        checker->checkReal(output.dvdlambda, "dVdlambda ");
+    }
     checker->checkSequence(std::begin(output.f), std::end(output.f), "Forces");
 }
 
@@ -116,6 +132,8 @@ public:
     //! Interaction parameters
     t_iparams iparams = { { 0 } };
 
+    friend std::ostream& operator<<(std::ostream& out, const iListInput& input);
+
     //! Constructor
     iListInput() {}
 
@@ -433,17 +451,15 @@ public:
      * \param[in] a      Thole factor
      * \param[in] alpha1 Polarizability 1 (nm^3)
      * \param[in] alpha2 Polarizability 2 (nm^3)
-     * \param[in] rfac   Distance factor
      * \return The structure itself.
      */
-    iListInput setTholePolarization(real a, real alpha1, real alpha2, real rfac)
+    iListInput setTholePolarization(real a, real alpha1, real alpha2)
     {
         ftype                = F_THOLE_POL;
         fep                  = false;
         iparams.thole.a      = a;
         iparams.thole.alpha1 = alpha1;
         iparams.thole.alpha2 = alpha2;
-        iparams.thole.rfac   = rfac;
         return *this;
     }
     /*! \brief Set parameters for Water Polarization
@@ -470,6 +486,26 @@ public:
     }
 };
 
+//! Prints the interaction and parameters to a stream
+std::ostream& operator<<(std::ostream& out, const iListInput& input)
+{
+    using std::endl;
+    out << "Function type " << input.ftype << " called " << interaction_function[input.ftype].name
+        << " ie. labelled '" << interaction_function[input.ftype].longname << "' in an energy file"
+        << endl;
+
+    // Organize to print the legacy C union t_iparams, whose
+    // relevant contents vary with ftype.
+    StringOutputStream stream;
+    {
+        TextWriter writer(&stream);
+        printInteractionParameters(&writer, input.ftype, input.iparams);
+    }
+    out << "Function parameters " << stream.toString();
+    out << "Parameters trigger FEP? " << (input.fep ? "true" : "false") << endl;
+    return out;
+}
+
 /*! \brief Utility to fill iatoms struct
  *
  * \param[in]  ftype  Function type
@@ -490,52 +526,107 @@ void fillIatoms(int ftype, std::vector<t_iatom>* iatoms)
 }
 
 class ListedForcesTest :
-    public ::testing::TestWithParam<std::tuple<iListInput, std::vector<gmx::RVec>, int>>
+    public ::testing::TestWithParam<std::tuple<iListInput, PaddedVector<RVec>, PbcType>>
 {
 protected:
-    matrix                     box_;
-    t_pbc                      pbc_;
-    std::vector<gmx::RVec>     x_;
-    int                        epbc_;
-    iListInput                 input_;
-    test::TestReferenceData    refData_;
-    test::TestReferenceChecker checker_;
+    matrix                 box_;
+    t_pbc                  pbc_;
+    PaddedVector<RVec>     x_;
+    PbcType                pbcType_;
+    iListInput             input_;
+    TestReferenceData      refData_;
+    TestReferenceChecker   checker_;
+    FloatingPointTolerance shiftForcesTolerance_ = defaultRealTolerance();
     ListedForcesTest() : checker_(refData_.rootChecker())
     {
-        input_ = std::get<0>(GetParam());
-        x_     = std::get<1>(GetParam());
-        epbc_  = std::get<2>(GetParam());
+        input_   = std::get<0>(GetParam());
+        x_       = std::get<1>(GetParam());
+        pbcType_ = std::get<2>(GetParam());
         clear_mat(box_);
         box_[0][0] = box_[1][1] = box_[2][2] = 1.5;
-        set_pbc(&pbc_, epbc_, box_);
+        set_pbc(&pbc_, pbcType_, box_);
         // We need quite specific tolerances here since angle functions
         // etc. are not very precise and reproducible.
         test::FloatingPointTolerance tolerance(test::FloatingPointTolerance(
-                input_.ftoler, 1.0e-6, input_.dtoler, 1.0e-12, 10000, 100, false));
+                input_.ftoler, input_.dtoler, 1.0e-6, 1.0e-12, 10000, 100, false));
         checker_.setDefaultTolerance(tolerance);
+        // The SIMD acos() is only accurate to 2-3 ULP, so the angles
+        // computed by it and the non-SIMD code paths (that use
+        // std::acos) differ by enough to require quite large
+        // tolerances for the shift forces in mixed precision.
+        float singleShiftForcesAbsoluteTolerance =
+                ((input_.ftype == F_POLARIZATION) || (input_.ftype == F_ANHARM_POL)
+                                 || (IS_ANGLE(input_.ftype))
+                         ? 5e-3
+                         : 5e-5);
+        // Note that std::numeric_limits isn't required by the standard to
+        // have an implementation for uint64_t(!) but this is likely to
+        // work because that type is likely to be a typedef for one of
+        // the other numerical types that happens to be 64-bits wide.
+        shiftForcesTolerance_ = FloatingPointTolerance(singleShiftForcesAbsoluteTolerance,
+                                                       1e-8,
+                                                       1e-6,
+                                                       1e-12,
+                                                       std::numeric_limits<uint64_t>::max(),
+                                                       std::numeric_limits<uint64_t>::max(),
+                                                       false);
     }
-    void testOneIfunc(test::TestReferenceChecker* checker, const std::vector<t_iatom>& iatoms, const real lambda)
+    void testOneIfunc(TestReferenceChecker* checker, const std::vector<t_iatom>& iatoms, const real lambda)
     {
-        SCOPED_TRACE(std::string("Testing PBC ") + epbc_names[epbc_]);
+        SCOPED_TRACE(std::string("Testing PBC type: ") + c_pbcTypeNames[pbcType_]);
         std::vector<int>  ddgatindex = { 0, 1, 2, 3 };
-        std::vector<real> chargeA    = { 1.5, -2.0, 1.5, -1.0 };
-        t_mdatoms         mdatoms    = { 0 };
-        mdatoms.chargeA              = chargeA.data();
-        OutputQuantities output;
-        output.energy = calculateSimpleBond(
-                input_.ftype, iatoms.size(), iatoms.data(), &input_.iparams,
-                as_rvec_array(x_.data()), output.f, output.fshift, &pbc_,
-                /* const struct t_graph *g */ nullptr, lambda, &output.dvdlambda, &mdatoms,
-                /* struct t_fcdata * */ nullptr, ddgatindex.data(),
-                BondedKernelFlavor::ForcesAndVirialAndEnergy);
-        // Internal consistency test of both test input
-        // and bonded functions.
-        EXPECT_TRUE((input_.fep || (output.dvdlambda == 0.0)));
-        checkOutput(checker, output);
+        std::vector<real> charge     = { 1.5, -2.0, 1.5, -1.0 };
+        /* Here we run both the standard, plain-C force+shift-forces+energy+free-energy
+         * kernel flavor and the potentially optimized, with SIMD and less output,
+         * force only kernels. Note that we also run the optimized kernel for free-energy
+         * input when lambda=0, as the force output should match the non free-energy case.
+         */
+        std::vector<BondedKernelFlavor> flavors = { BondedKernelFlavor::ForcesAndVirialAndEnergy };
+        if (!input_.fep || lambda == 0)
+        {
+            flavors.push_back(BondedKernelFlavor::ForcesSimdWhenAvailable);
+        }
+        for (const auto flavor : flavors)
+        {
+            SCOPED_TRACE("Testing bonded kernel flavor: " + c_bondedKernelFlavorStrings[flavor]);
+            OutputQuantities output;
+            output.energy = calculateSimpleBond(input_.ftype,
+                                                iatoms.size(),
+                                                iatoms.data(),
+                                                &input_.iparams,
+                                                as_rvec_array(x_.data()),
+                                                output.f,
+                                                output.fshift,
+                                                &pbc_,
+                                                lambda,
+                                                &output.dvdlambda,
+                                                charge,
+                                                /* struct t_fcdata * */ nullptr,
+                                                nullptr,
+                                                nullptr,
+                                                ddgatindex.data(),
+                                                flavor);
+            // Internal consistency test of both test input
+            // and bonded functions.
+            EXPECT_TRUE((input_.fep || (output.dvdlambda == 0.0))) << "dvdlambda was " << output.dvdlambda;
+            checkOutput(checker, output, flavor);
+            auto shiftForcesChecker = checker->checkCompound("Shift-Forces", "Shift-forces");
+            if (computeVirial(flavor))
+            {
+                shiftForcesChecker.setDefaultTolerance(shiftForcesTolerance_);
+                shiftForcesChecker.checkVector(output.fshift[c_centralShiftIndex], "Central");
+            }
+            else
+            {
+                // Permit omitting to compare shift forces with
+                // reference data when that is useless.
+                shiftForcesChecker.disableUnusedEntriesCheck();
+            }
+        }
     }
     void testIfunc()
     {
-        test::TestReferenceChecker thisChecker =
+        TestReferenceChecker thisChecker =
                 checker_.checkCompound("FunctionType", interaction_function[input_.ftype].name)
                         .checkCompound("FEP", (input_.fep ? "Yes" : "No"));
         std::vector<t_iatom> iatoms;
@@ -564,7 +655,7 @@ TEST_P(ListedForcesTest, Ifunc)
 
 //! Function types for testing bonds. Add new terms at the end.
 std::vector<iListInput> c_InputBonds = {
-    { iListInput().setHarmonic(F_BONDS, 0.15, 500.0) },
+    { iListInput(2e-6F, 1e-8).setHarmonic(F_BONDS, 0.15, 500.0) },
     { iListInput(2e-6F, 1e-8).setHarmonic(F_BONDS, 0.15, 500.0, 0.17, 400.0) },
     { iListInput(1e-4F, 1e-8).setHarmonic(F_G96BONDS, 0.15, 50.0) },
     { iListInput().setHarmonic(F_G96BONDS, 0.15, 50.0, 0.17, 40.0) },
@@ -615,7 +706,7 @@ std::vector<iListInput> c_InputDihs = {
 std::vector<iListInput> c_InputPols = {
     { iListInput(2e-5, 1e-8).setPolarization(0.12) },
     { iListInput(2e-3, 1e-8).setAnharmPolarization(0.0013, 0.02, 1235.6) },
-    { iListInput(1.4e-3, 1e-8).setTholePolarization(0.26, 0.07, 0.09, 1.6) },
+    { iListInput(1.4e-3, 1e-8).setTholePolarization(0.26, 0.07, 0.09) },
     { iListInput(2e-3, 1e-8).setWaterPolarization(0.001, 0.0012, 0.0016, 0.095, 0.15, 0.02) },
 };
 
@@ -629,48 +720,111 @@ std::vector<iListInput> c_InputRestraints = {
     { iListInput(2e-3, 1e-8).setHarmonic(F_RESTRANGLES, 100.0, 50.0, 110.0, 45.0) }
 };
 
-//! Coordinates for testing
-std::vector<std::vector<gmx::RVec>> c_coordinatesForTests = {
-    { { 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.2 }, { 0.005, 0.0, 0.1 }, { -0.001, 0.1, 0.0 } },
-    { { 0.5, 0.0, 0.0 }, { 0.5, 0.0, 0.15 }, { 0.5, 0.07, 0.22 }, { 0.5, 0.18, 0.22 } },
-    { { -0.1143, -0.0282, 0.0 }, { 0.0, 0.0434, 0.0 }, { 0.1185, -0.0138, 0.0 }, { -0.0195, 0.1498, 0.0 } }
+//! Function types for testing bond with zero length, has zero reference length to make physical sense.
+std::vector<iListInput> c_InputBondsZeroLength = {
+    { iListInput().setHarmonic(F_BONDS, 0.0, 500.0) },
+};
+
+//! Function types for testing angles with zero angle, has zero reference angle to make physical sense.
+std::vector<iListInput> c_InputAnglesZeroAngle = {
+    { iListInput(2e-3, 1e-8).setHarmonic(F_ANGLES, 0.0, 50.0) },
+};
+
+} // namespace
+} // namespace test
+
+//! Print an RVec to \c os
+static void PrintTo(const RVec& value, std::ostream* os)
+{
+    *os << value[XX] << " " << value[YY] << " " << value[ZZ] << std::endl;
+}
+
+//! Print a padded vector of RVec to \c os
+static void PrintTo(const PaddedVector<RVec>& vector, std::ostream* os)
+{
+    if (vector.empty())
+    {
+        *os << "Empty vector" << std::endl;
+    }
+    else
+    {
+        *os << "Vector of RVec containing:" << std::endl;
+        std::for_each(vector.begin(), vector.end(), [os](const RVec& v) { PrintTo(v, os); });
+    }
+}
+
+namespace test
+{
+namespace
+{
+
+/*! \brief Coordinates for testing
+ *
+ * Taken from a butane molecule, so we have some
+ * normal-sized bonds and angles to test.
+ *
+ * \todo Test also some weirder values */
+std::vector<PaddedVector<RVec>> c_coordinatesForTests = {
+    { { 1.382, 1.573, 1.482 }, { 1.281, 1.559, 1.596 }, { 1.292, 1.422, 1.663 }, { 1.189, 1.407, 1.775 } }
+};
+
+//! Coordinates for testing bonds with zero length
+std::vector<PaddedVector<RVec>> c_coordinatesForTestsZeroBondLength = {
+    { { 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 }, { 0.005, 0.0, 0.1 }, { 0.005, 0.0, 0.1 } }
+};
+
+//! Coordinates for testing bonds with zero length
+std::vector<PaddedVector<RVec>> c_coordinatesForTestsZeroAngle = {
+    { { 0.005, 0.0, 0.1 }, { 0.0, 0.0, 0.0 }, { 0.005, 0.0, 0.1 }, { 0.5, 0.18, 0.22 } }
 };
 
 //! PBC values for testing
-std::vector<int> c_pbcForTests = { epbcNONE, epbcXY, epbcXYZ };
-
-// Those tests give errors with the intel compiler and nothing else, so we disable them only there.
-#ifndef __INTEL_COMPILER
-INSTANTIATE_TEST_CASE_P(Bond,
-                        ListedForcesTest,
-                        ::testing::Combine(::testing::ValuesIn(c_InputBonds),
-                                           ::testing::ValuesIn(c_coordinatesForTests),
-                                           ::testing::ValuesIn(c_pbcForTests)));
-
-INSTANTIATE_TEST_CASE_P(Angle,
-                        ListedForcesTest,
-                        ::testing::Combine(::testing::ValuesIn(c_InputAngles),
-                                           ::testing::ValuesIn(c_coordinatesForTests),
-                                           ::testing::ValuesIn(c_pbcForTests)));
-
-INSTANTIATE_TEST_CASE_P(Dihedral,
-                        ListedForcesTest,
-                        ::testing::Combine(::testing::ValuesIn(c_InputDihs),
-                                           ::testing::ValuesIn(c_coordinatesForTests),
-                                           ::testing::ValuesIn(c_pbcForTests)));
-
-INSTANTIATE_TEST_CASE_P(Polarize,
-                        ListedForcesTest,
-                        ::testing::Combine(::testing::ValuesIn(c_InputPols),
-                                           ::testing::ValuesIn(c_coordinatesForTests),
-                                           ::testing::ValuesIn(c_pbcForTests)));
-
-INSTANTIATE_TEST_CASE_P(Restraints,
-                        ListedForcesTest,
-                        ::testing::Combine(::testing::ValuesIn(c_InputRestraints),
-                                           ::testing::ValuesIn(c_coordinatesForTests),
-                                           ::testing::ValuesIn(c_pbcForTests)));
-#endif
+std::vector<PbcType> c_pbcForTests = { PbcType::No, PbcType::XY, PbcType::Xyz };
+
+INSTANTIATE_TEST_SUITE_P(Bond,
+                         ListedForcesTest,
+                         ::testing::Combine(::testing::ValuesIn(c_InputBonds),
+                                            ::testing::ValuesIn(c_coordinatesForTests),
+                                            ::testing::ValuesIn(c_pbcForTests)));
+
+INSTANTIATE_TEST_SUITE_P(Angle,
+                         ListedForcesTest,
+                         ::testing::Combine(::testing::ValuesIn(c_InputAngles),
+                                            ::testing::ValuesIn(c_coordinatesForTests),
+                                            ::testing::ValuesIn(c_pbcForTests)));
+
+INSTANTIATE_TEST_SUITE_P(Dihedral,
+                         ListedForcesTest,
+                         ::testing::Combine(::testing::ValuesIn(c_InputDihs),
+                                            ::testing::ValuesIn(c_coordinatesForTests),
+                                            ::testing::ValuesIn(c_pbcForTests)));
+
+INSTANTIATE_TEST_SUITE_P(Polarize,
+                         ListedForcesTest,
+                         ::testing::Combine(::testing::ValuesIn(c_InputPols),
+                                            ::testing::ValuesIn(c_coordinatesForTests),
+                                            ::testing::ValuesIn(c_pbcForTests)));
+
+INSTANTIATE_TEST_SUITE_P(Restraints,
+                         ListedForcesTest,
+                         ::testing::Combine(::testing::ValuesIn(c_InputRestraints),
+                                            ::testing::ValuesIn(c_coordinatesForTests),
+                                            ::testing::ValuesIn(c_pbcForTests)));
+
+INSTANTIATE_TEST_SUITE_P(BondZeroLength,
+                         ListedForcesTest,
+                         ::testing::Combine(::testing::ValuesIn(c_InputBondsZeroLength),
+                                            ::testing::ValuesIn(c_coordinatesForTestsZeroBondLength),
+                                            ::testing::ValuesIn(c_pbcForTests)));
+
+INSTANTIATE_TEST_SUITE_P(AngleZero,
+                         ListedForcesTest,
+                         ::testing::Combine(::testing::ValuesIn(c_InputAnglesZeroAngle),
+                                            ::testing::ValuesIn(c_coordinatesForTestsZeroAngle),
+                                            ::testing::ValuesIn(c_pbcForTests)));
+
 } // namespace
 
+} // namespace test
+
 } // namespace gmx