Add backend for nblib listed forces API
[alexxy/gromacs.git] / api / nblib / topology.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2020, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements nblib Topology and TopologyBuilder
38  *
39  * \author Victor Holanda <victor.holanda@cscs.ch>
40  * \author Joe Jordan <ejjordan@kth.se>
41  * \author Prashanth Kanduri <kanduri@cscs.ch>
42  * \author Sebastian Keller <keller@cscs.ch>
43  * \author Artem Zhmurov <zhmurov@gmail.com>
44  */
45 #include <numeric>
46
47 #include "gromacs/topology/exclusionblocks.h"
48 #include "gromacs/utility/listoflists.h"
49 #include "gromacs/utility/smalloc.h"
50 #include "nblib/exception.h"
51 #include "nblib/particletype.h"
52 #include "nblib/topology.h"
53 #include "nblib/util/internal.h"
54
55 namespace nblib
56 {
57
58 TopologyBuilder::TopologyBuilder() : numParticles_(0) {}
59
60 gmx::ListOfLists<int> TopologyBuilder::createExclusionsListOfLists() const
61 {
62     const auto& moleculesList = molecules_;
63
64     std::vector<gmx::ExclusionBlock> exclusionBlockGlobal;
65     exclusionBlockGlobal.reserve(numParticles_);
66
67     size_t particleNumberOffset = 0;
68     for (const auto& molNumberTuple : moleculesList)
69     {
70         const Molecule& molecule   = std::get<0>(molNumberTuple);
71         size_t          numMols    = std::get<1>(molNumberTuple);
72         const auto&     exclusions = molecule.getExclusions();
73
74         // Note this is a programming error as all particles should exclude at least themselves and empty topologies are not allowed.
75         const std::string message =
76                 "No exclusions found in the " + molecule.name().value() + " molecule.";
77         assert((!exclusions.empty() && message.c_str()));
78
79         std::vector<gmx::ExclusionBlock> exclusionBlockPerMolecule =
80                 detail::toGmxExclusionBlock(exclusions);
81
82         // duplicate the exclusionBlockPerMolecule for the number of Molecules of (numMols)
83         for (size_t i = 0; i < numMols; ++i)
84         {
85             auto offsetExclusions =
86                     detail::offsetGmxBlock(exclusionBlockPerMolecule, particleNumberOffset);
87
88             std::copy(std::begin(offsetExclusions), std::end(offsetExclusions),
89                       std::back_inserter(exclusionBlockGlobal));
90
91             particleNumberOffset += molecule.numParticlesInMolecule();
92         }
93     }
94
95     gmx::ListOfLists<int> exclusionsListOfListsGlobal;
96     for (const auto& block : exclusionBlockGlobal)
97     {
98         exclusionsListOfListsGlobal.pushBack(block.atomNumber);
99     }
100
101     return exclusionsListOfListsGlobal;
102 }
103
104 ListedInteractionData TopologyBuilder::createInteractionData(const detail::ParticleSequencer& particleSequencer)
105 {
106     ListedInteractionData interactionData;
107
108     // this code is doing the compile time equivalent of
109     // for (int i = 0; i < interactionData.size(); ++i)
110     //     create(get<i>(interactionData));
111
112     auto create = [this, &particleSequencer](auto& interactionDataElement) {
113         using InteractionType = typename std::decay_t<decltype(interactionDataElement)>::type;
114
115         // first compression stage: each bond per molecule listed once,
116         // eliminates duplicates from multiple identical molecules
117         auto  compressedDataStage1 = detail::collectInteractions<InteractionType>(this->molecules_);
118         auto& expansionArrayStage1 = std::get<0>(compressedDataStage1);
119         auto& moleculeInteractions = std::get<1>(compressedDataStage1);
120
121         // second compression stage: recognize bond duplicates among bonds from all molecules put together
122         auto  compressedDataStage2 = detail::eliminateDuplicateInteractions(moleculeInteractions);
123         auto& expansionArrayStage2 = std::get<0>(compressedDataStage2);
124         auto& uniqueInteractionInstances = std::get<1>(compressedDataStage2);
125
126         // combine stage 1 + 2 expansion arrays
127         std::vector<size_t> expansionArray(expansionArrayStage1.size());
128         std::transform(begin(expansionArrayStage1), end(expansionArrayStage1), begin(expansionArray),
129                        [& S2 = expansionArrayStage2](size_t S1Element) { return S2[S1Element]; });
130
131         // add data about InteractionType instances
132         interactionDataElement.parameters = std::move(uniqueInteractionInstances);
133
134         interactionDataElement.indices.resize(expansionArray.size());
135         // coordinateIndices contains the particle sequence IDs of all interaction coordinates of type <BondType>
136         auto coordinateIndices = detail::sequenceIDs<InteractionType>(this->molecules_, particleSequencer);
137         // zip coordinateIndices(i,j,...) + expansionArray(k) -> interactionDataElement.indices(i,j,...,k)
138         std::transform(begin(coordinateIndices), end(coordinateIndices), begin(expansionArray),
139                        begin(interactionDataElement.indices),
140                        [](auto coordinateIndex, auto interactionIndex) {
141                            std::array<int, coordinateIndex.size() + 1> ret;
142                            for (int i = 0; i < int(coordinateIndex.size()); ++i)
143                            {
144                                ret[i] = coordinateIndex[i];
145                            }
146                            ret[coordinateIndex.size()] = interactionIndex;
147                            return ret;
148                            // return std::tuple_cat(coordinateIndex, std::make_tuple(interactionIndex));
149                        });
150     };
151
152     for_each_tuple(create, interactionData);
153
154     return interactionData;
155 }
156
157 template<typename T, class Extractor>
158 std::vector<T> TopologyBuilder::extractParticleTypeQuantity(Extractor&& extractor)
159 {
160     auto& moleculesList = molecules_;
161
162     // returned object
163     std::vector<T> ret;
164     ret.reserve(numParticles_);
165
166     for (auto& molNumberTuple : moleculesList)
167     {
168         Molecule& molecule = std::get<0>(molNumberTuple);
169         size_t    numMols  = std::get<1>(molNumberTuple);
170
171         for (size_t i = 0; i < numMols; ++i)
172         {
173             for (auto& particleData : molecule.particleData())
174             {
175                 auto particleTypesMap = molecule.particleTypesMap();
176                 ret.push_back(extractor(particleData, particleTypesMap));
177             }
178         }
179     }
180
181     return ret;
182 }
183
184 Topology TopologyBuilder::buildTopology()
185 {
186     assert((!(numParticles_ < 0) && "It should not be possible to have negative particles"));
187     if (numParticles_ == 0)
188     {
189         throw InputException("You cannot build a topology with no particles");
190     }
191     topology_.numParticles_ = numParticles_;
192
193     topology_.exclusions_ = createExclusionsListOfLists();
194     topology_.charges_    = extractParticleTypeQuantity<real>([](const auto& data, auto& map) {
195         ignore_unused(map);
196         return data.charge_;
197     });
198
199     // map unique ParticleTypes to IDs
200     std::unordered_map<std::string, int> nameToId;
201     for (auto& name_particleType_tuple : particleTypes_)
202     {
203         topology_.particleTypes_.push_back(name_particleType_tuple.second);
204         nameToId[name_particleType_tuple.first] = nameToId.size();
205     }
206
207     topology_.particleTypeIdOfAllParticles_ =
208             extractParticleTypeQuantity<int>([&nameToId](const auto& data, auto& map) {
209                 ignore_unused(map);
210                 return nameToId[data.particleTypeName_];
211             });
212
213     detail::ParticleSequencer particleSequencer;
214     particleSequencer.build(molecules_);
215     topology_.particleSequencer_ = std::move(particleSequencer);
216
217     topology_.combinationRule_         = particleTypesInteractions_.getCombinationRule();
218     topology_.nonBondedInteractionMap_ = particleTypesInteractions_.generateTable();
219
220     topology_.interactionData_ = createInteractionData(topology_.particleSequencer_);
221
222     // Check whether there is any missing term in the particleTypesInteractions compared to the
223     // list of particletypes
224     for (const auto& particleType1 : particleTypes_)
225     {
226         for (const auto& particleType2 : particleTypes_)
227         {
228             auto interactionKey = std::make_tuple(ParticleTypeName(particleType1.first),
229                                                   ParticleTypeName(particleType2.first));
230             if (topology_.nonBondedInteractionMap_.count(interactionKey) == 0)
231             {
232                 std::string message =
233                         formatString("Missing nonbonded interaction parameters for pair {} {}",
234                                      particleType1.first, particleType2.first);
235                 throw InputException(message);
236             }
237         }
238     }
239
240     return topology_;
241 }
242
243 TopologyBuilder& TopologyBuilder::addMolecule(const Molecule& molecule, const int nMolecules)
244 {
245     /*
246      * 1. Push-back a tuple of molecule type and nMolecules
247      * 2. Append exclusion list into the data structure
248      */
249
250     molecules_.emplace_back(molecule, nMolecules);
251     numParticles_ += nMolecules * molecule.numParticlesInMolecule();
252
253     auto particleTypesInMolecule = molecule.particleTypesMap();
254
255     for (const auto& name_type_tuple : particleTypesInMolecule)
256     {
257         // If we already have the particleType, we need to make
258         // sure that the type's parameters are actually the same
259         // otherwise we would overwrite them
260         if (particleTypes_.count(name_type_tuple.first) > 0)
261         {
262             if (!(particleTypes_.at(name_type_tuple.first) == name_type_tuple.second))
263             {
264                 throw InputException("Differing ParticleTypes with identical names encountered");
265             }
266         }
267     }
268
269     // Note: insert does nothing if the key already exists
270     particleTypes_.insert(particleTypesInMolecule.begin(), particleTypesInMolecule.end());
271
272     return *this;
273 }
274
275 void TopologyBuilder::addParticleTypesInteractions(const ParticleTypesInteractions& particleTypesInteractions)
276 {
277     particleTypesInteractions_.merge(particleTypesInteractions);
278 }
279
280 int Topology::numParticles() const
281 {
282     return numParticles_;
283 }
284
285 std::vector<real> Topology::getCharges() const
286 {
287     return charges_;
288 }
289
290 std::vector<ParticleType> Topology::getParticleTypes() const
291 {
292     return particleTypes_;
293 }
294
295 std::vector<int> Topology::getParticleTypeIdOfAllParticles() const
296 {
297     return particleTypeIdOfAllParticles_;
298 }
299
300 int Topology::sequenceID(MoleculeName moleculeName, int moleculeNr, ResidueName residueName, ParticleName particleName) const
301 {
302     return particleSequencer_(moleculeName, moleculeNr, residueName, particleName);
303 }
304
305 NonBondedInteractionMap Topology::getNonBondedInteractionMap() const
306 {
307     return nonBondedInteractionMap_;
308 }
309
310 ListedInteractionData Topology::getInteractionData() const
311 {
312     return interactionData_;
313 }
314
315 CombinationRule Topology::getCombinationRule() const
316 {
317     return combinationRule_;
318 }
319
320 gmx::ListOfLists<int> Topology::getGmxExclusions() const
321 {
322     return exclusions_;
323 }
324
325 } // namespace nblib