Merge branch release-2021
[alexxy/gromacs.git] / api / nblib / tests / 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  * This implements topology setup tests
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  */
44 #include <gmock/gmock.h>
45 #include <gtest/gtest.h>
46
47 #include "gromacs/topology/exclusionblocks.h"
48 #include "gromacs/utility/listoflists.h"
49 #include "nblib/exception.h"
50 #include "nblib/particletype.h"
51 #include "nblib/sequencing.hpp"
52 #include "nblib/tests/testsystems.h"
53 #include "nblib/topology.h"
54 #include "nblib/topologyhelpers.h"
55 #include "nblib/particlesequencer.h"
56
57 namespace nblib
58 {
59 namespace test
60 {
61 namespace
62 {
63
64 using ::testing::Eq;
65 using ::testing::Pointwise;
66
67 //! Compares all element between two lists of lists
68 //! Todo: unify this with the identical function in nbkernelsystem test make this a method
69 //!       of ListOfLists<>
70 template<typename T>
71 void compareLists(const gmx::ListOfLists<T>& list, const std::vector<std::vector<T>>& v)
72 {
73     ASSERT_EQ(list.size(), v.size());
74     for (std::size_t i = 0; i < list.size(); i++)
75     {
76         ASSERT_EQ(list[i].size(), v[i].size());
77         EXPECT_THAT(list[i], Pointwise(Eq(), v[i]));
78     }
79 }
80
81 // This is defined in src/gromacs/mdtypes/forcerec.h but there is also a
82 // legacy C6 macro defined there that conflicts with the nblib C6 type.
83 // Todo: Once that C6 has been refactored into a regular function, this
84 //       file can just include forcerec.h
85 //! Macro to marks particles to have Van der Waals interactions
86 #define SET_CGINFO_HAS_VDW(cgi) (cgi) = ((cgi) | (1 << 23))
87
88 TEST(NBlibTest, TopologyHasNumParticles)
89 {
90     WaterTopologyBuilder waters;
91     Topology             watersTopology = waters.buildTopology(2);
92     const int            test           = watersTopology.numParticles();
93     const int            ref            = 6;
94     EXPECT_EQ(ref, test);
95 }
96
97 TEST(NBlibTest, TopologyHasCharges)
98 {
99     WaterTopologyBuilder     waters;
100     Topology                 watersTopology = waters.buildTopology(2);
101     const std::vector<real>& test           = watersTopology.getCharges();
102     const std::vector<real>& ref = { Charges.at("Ow"), Charges.at("Hw"), Charges.at("Hw"),
103                                      Charges.at("Ow"), Charges.at("Hw"), Charges.at("Hw") };
104     EXPECT_EQ(ref, test);
105 }
106
107 TEST(NBlibTest, TopologyHasMasses)
108 {
109     WaterTopologyBuilder waters;
110     Topology             watersTopology = waters.buildTopology(2);
111
112     const Mass              refOwMass = waters.water().at("Ow").mass();
113     const Mass              refHwMass = waters.water().at("H").mass();
114     const std::vector<Mass> ref = { refOwMass, refHwMass, refHwMass, refOwMass, refHwMass, refHwMass };
115     const std::vector<Mass> test = expandQuantity(watersTopology, &ParticleType::mass);
116     EXPECT_EQ(ref, test);
117 }
118
119 TEST(NBlibTest, TopologyHasParticleTypes)
120 {
121     WaterTopologyBuilder             waters;
122     Topology                         watersTopology = waters.buildTopology(2);
123     const std::vector<ParticleType>& test           = watersTopology.getParticleTypes();
124     const ParticleType               refOw          = waters.water().at("Ow");
125     const ParticleType               refHw          = waters.water().at("H");
126     const std::vector<ParticleType>& ref            = { refOw, refHw };
127     const std::vector<ParticleType>& ref2           = { refHw, refOw };
128     EXPECT_TRUE(ref == test || ref2 == test);
129 }
130
131 TEST(NBlibTest, TopologyHasParticleTypeIds)
132 {
133     WaterTopologyBuilder waters;
134     Topology             watersTopology = waters.buildTopology(2);
135
136     const std::vector<int>&          testIds   = watersTopology.getParticleTypeIdOfAllParticles();
137     const std::vector<ParticleType>& testTypes = watersTopology.getParticleTypes();
138
139     std::vector<ParticleType> testTypesExpanded;
140     testTypesExpanded.reserve(testTypes.size());
141     for (int i : testIds)
142     {
143         testTypesExpanded.push_back(testTypes[i]);
144     }
145
146     const ParticleType              refOw = waters.water().at("Ow");
147     const ParticleType              refHw = waters.water().at("H");
148     const std::vector<ParticleType> ref   = { refOw, refHw, refHw, refOw, refHw, refHw };
149
150     EXPECT_TRUE(ref == testTypesExpanded);
151 }
152
153 TEST(NBlibTest, TopologyThrowsIdenticalParticleType)
154 {
155     //! User error: Two different ParticleTypes with the same name
156     ParticleType U235(ParticleTypeName("Uranium"), Mass(235));
157     ParticleType U238(ParticleTypeName("Uranium"), Mass(238));
158
159     Molecule ud235(MoleculeName("UraniumDimer235"));
160     ud235.addParticle(ParticleName("U1"), U235);
161     ud235.addParticle(ParticleName("U2"), U235);
162
163     Molecule ud238(MoleculeName("UraniumDimer238"));
164     ud238.addParticle(ParticleName("U1"), U238);
165     ud238.addParticle(ParticleName("U2"), U238);
166
167     TopologyBuilder topologyBuilder;
168     topologyBuilder.addMolecule(ud235, 1);
169     EXPECT_THROW(topologyBuilder.addMolecule(ud238, 1), InputException);
170 }
171
172 TEST(NBlibTest, TopologyHasExclusions)
173 {
174     WaterTopologyBuilder        waters;
175     Topology                    watersTopology = waters.buildTopology(2);
176     ExclusionLists<int>         exclusionLists = watersTopology.exclusionLists();
177     const gmx::ListOfLists<int> testExclusions(std::move(exclusionLists.ListRanges),
178                                                std::move(exclusionLists.ListElements));
179
180     const std::vector<std::vector<int>>& refExclusions = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
181                                                            { 3, 4, 5 }, { 3, 4, 5 }, { 3, 4, 5 } };
182
183     compareLists(testExclusions, refExclusions);
184 }
185
186 TEST(NBlibTest, TopologyHasSequencing)
187 {
188     WaterTopologyBuilder waters;
189     Topology             watersTopology = waters.buildTopology(2);
190
191     EXPECT_EQ(0, watersTopology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("Oxygen")));
192     EXPECT_EQ(1, watersTopology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("H1")));
193     EXPECT_EQ(2, watersTopology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("H2")));
194     EXPECT_EQ(3, watersTopology.sequenceID(MoleculeName("SOL"), 1, ResidueName("SOL"), ParticleName("Oxygen")));
195     EXPECT_EQ(4, watersTopology.sequenceID(MoleculeName("SOL"), 1, ResidueName("SOL"), ParticleName("H1")));
196     EXPECT_EQ(5, watersTopology.sequenceID(MoleculeName("SOL"), 1, ResidueName("SOL"), ParticleName("H2")));
197 }
198
199 TEST(NBlibTest, TopologyCanAggregateBonds)
200 {
201     Molecule water    = WaterMoleculeBuilder{}.waterMolecule();
202     Molecule methanol = MethanolMoleculeBuilder{}.methanolMolecule();
203
204     std::vector<std::tuple<Molecule, int>> molecules{ std::make_tuple(water, 2),
205                                                       std::make_tuple(methanol, 1) };
206     std::vector<HarmonicBondType>          bonds;
207     std::vector<size_t>                    bondsExpansion;
208     std::tie(bondsExpansion, bonds) = detail::collectInteractions<HarmonicBondType>(molecules);
209
210     std::vector<HarmonicBondType> bondsTest;
211     // use the expansionArray (bondsExpansion) to expand to the full list if bonds
212     std::transform(begin(bondsExpansion),
213                    end(bondsExpansion),
214                    std::back_inserter(bondsTest),
215                    [&bonds](size_t i) { return bonds[i]; });
216
217     std::vector<HarmonicBondType> waterBonds =
218             pickType<HarmonicBondType>(water.interactionData()).interactionTypes_;
219     std::vector<HarmonicBondType> methanolBonds =
220             pickType<HarmonicBondType>(methanol.interactionData()).interactionTypes_;
221
222     std::vector<HarmonicBondType> bondsReference;
223     std::copy(begin(waterBonds), end(waterBonds), std::back_inserter(bondsReference));
224     std::copy(begin(waterBonds), end(waterBonds), std::back_inserter(bondsReference));
225     std::copy(begin(methanolBonds), end(methanolBonds), std::back_inserter(bondsReference));
226
227     EXPECT_EQ(bondsTest, bondsReference);
228 }
229
230 TEST(NBlibTest, TopologyCanSequencePairIDs)
231 {
232     Molecule water    = WaterMoleculeBuilder{}.waterMolecule();
233     Molecule methanol = MethanolMoleculeBuilder{}.methanolMolecule();
234
235     std::vector<std::tuple<Molecule, int>> molecules{ std::make_tuple(water, 2),
236                                                       std::make_tuple(methanol, 1) };
237     ParticleSequencer                      particleSequencer;
238     particleSequencer.build(molecules);
239     auto pairs = detail::sequenceIDs<HarmonicBondType>(molecules, particleSequencer);
240
241     int Ow1 = particleSequencer(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("Oxygen"));
242     int H11 = particleSequencer(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("H1"));
243     int H12 = particleSequencer(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("H2"));
244     int Ow2 = particleSequencer(MoleculeName("SOL"), 1, ResidueName("SOL"), ParticleName("Oxygen"));
245     int H21 = particleSequencer(MoleculeName("SOL"), 1, ResidueName("SOL"), ParticleName("H1"));
246     int H22 = particleSequencer(MoleculeName("SOL"), 1, ResidueName("SOL"), ParticleName("H2"));
247
248     int Me  = particleSequencer(MoleculeName("MeOH"), 0, ResidueName("MeOH"), ParticleName("Me1"));
249     int MeO = particleSequencer(MoleculeName("MeOH"), 0, ResidueName("MeOH"), ParticleName("O2"));
250     int MeH = particleSequencer(MoleculeName("MeOH"), 0, ResidueName("MeOH"), ParticleName("H3"));
251
252     /// \cond DO_NOT_DOCUMENT
253 #define SORT(i, j) (i < j) ? i : j, (i < j) ? j : i
254
255     std::vector<CoordinateIndex<HarmonicBondType>> pairs_reference{
256         { SORT(Ow1, H11) }, { SORT(Ow1, H12) }, { SORT(Ow2, H21) },
257         { SORT(Ow2, H22) }, { SORT(MeO, MeH) }, { SORT(MeO, Me) }
258     };
259 #undef SORT
260     /// \endcond
261
262     EXPECT_EQ(pairs, pairs_reference);
263 }
264
265 TEST(NBlibTest, TopologySequenceIdThrows)
266 {
267     Molecule water    = WaterMoleculeBuilder{}.waterMolecule();
268     Molecule methanol = MethanolMoleculeBuilder{}.methanolMolecule();
269
270     std::vector<std::tuple<Molecule, int>> molecules{ std::make_tuple(water, 2),
271                                                       std::make_tuple(methanol, 1) };
272     ParticleSequencer                      particleSequencer;
273     particleSequencer.build(molecules);
274     auto pairs = detail::sequenceIDs<HarmonicBondType>(molecules, particleSequencer);
275
276     // Input error: no particle called O-Atom in molecule "water"
277     EXPECT_THROW(particleSequencer(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("O-Atom")),
278                  InputException);
279 }
280
281 TEST(NBlibTest, TopologyCanEliminateDuplicateBonds)
282 {
283     HarmonicBondType b1(1.0, 2.0);
284     HarmonicBondType b2(1.1, 2.1);
285     HarmonicBondType b3(1.2, 2.2);
286
287     // can be compressed to {b1,b2,b3} + {1,1,2,0,1,0,2,2}
288     std::vector<HarmonicBondType> bonds{ b2, b2, b3, b1, b2, b1, b3, b3 };
289
290     // expected output
291     std::vector<HarmonicBondType> uniqueBondsReference{ b1, b2, b3 };
292     std::vector<size_t>           indicesReference{ 1, 1, 2, 0, 1, 0, 2, 2 };
293
294     std::tuple<std::vector<size_t>, std::vector<HarmonicBondType>> bondData =
295             detail::eliminateDuplicateInteractions(bonds);
296
297     auto indices     = std::get<0>(bondData);
298     auto uniqueBonds = std::get<1>(bondData);
299
300     EXPECT_EQ(uniqueBondsReference, uniqueBonds);
301     EXPECT_EQ(indicesReference, indices);
302 }
303
304 TEST(NBlibTest, TopologyListedInteractions)
305 {
306     // Todo: add angles to SpcMethanolTopologyBuilder and extend interactions_reference below to
307     // Todo: include angles
308
309     Topology spcTopology = SpcMethanolTopologyBuilder{}.buildTopology(1, 2);
310
311     auto  interactionData = spcTopology.getInteractionData();
312     auto& harmonicBonds   = pickType<HarmonicBondType>(interactionData);
313
314     auto& indices = harmonicBonds.indices;
315     auto& bonds   = harmonicBonds.parameters;
316
317     std::map<std::tuple<int, int>, HarmonicBondType> interactions_test;
318     for (auto& ituple : indices)
319     {
320         interactions_test[std::make_tuple(std::get<0>(ituple), std::get<1>(ituple))] =
321                 bonds[std::get<2>(ituple)];
322     }
323
324     // there should be 3 unique HarmonicBondType instances
325     EXPECT_EQ(bonds.size(), 3);
326     // and 6 interaction pairs (bonds)
327     EXPECT_EQ(indices.size(), 6);
328
329     HarmonicBondType ohBond(1., 1.);
330     HarmonicBondType ohBondMethanol(1.01, 1.02);
331     HarmonicBondType ometBond(1.1, 1.2);
332
333     std::map<std::tuple<int, int>, HarmonicBondType> interactions_reference;
334
335     int Ow = spcTopology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("Oxygen"));
336     int H1 = spcTopology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("H1"));
337     int H2 = spcTopology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("H2"));
338
339     int Me1 = spcTopology.sequenceID(MoleculeName("MeOH"), 0, ResidueName("MeOH"), ParticleName("Me1"));
340     int MeO1 = spcTopology.sequenceID(MoleculeName("MeOH"), 0, ResidueName("MeOH"), ParticleName("O2"));
341     int MeH1 = spcTopology.sequenceID(MoleculeName("MeOH"), 0, ResidueName("MeOH"), ParticleName("H3"));
342
343     int Me2 = spcTopology.sequenceID(MoleculeName("MeOH"), 1, ResidueName("MeOH"), ParticleName("Me1"));
344     int MeO2 = spcTopology.sequenceID(MoleculeName("MeOH"), 1, ResidueName("MeOH"), ParticleName("O2"));
345     int MeH2 = spcTopology.sequenceID(MoleculeName("MeOH"), 1, ResidueName("MeOH"), ParticleName("H3"));
346
347     /// \cond DO_NOT_DOCUMENT
348 #define SORT(i, j) (i < j) ? i : j, (i < j) ? j : i
349     interactions_reference[std::make_tuple(SORT(Ow, H1))]     = ohBond;
350     interactions_reference[std::make_tuple(SORT(Ow, H2))]     = ohBond;
351     interactions_reference[std::make_tuple(SORT(MeO1, MeH1))] = ohBondMethanol;
352     interactions_reference[std::make_tuple(SORT(MeO1, Me1))]  = ometBond;
353     interactions_reference[std::make_tuple(SORT(MeO2, MeH2))] = ohBondMethanol;
354     interactions_reference[std::make_tuple(SORT(MeO2, Me2))]  = ometBond;
355 #undef SORT
356     /// \endcond
357
358     EXPECT_TRUE(std::equal(
359             begin(interactions_reference), end(interactions_reference), begin(interactions_test)));
360 }
361
362 TEST(NBlibTest, TopologyListedInteractionsMultipleTypes)
363 {
364     // Todo: add an angle type here
365
366     Molecule water    = WaterMoleculeBuilder{}.waterMolecule();
367     Molecule methanol = MethanolMoleculeBuilder{}.methanolMolecule();
368
369     CubicBondType     testBond(1., 1., 1.);
370     HarmonicAngleType testAngle(Degrees(1), 1);
371
372     water.addInteraction(ParticleName("H1"), ParticleName("H2"), testBond);
373     water.addInteraction(ParticleName("H1"), ParticleName("Oxygen"), ParticleName("H2"), testAngle);
374
375     ParticleTypesInteractions nbInteractions;
376     std::vector<std::string>  particleTypeNames = { "Ow", "H", "OMet", "CMet" };
377     for (const auto& name : particleTypeNames)
378     {
379         nbInteractions.add(ParticleTypeName(name), C6(0), C12(0));
380     }
381
382     TopologyBuilder topologyBuilder;
383     topologyBuilder.addMolecule(water, 1);
384     topologyBuilder.addMolecule(methanol, 1);
385     topologyBuilder.addParticleTypesInteractions(nbInteractions);
386
387     Topology topology = topologyBuilder.buildTopology();
388
389     auto  interactionData   = topology.getInteractionData();
390     auto& harmonicBonds     = pickType<HarmonicBondType>(interactionData);
391     auto& cubicBonds        = pickType<CubicBondType>(interactionData);
392     auto& angleInteractions = pickType<HarmonicAngleType>(interactionData);
393
394     HarmonicBondType              ohBond(1., 1.);
395     HarmonicBondType              ohBondMethanol(1.01, 1.02);
396     HarmonicBondType              ometBond(1.1, 1.2);
397     std::vector<HarmonicBondType> harmonicBondsReference{ ohBond, ohBondMethanol, ometBond };
398
399     EXPECT_EQ(harmonicBonds.parameters, harmonicBondsReference);
400
401     int H1 = topology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("H1"));
402     int H2 = topology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("H2"));
403     int Ow = topology.sequenceID(MoleculeName("SOL"), 0, ResidueName("SOL"), ParticleName("Oxygen"));
404
405     int Me1 = topology.sequenceID(MoleculeName("MeOH"), 0, ResidueName("MeOH"), ParticleName("Me1"));
406     int MeO1 = topology.sequenceID(MoleculeName("MeOH"), 0, ResidueName("MeOH"), ParticleName("O2"));
407     int MeH1 = topology.sequenceID(MoleculeName("MeOH"), 0, ResidueName("MeOH"), ParticleName("H3"));
408
409     std::vector<CubicBondType>                   cubicBondsReference{ testBond };
410     std::vector<InteractionIndex<CubicBondType>> cubicIndicesReference{
411         { std::min(H1, H2), std::max(H1, H2), 0 }
412     };
413     EXPECT_EQ(cubicBondsReference, cubicBonds.parameters);
414     EXPECT_EQ(cubicIndicesReference, cubicBonds.indices);
415
416     HarmonicAngleType                                methanolAngle(Degrees(108.52), 397.5);
417     std::vector<HarmonicAngleType>                   angleReference{ testAngle, methanolAngle };
418     std::vector<InteractionIndex<HarmonicAngleType>> angleIndicesReference{
419         { std::min(H1, H2), Ow, std::max(H1, H2), 0 }, { std::min(MeH1, MeO1), Me1, std::max(MeO1, MeH1), 1 }
420     };
421     EXPECT_EQ(angleReference, angleInteractions.parameters);
422     EXPECT_EQ(angleIndicesReference, angleInteractions.indices);
423 }
424
425 TEST(NBlibTest, TopologyInvalidParticleInInteractionThrows)
426 {
427     Molecule water    = WaterMoleculeBuilder{}.waterMolecule();
428     Molecule methanol = MethanolMoleculeBuilder{}.methanolMolecule();
429
430     HarmonicBondType testBond(1., 1.);
431
432     // Invalid input: no particle named "Iron" in molecule water
433     water.addInteraction(ParticleName("H1"), ParticleName("Iron"), testBond);
434
435     ParticleTypesInteractions nbInteractions;
436     std::vector<std::string>  particleTypeNames = { "Ow", "H", "OMet", "CMet" };
437     for (const auto& name : particleTypeNames)
438     {
439         nbInteractions.add(ParticleTypeName(name), C6(0), C12(0));
440     }
441
442     TopologyBuilder topologyBuilder;
443     topologyBuilder.addMolecule(water, 1);
444     topologyBuilder.addMolecule(methanol, 1);
445     topologyBuilder.addParticleTypesInteractions(nbInteractions);
446
447     EXPECT_THROW(topologyBuilder.buildTopology(), InputException);
448 }
449
450
451 TEST(NBlibTest, toGmxExclusionBlockWorks)
452 {
453     std::vector<std::tuple<int, int>> testInput{ { 0, 0 }, { 0, 1 }, { 0, 2 }, { 1, 0 }, { 1, 1 },
454                                                  { 1, 2 }, { 2, 0 }, { 2, 1 }, { 2, 2 } };
455
456     std::vector<gmx::ExclusionBlock> reference;
457
458     gmx::ExclusionBlock localBlock;
459     localBlock.atomNumber.push_back(0);
460     localBlock.atomNumber.push_back(1);
461     localBlock.atomNumber.push_back(2);
462
463     reference.push_back(localBlock);
464     reference.push_back(localBlock);
465     reference.push_back(localBlock);
466
467     std::vector<gmx::ExclusionBlock> probe = toGmxExclusionBlock(testInput);
468
469     ASSERT_EQ(reference.size(), probe.size());
470     for (size_t i = 0; i < reference.size(); ++i)
471     {
472         ASSERT_EQ(reference[i].atomNumber.size(), probe[i].atomNumber.size());
473         for (size_t j = 0; j < reference[i].atomNumber.size(); ++j)
474         {
475             EXPECT_EQ(reference[i].atomNumber[j], probe[i].atomNumber[j]);
476         }
477     }
478 }
479
480 } // namespace
481 } // namespace test
482 } // namespace nblib