acaea4999f2a25cce5fc775f78aa112c10122f55
[alexxy/gromacs.git] / api / nblib / topology.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2020,2021, 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 <algorithm>
46 #include <numeric>
47
48 #include "gromacs/topology/exclusionblocks.h"
49 #include "gromacs/utility/listoflists.h"
50 #include "gromacs/utility/smalloc.h"
51 #include "nblib/exception.h"
52 #include "nblib/particletype.h"
53 #include "nblib/sequencing.hpp"
54 #include "nblib/topology.h"
55 #include "nblib/util/util.hpp"
56 #include "nblib/topologyhelpers.h"
57
58 namespace nblib
59 {
60
61 TopologyBuilder::TopologyBuilder() : numParticles_(0) {}
62
63 ExclusionLists<int> TopologyBuilder::createExclusionsLists() const
64 {
65     const auto& moleculesList = molecules_;
66
67     std::vector<gmx::ExclusionBlock> exclusionBlockGlobal;
68     exclusionBlockGlobal.reserve(numParticles_);
69
70     size_t particleNumberOffset = 0;
71     for (const auto& molNumberTuple : moleculesList)
72     {
73         const Molecule& molecule   = std::get<0>(molNumberTuple);
74         size_t          numMols    = std::get<1>(molNumberTuple);
75         const auto&     exclusions = molecule.getExclusions();
76
77         // Note this is a programming error as all particles should exclude at least themselves and empty topologies are not allowed.
78         const std::string message =
79                 "No exclusions found in the " + molecule.name().value() + " molecule.";
80         assert((!exclusions.empty() && message.c_str()));
81
82         std::vector<gmx::ExclusionBlock> exclusionBlockPerMolecule = toGmxExclusionBlock(exclusions);
83
84         // duplicate the exclusionBlockPerMolecule for the number of Molecules of (numMols)
85         for (size_t i = 0; i < numMols; ++i)
86         {
87             auto offsetExclusions = offsetGmxBlock(exclusionBlockPerMolecule, particleNumberOffset);
88
89             std::copy(std::begin(offsetExclusions),
90                       std::end(offsetExclusions),
91                       std::back_inserter(exclusionBlockGlobal));
92
93             particleNumberOffset += molecule.numParticlesInMolecule();
94         }
95     }
96
97     gmx::ListOfLists<int> exclusionsListOfListsGlobal;
98     for (const auto& block : exclusionBlockGlobal)
99     {
100         exclusionsListOfListsGlobal.pushBack(block.atomNumber);
101     }
102
103     std::vector<int>    listRanges(exclusionsListOfListsGlobal.listRangesView().begin(),
104                                 exclusionsListOfListsGlobal.listRangesView().end());
105     std::vector<int>    listElements(exclusionsListOfListsGlobal.elementsView().begin(),
106                                   exclusionsListOfListsGlobal.elementsView().end());
107     ExclusionLists<int> exclusionListsGlobal;
108     exclusionListsGlobal.ListRanges   = std::move(listRanges);
109     exclusionListsGlobal.ListElements = std::move(listElements);
110
111     return exclusionListsGlobal;
112 }
113
114 ListedInteractionData TopologyBuilder::createInteractionData(const ParticleSequencer& particleSequencer)
115 {
116     ListedInteractionData interactionData;
117
118     // this code is doing the compile time equivalent of
119     // for (int i = 0; i < interactionData.size(); ++i)
120     //     create(get<i>(interactionData));
121
122     auto create = [this, &particleSequencer](auto& interactionDataElement) {
123         using InteractionType = typename std::decay_t<decltype(interactionDataElement)>::type;
124
125         // first compression stage: each bond per molecule listed once,
126         // eliminates duplicates from multiple identical molecules
127         auto  compressedDataStage1 = detail::collectInteractions<InteractionType>(this->molecules_);
128         auto& expansionArrayStage1 = std::get<0>(compressedDataStage1);
129         auto& moleculeInteractions = std::get<1>(compressedDataStage1);
130
131         // second compression stage: recognize bond duplicates among bonds from all molecules put together
132         auto  compressedDataStage2 = detail::eliminateDuplicateInteractions(moleculeInteractions);
133         auto& expansionArrayStage2 = std::get<0>(compressedDataStage2);
134         auto& uniqueInteractionInstances = std::get<1>(compressedDataStage2);
135
136         // combine stage 1 + 2 expansion arrays
137         std::vector<size_t> expansionArray(expansionArrayStage1.size());
138         std::transform(begin(expansionArrayStage1),
139                        end(expansionArrayStage1),
140                        begin(expansionArray),
141                        [& S2 = expansionArrayStage2](size_t S1Element) { return S2[S1Element]; });
142
143         // add data about InteractionType instances
144         interactionDataElement.parameters = std::move(uniqueInteractionInstances);
145
146         interactionDataElement.indices.resize(expansionArray.size());
147         // coordinateIndices contains the particle sequence IDs of all interaction coordinates of type <BondType>
148         auto coordinateIndices = detail::sequenceIDs<InteractionType>(this->molecules_, particleSequencer);
149         // zip coordinateIndices(i,j,...) + expansionArray(k) -> interactionDataElement.indices(i,j,...,k)
150         std::transform(begin(coordinateIndices),
151                        end(coordinateIndices),
152                        begin(expansionArray),
153                        begin(interactionDataElement.indices),
154                        [](auto coordinateIndex, auto interactionIndex) {
155                            std::array<int, coordinateIndex.size() + 1> ret{ 0 };
156                            for (int i = 0; i < int(coordinateIndex.size()); ++i)
157                            {
158                                ret[i] = coordinateIndex[i];
159                            }
160                            ret[coordinateIndex.size()] = interactionIndex;
161                            return ret;
162                        });
163     };
164
165     for_each_tuple(create, interactionData);
166
167     return interactionData;
168 }
169
170 template<typename T, class Extractor>
171 std::vector<T> TopologyBuilder::extractParticleTypeQuantity(Extractor&& extractor)
172 {
173     auto& moleculesList = molecules_;
174
175     // returned object
176     std::vector<T> ret;
177     ret.reserve(numParticles_);
178
179     for (auto& molNumberTuple : moleculesList)
180     {
181         Molecule& molecule = std::get<0>(molNumberTuple);
182         size_t    numMols  = std::get<1>(molNumberTuple);
183
184         for (size_t i = 0; i < numMols; ++i)
185         {
186             for (auto& particleData : molecule.particleData())
187             {
188                 auto particleTypesMap = molecule.particleTypesMap();
189                 ret.push_back(extractor(particleData, particleTypesMap));
190             }
191         }
192     }
193
194     return ret;
195 }
196
197 Topology TopologyBuilder::buildTopology()
198 {
199     assert((!(numParticles_ < 0) && "It should not be possible to have negative particles"));
200     if (numParticles_ == 0)
201     {
202         throw InputException("You cannot build a topology with no particles");
203     }
204     topology_.numParticles_ = numParticles_;
205
206     topology_.exclusionLists_ = createExclusionsLists();
207     topology_.charges_        = extractParticleTypeQuantity<real>(
208             [](const auto& data, [[maybe_unused]] auto& map) { return data.charge_; });
209
210     // map unique ParticleTypes to IDs
211     std::unordered_map<std::string, int> nameToId;
212     for (auto& name_particleType_tuple : particleTypes_)
213     {
214         topology_.particleTypes_.push_back(name_particleType_tuple.second);
215         nameToId[name_particleType_tuple.first] = nameToId.size();
216     }
217
218     topology_.particleTypeIdOfAllParticles_ =
219             extractParticleTypeQuantity<int>([&nameToId](const auto& data, [[maybe_unused]] auto& map) {
220                 return nameToId[data.particleTypeName_];
221             });
222
223     ParticleSequencer particleSequencer;
224     particleSequencer.build(molecules_);
225     topology_.particleSequencer_ = std::move(particleSequencer);
226
227     topology_.combinationRule_         = particleTypesInteractions_.getCombinationRule();
228     topology_.nonBondedInteractionMap_ = particleTypesInteractions_.generateTable();
229
230     topology_.interactionData_ = createInteractionData(topology_.particleSequencer_);
231
232     // Check whether there is any missing term in the particleTypesInteractions compared to the
233     // list of particletypes
234     for (const auto& particleType1 : particleTypes_)
235     {
236         for (const auto& particleType2 : particleTypes_)
237         {
238             auto interactionKey = std::make_tuple(ParticleTypeName(particleType1.first),
239                                                   ParticleTypeName(particleType2.first));
240             if (topology_.nonBondedInteractionMap_.count(interactionKey) == 0)
241             {
242                 std::string message =
243                         formatString("Missing nonbonded interaction parameters for pair {} {}",
244                                      particleType1.first,
245                                      particleType2.first);
246                 throw InputException(message);
247             }
248         }
249     }
250
251     return topology_;
252 }
253
254 TopologyBuilder& TopologyBuilder::addMolecule(const Molecule& molecule, const int nMolecules)
255 {
256     /*
257      * 1. Push-back a tuple of molecule type and nMolecules
258      * 2. Append exclusion list into the data structure
259      */
260
261     molecules_.emplace_back(molecule, nMolecules);
262     numParticles_ += nMolecules * molecule.numParticlesInMolecule();
263
264     auto particleTypesInMolecule = molecule.particleTypesMap();
265
266     for (const auto& name_type_tuple : particleTypesInMolecule)
267     {
268         // If we already have the particleType, we need to make
269         // sure that the type's parameters are actually the same
270         // otherwise we would overwrite them
271         if (particleTypes_.count(name_type_tuple.first) > 0)
272         {
273             if (!(particleTypes_.at(name_type_tuple.first) == name_type_tuple.second))
274             {
275                 throw InputException("Differing ParticleTypes with identical names encountered");
276             }
277         }
278     }
279
280     // Note: insert does nothing if the key already exists
281     particleTypes_.insert(particleTypesInMolecule.begin(), particleTypesInMolecule.end());
282
283     return *this;
284 }
285
286 void TopologyBuilder::addParticleTypesInteractions(const ParticleTypesInteractions& particleTypesInteractions)
287 {
288     particleTypesInteractions_.merge(particleTypesInteractions);
289 }
290
291 int Topology::numParticles() const
292 {
293     return numParticles_;
294 }
295
296 std::vector<real> Topology::getCharges() const
297 {
298     return charges_;
299 }
300
301 std::vector<ParticleType> Topology::getParticleTypes() const
302 {
303     return particleTypes_;
304 }
305
306 std::vector<int> Topology::getParticleTypeIdOfAllParticles() const
307 {
308     return particleTypeIdOfAllParticles_;
309 }
310
311 int Topology::sequenceID(MoleculeName moleculeName, int moleculeNr, ResidueName residueName, ParticleName particleName) const
312 {
313     return particleSequencer_(moleculeName, moleculeNr, residueName, particleName);
314 }
315
316 NonBondedInteractionMap Topology::getNonBondedInteractionMap() const
317 {
318     return nonBondedInteractionMap_;
319 }
320
321 ListedInteractionData Topology::getInteractionData() const
322 {
323     return interactionData_;
324 }
325
326 CombinationRule Topology::getCombinationRule() const
327 {
328     return combinationRule_;
329 }
330
331 ExclusionLists<int> Topology::exclusionLists() const
332 {
333     return exclusionLists_;
334 }
335
336 } // namespace nblib