LCOV - code coverage report
Current view: top level - tools - AtomDistribution.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 328 385 85.2 %
Date: 2026-11-05 10:19:04 Functions: 59 82 72.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2025 The plumed team
       3             :    (see the PEOPLE file at the root of the distribution for a list of names)
       4             : 
       5             :    See http://www.plumed.org for more information.
       6             : 
       7             :    This file is part of plumed, version 2.
       8             : 
       9             :    plumed is free software: you can redistribute it and/or modify
      10             :    it under the terms of the GNU Lesser General Public License as published by
      11             :    the Free Software Foundation, either version 3 of the License, or
      12             :    (at your option) any later version.
      13             : 
      14             :    plumed 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
      17             :    GNU Lesser General Public License for more details.
      18             : 
      19             :    You should have received a copy of the GNU Lesser General Public License
      20             :    along with plumed.  If not, see <http://www.gnu.org/licenses/>.
      21             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
      22             : #include "AtomDistribution.h"
      23             : #include "Exception.h"
      24             : #include "View.h"
      25             : #include "Tools.h"
      26             : #include <cassert>
      27             : #include <variant>
      28             : #include <optional>
      29             : #include <functional>
      30             : 
      31             : 
      32             : namespace PLMD {
      33             : 
      34             : using unpackedLine=gch::small_vector<std::string_view>;
      35             : 
      36         196 : void AtomDistribution::frame(std::vector<Vector>& posToUpdate,
      37             :                              std::vector<double>& box,
      38             :                              unsigned step,
      39             :                              Random& rng) {
      40             :   assert(box.size()>=9);
      41             :   //call the specialized frame
      42         196 :   frame(make_view(posToUpdate),
      43             :         View<double,9>(box.data()),
      44             :         step,
      45             :         rng);
      46         196 : }
      47             : 
      48             : class UniformSphericalVector {
      49             :   double rCub;
      50             : 
      51             : public:
      52             :   //assuming rmin=0
      53          40 :   explicit UniformSphericalVector(const double rmax):
      54          40 :     rCub (rmax*rmax*rmax/*-rminCub*/) {}
      55        7000 :   PLMD::Vector operator()(Random& rng) const {
      56        7000 :     double rho = std::cbrt (/*rminCub + */rng.RandU01()*rCub);
      57        7000 :     double theta =std::acos (2.0*rng.RandU01() -1.0);
      58        7000 :     double phi = 2.0 * PLMD::pi * rng.RandU01();
      59             :     return Vector (
      60        7000 :              rho * sin (theta) * cos (phi),
      61        7000 :              rho * sin (theta) * sin (phi),
      62        7000 :              rho * cos (theta));
      63             :   }
      64             : };
      65             : 
      66             : ///A wiggly line of atoms
      67           1 : struct theLine:public AtomDistribution {
      68             :   static constexpr auto id="line";
      69             :   static constexpr auto doc="A line of atoms, that wiggles randomly at each step";
      70           1 :   void frame(View<Vector> posToUpdate,
      71             :              View<double,9> box,
      72             :              unsigned /*step*/,
      73             :              Random& /*rng*/) override {
      74             :     auto nat = posToUpdate.size();
      75             : 
      76         201 :     for (unsigned i=0; i<nat; ++i) {
      77         200 :       posToUpdate[i] = Vector(i, 0, 0);
      78             :     }
      79           1 :     box[0]=nat;
      80           1 :     box[1]=0.0;
      81           1 :     box[2]=0.0;
      82           1 :     box[3]=0.0;
      83           1 :     box[4]=1.75;
      84           1 :     box[5]=0.0;
      85           1 :     box[6]=0.0;
      86           1 :     box[7]=0.0;
      87           1 :     box[8]=1.75;
      88           1 :   }
      89             : };
      90             : 
      91             : ///Atom randomly distributed in a sphere
      92          17 : struct uniformSphere:public AtomDistribution {
      93             :   static constexpr auto id="sphere";
      94             :   static constexpr auto doc="Atoms are displaced uniformly in a sphere, the desity is ccirca 1/unit^3";
      95          31 :   void frame(View<Vector> posToUpdate,
      96             :              View<double,9> box,
      97             :              unsigned /*step*/,
      98             :              Random& rng) override {
      99             :     //giving more or less a cubic udm of volume for each atom: V=nat
     100          31 :     const double rmax= std::cbrt ((3.0/(4.0*PLMD::pi)) * posToUpdate.size());
     101             : 
     102             :     UniformSphericalVector usv(rmax);
     103             :     auto s=posToUpdate.begin();
     104             :     auto e=posToUpdate.end();
     105             :     //I am using the iterators:this is slightly faster,
     106             :     // enough to overcome the cost of the vtable that I added
     107        5231 :     for (unsigned i=0; s!=e; ++s,++i) {
     108        5200 :       *s = usv (rng);
     109             :     }
     110             : 
     111          31 :     box[0]=2.0*rmax;
     112          31 :     box[1]=0.0;
     113          31 :     box[2]=0.0;
     114          31 :     box[3]=0.0;
     115          31 :     box[4]=2.0*rmax;
     116          31 :     box[5]=0.0;
     117          31 :     box[6]=0.0;
     118          31 :     box[7]=0.0;
     119          31 :     box[8]=2.0*rmax;
     120          31 :   }
     121             : };
     122             : 
     123             : ///Atom randomly distributed between two not overlapping a spheres
     124           5 : struct twoGlobs: public AtomDistribution {
     125             :   static constexpr auto id="globs";
     126             :   static constexpr auto doc="Two spheres of uniformly distributed atosm";
     127           8 :   void frame(View<Vector> posToUpdate,
     128             :              View<double,9> box,
     129             :              unsigned /*step*/,
     130             :              Random&rng) override {
     131             :     //I am using two unigform spheres and 2V=n
     132           8 :     const double rmax= std::cbrt ((3.0/(8.0*PLMD::pi)) * posToUpdate.size());
     133             : 
     134             :     UniformSphericalVector usv(rmax);
     135           8 :     const std::array<const Vector,2> centers{
     136             :       PLMD::Vector{0.0,0.0,0.0},
     137             : //so they do not overlap
     138             :       PLMD::Vector{2.0*rmax,2.0*rmax,2.0*rmax}
     139           8 :     };
     140        1608 :     std::generate(posToUpdate.begin(),posToUpdate.end(),[&]() {
     141             :       //RandInt is only declared
     142             :       // return usv (rng) + centers[rng.RandInt(1)];
     143        1600 :       return usv (rng) + centers[rng.RandU01()>0.5];
     144             :     });
     145             : 
     146           8 :     box[0]=4.0 *rmax;
     147           8 :     box[1]=0.0;
     148           8 :     box[2]=0.0;
     149           8 :     box[3]=0.0;
     150           8 :     box[4]=4.0 *rmax;
     151           8 :     box[5]=0.0;
     152           8 :     box[6]=0.0;
     153           8 :     box[7]=0.0;
     154           8 :     box[8]=4.0 *rmax;
     155           8 :   }
     156             : };
     157             : 
     158           2 : struct uniformCube:public AtomDistribution {
     159             :   static constexpr auto id="cube";
     160             :   static constexpr auto doc="Randomly displaced atoms in a cube, the density is circa 1/unit^3";
     161           2 :   void frame(View<Vector> posToUpdate,
     162             :              View<double,9> box,
     163             :              unsigned /*step*/,
     164             :              Random& rng) override {
     165             :     //giving more or less a cubic udm of volume for each atom: V = nat
     166           2 :     const double rmax = std::cbrt(static_cast<double>(posToUpdate.size()));
     167             : 
     168             :     auto s=posToUpdate.begin();
     169             :     auto e=posToUpdate.end();
     170             :     //I am using the iterators:this is slightly faster,
     171             :     // enough to overcome the cost of the vtable that I added
     172         402 :     for (unsigned i=0; s!=e; ++s,++i) {
     173         400 :       *s = Vector (rng.RandU01()*rmax,rng.RandU01()*rmax,rng.RandU01()*rmax);
     174             :     }
     175             :     //+0.05 to avoid overlap
     176           2 :     box[0]=rmax+0.05;
     177           2 :     box[1]=0.0;
     178           2 :     box[2]=0.0;
     179           2 :     box[3]=0.0;
     180           2 :     box[4]=rmax+0.05;
     181           2 :     box[5]=0.0;
     182           2 :     box[6]=0.0;
     183           2 :     box[7]=0.0;
     184           2 :     box[8]=rmax+0.05;
     185           2 :   }
     186             : };
     187             : 
     188             : /// A simple correction to getting (in ceil mode) the correct nearest perfect cube
     189         146 : unsigned ceiledPerfectCube(const unsigned N) {
     190             :   // In some case a perfect cube is not well represented by double
     191             :   // and will get uncorrectly ceiled up (for example 24389 will result in a 30 and not in 29)
     192             : 
     193             :   // This assumes that N!=0
     194         146 :   const size_t x = std::ceil(std::cbrt(static_cast<double>(N)))-1;
     195         146 :   if ( x*x*x >= N) {
     196          22 :     return x;
     197             :   }
     198         124 :   return x+1;
     199             : }
     200             : 
     201          92 : struct tiledSimpleCubic:public AtomDistribution {
     202             :   static constexpr auto id="sc";
     203             :   static constexpr auto doc="Simple Cubic";
     204         139 :   void frame(View<Vector> posToUpdate,
     205             :              View<double,9> box,
     206             :              unsigned /*step*/,
     207             :              Random& rng) override {
     208             :     //Tiling the space in this way will not tests 100% the pbc, but
     209             :     //I do not think that write a spacefilling curve, like Hilbert, Peano or Morton
     210             :     //could be a good idea, in this case
     211             : 
     212         139 :     const unsigned rmax =ceiledPerfectCube(posToUpdate.size());
     213             : 
     214             :     auto s=posToUpdate.begin();
     215             :     auto e=posToUpdate.end();
     216             :     //I am using the iterators:this is slightly faster,
     217             :     // enough to overcome the cost of the vtable that I added
     218         759 :     for (unsigned k=0; k<rmax&&s!=e; ++k) {
     219        3464 :       for (unsigned j=0; j<rmax&&s!=e; ++j) {
     220       16258 :         for (unsigned i=0; i<rmax&&s!=e; ++i) {
     221       13414 :           *s = Vector (i,j,k);
     222       13414 :           ++s;
     223             :         }
     224             :       }
     225             :     }
     226         139 :     box[0]=rmax;
     227         139 :     box[1]=0.0;
     228         139 :     box[2]=0.0;
     229         139 :     box[3]=0.0;
     230         139 :     box[4]=rmax;
     231         139 :     box[5]=0.0;
     232         139 :     box[6]=0.0;
     233         139 :     box[7]=0.0;
     234         139 :     box[8]=rmax;
     235         139 :   }
     236             : };
     237             : 
     238             : /// This FCC is contained in a orthogonal cubic box
     239             : ///
     240             : /// Fun facts: full cubes at 4, 32, 108, 256, 500 atoms and so on.
     241             : ///
     242             : /// At certain sizes above the ones above (36, 504), you get
     243             : /// good (100) slabs quite separated on th z axis
     244           2 : struct inscribedFaceCenteredCubic:public AtomDistribution {
     245             :   static constexpr auto id="ifcc";
     246             :   static constexpr auto doc="FCC, but in a cubic box";
     247           2 :   void frame(View<Vector> posToUpdate,
     248             :              View<double,9> box,
     249             :              unsigned /*step*/,
     250             :              Random& rng) override {
     251             :     //Here we are exploiting a little trick:
     252             :     // if you remove the even or odd atoms from a simple cubic built with our algorithm,
     253             :     //you get an fcc boxed in a cube
     254           2 :     const unsigned rmax = [&] {
     255           2 :       auto x = ceiledPerfectCube(2*posToUpdate.size());
     256             :       //rmax needs to be even for this trick to work
     257           2 :       if ( x%2==0) {
     258             :         return x;
     259             :       } else {
     260           0 :         return x+1;
     261             :       }
     262             : 
     263           2 :     }();
     264             : 
     265             :     auto s=posToUpdate.begin();
     266             :     auto e=posToUpdate.end();
     267             :     //I am using the iterators:this is slightly faster,
     268             :     // enough to overcome the cost of the vtable that I added
     269          16 :     for (unsigned k=0; k<rmax&&s!=e; ++k) {
     270         114 :       for (unsigned j=0; j<rmax&&s!=e; ++j) {
     271             :         //we choose to show only the atoms with even index: (i+j+k)%2
     272             :         //Like this we skip steps and lots of divisions
     273         500 :         for (unsigned i=(j+k)%2; i<rmax&&s!=e; i+=2) {
     274         400 :           *s = 0.5*Vector (i,j,k);
     275         400 :           ++s;
     276             :         }
     277             :       }
     278             :     }
     279           2 :     box[0]=0.5*rmax;
     280           2 :     box[1]=0.0;
     281           2 :     box[2]=0.0;
     282           2 :     box[3]=0.0;
     283           2 :     box[4]=0.5*rmax;
     284           2 :     box[5]=0.0;
     285           2 :     box[6]=0.0;
     286           2 :     box[7]=0.0;
     287           2 :     box[8]=0.5*rmax;
     288           2 :   }
     289             : };
     290             : 
     291             : /// This FCC is contained in a "standard" FCC box
     292           3 : struct tiledFaceCenteredCubic:public AtomDistribution {
     293             :   static constexpr auto id="fcc";
     294             :   static constexpr auto doc="Face Centered Cubic";
     295           3 :   void frame(View<Vector> posToUpdate,
     296             :              View<double,9> box,
     297             :              unsigned /*step*/,
     298             :              Random& rng) override {
     299             : 
     300           3 :     const unsigned rmax =ceiledPerfectCube(posToUpdate.size());
     301             : 
     302             :     auto s=posToUpdate.begin();
     303             :     auto e=posToUpdate.end();
     304             : #define X PLMD::Versors::xp<double>
     305             : #define Y PLMD::Versors::yp<double>
     306             : #define Z PLMD::Versors::zp<double>
     307             :     const auto a=sqrt(2)*0.5*(X+Y);
     308             :     const auto b=sqrt(2)*0.5*(X+Z);
     309             :     const auto c=sqrt(2)*0.5*(Y+Z);
     310             : #undef X
     311             : #undef Y
     312             : #undef Z
     313             :     //I am using the iterators:this is slightly faster,
     314             :     // enough to overcome the cost of the vtable that I added
     315          21 :     for (unsigned k=0; k<rmax&&s!=e; ++k) {
     316         120 :       for (unsigned j=0; j<rmax&&s!=e; ++j) {
     317         702 :         for (unsigned i=0; i<rmax&&s!=e; ++i) {
     318             :           *s = i*a
     319             :                +j*b
     320         600 :                +k*c;
     321         600 :           ++s;
     322             :         }
     323             :       }
     324             :     }
     325             : 
     326             :     box.subview<0,3>() = a*rmax;
     327             :     box.subview<3,3>() = b*rmax;
     328             :     box.subview<6,3>() = c*rmax;
     329           3 :   }
     330             : };
     331             : 
     332             : /// This BCC is contained in a "standard" FCC box
     333           1 : struct tiledBodyCenteredCubic:public AtomDistribution {
     334             :   static constexpr auto id="bcc";
     335             :   static constexpr auto doc="Body Centered Cubic";
     336           1 :   void frame(View<Vector> posToUpdate,
     337             :              View<double,9> box,
     338             :              unsigned /*step*/,
     339             :              Random& rng) override {
     340             :     //For the base functionality of this see the comment in the tiledSimpleCubic
     341             : 
     342           1 :     const unsigned rmax =ceiledPerfectCube(posToUpdate.size());
     343             : 
     344             :     auto s=posToUpdate.begin();
     345             :     auto e=posToUpdate.end();
     346             : #define X PLMD::Versors::xp<double>
     347             : #define Y PLMD::Versors::yp<double>
     348             : #define Z PLMD::Versors::zp<double>
     349             :     const auto a=0.5*(-X+Y+Z);
     350             :     const auto b=0.5*( X-Y+Z);
     351             :     const auto c=0.5*( X+Y-Z);
     352             : #undef X
     353             : #undef Y
     354             : #undef Z
     355             :     //I am using the iterators:this is slightly faster,
     356             :     // enough to overcome the cost of the vtable that I added
     357           7 :     for (unsigned k=0; k<rmax&&s!=e; ++k) {
     358          40 :       for (unsigned j=0; j<rmax&&s!=e; ++j) {
     359         234 :         for (unsigned i=0; i<rmax&&s!=e; ++i) {
     360             :           *s = i*a
     361             :                +j*b
     362         200 :                +k*c;
     363         200 :           ++s;
     364             :         }
     365             :       }
     366             :     }
     367             : 
     368             :     box.subview<0,3>() = a*rmax;
     369             :     box.subview<3,3>() = b*rmax;
     370             :     box.subview<6,3>() = c*rmax;
     371           1 :   }
     372             : };
     373             : 
     374             : /// This BCC is contained in a orthogonal cubic box
     375           1 : struct inscribedBodyCenteredCubic:public AtomDistribution {
     376             :   static constexpr auto id="ibcc";
     377             :   static constexpr auto doc="BCC, but in a cubic box";
     378           1 :   void frame(View<Vector> posToUpdate,
     379             :              View<double,9> box,
     380             :              unsigned /*step*/,
     381             :              Random& rng) override {
     382             :     //Here we are exploiting a little trick:
     383             :     // if you remove the even or odd atoms from a simple cubic built with our algorithm,
     384             :     //you get an bcc boxed in a cube
     385           1 :     const unsigned rmax = [&] {
     386             :       //we take one atoms every 4
     387           1 :       auto x = ceiledPerfectCube(4*posToUpdate.size());
     388             :       //rmax needs to be even for this trick to work
     389           1 :       if ( x%2==0) {
     390             :         return x;
     391             :       } else {
     392           0 :         return x+1;
     393             :       }
     394             : 
     395           1 :     }();
     396             : 
     397             :     auto s=posToUpdate.begin();
     398             :     auto e=posToUpdate.end();
     399             :     //I am using the iterators:this is slightly faster,
     400             :     // enough to overcome the cost of the vtable that I added
     401           9 :     for (unsigned k=0; k<rmax&&s!=e; ++k) {
     402             :       //we alternate on the z axis the choice between atoms with both i and j even or odd
     403             :       //The +=2 skips an extra check in the inner body
     404          48 :       for (unsigned j=k%2; j<rmax&&s!=e; j+=2) {
     405         240 :         for (unsigned i=k%2; i<rmax&&s!=e; i+=2) {
     406         200 :           *s = 0.5*Vector (i,j,k);
     407         200 :           ++s;
     408             :         }
     409             :       }
     410             :     }
     411           1 :     box[0]=0.5*rmax;
     412           1 :     box[1]=0.0;
     413           1 :     box[2]=0.0;
     414           1 :     box[3]=0.0;
     415           1 :     box[4]=0.5*rmax;
     416           1 :     box[5]=0.0;
     417           1 :     box[6]=0.0;
     418           1 :     box[7]=0.0;
     419           1 :     box[8]=0.5*rmax;
     420           1 :   }
     421             : };
     422             : 
     423             : 
     424             : ///a decorator for replicating the atomic distribution
     425             : class repliedTrajectory: public AtomDistribution {
     426             :   std::unique_ptr<AtomDistribution> distribution;
     427             :   unsigned rX=1;
     428             :   unsigned rY=1;
     429             :   unsigned rZ=1;
     430             : public:
     431             :   static constexpr auto id="reply";
     432             :   static constexpr auto doc=R"=(replicate the box by the given in all the directions:
     433             : usage:
     434             :   reply x y z)=";
     435             : 
     436          16 :   static std::unique_ptr<AtomDistribution> decorate(std::unique_ptr<AtomDistribution>&& d,
     437             :       std::string_view cmd) {
     438             :     unpackedLine lines;
     439          16 :     Tools::getWordsSimple(lines,cmd);
     440             :     unsigned repeatX;
     441             :     unsigned repeatY;
     442             :     unsigned repeatZ;
     443          16 :     plumed_assert(lines.size() ==4) << id << " supports exacly three inputs: x y z";
     444             : 
     445          32 :     Tools::convert(std::string(lines[1]),repeatX);
     446          32 :     Tools::convert(std::string(lines[2]),repeatY);
     447          16 :     Tools::convert(std::string(lines[3]),repeatZ);
     448          16 :     return std::make_unique<repliedTrajectory>(std::move(d),
     449             :            repeatX,
     450             :            repeatY,
     451          16 :            repeatZ);
     452             :   }
     453             : 
     454             :   repliedTrajectory(std::unique_ptr<AtomDistribution>&& d,
     455             :                     const unsigned repeatX,
     456             :                     const unsigned repeatY,
     457          16 :                     const unsigned repeatZ) :
     458             :     distribution(std::move(d)),
     459          16 :     rX(repeatX),
     460          16 :     rY(repeatY),
     461          16 :     rZ(repeatZ)
     462             :   {}
     463             : 
     464             : ///Generates the inner trajectory of `(nat)/(rX*rY*rZ)`, where `nat` is the dimension of the vector view, then it replicate the atoms in the various directions
     465          19 :   void frame(View<Vector> posToUpdate,
     466             :              View<double,9> box,
     467             :              unsigned step,
     468             :              Random& rng) override {
     469          19 :     plumed_assert((posToUpdate.size() % (rX*rY*rZ)==0));
     470          19 :     const auto nat = posToUpdate.size()/(rX*rY*rZ);
     471          19 :     auto coordinates=posToUpdate.subview(0,nat);
     472          19 :     distribution->frame(coordinates,box,step,rng);
     473             : 
     474             :     // repetitions
     475          19 :     auto p = posToUpdate.begin()+nat;
     476             : 
     477             :     assert((rX*rY*rZ)*nat == posToUpdate.size());
     478             : 
     479          19 :     Vector boxX(box[0],box[1],box[2]);
     480          19 :     Vector boxY(box[3],box[4],box[5]);
     481          19 :     Vector boxZ(box[6],box[7],box[8]);
     482          53 :     for (unsigned x=0; x<rX; ++x) {
     483          85 :       for (unsigned y=0; y<rY; ++y) {
     484         201 :         for (unsigned z=0; z<rZ; ++z) {
     485         150 :           if(x==0&&y==0&&z==0) {
     486          19 :             continue;
     487             :           }
     488       23531 :           for (unsigned i=0; i<nat; ++i) {
     489             :             *p=coordinates[i]
     490             :                + x * boxX
     491             :                + y * boxY
     492       23400 :                + z * boxZ;
     493       23400 :             ++p;
     494             :           }
     495             :         }
     496             :       }
     497             :     }
     498          19 :     box[0]*=rX;
     499          19 :     box[1]*=rX;
     500          19 :     box[2]*=rX;
     501             : 
     502          19 :     box[3]*=rY;
     503          19 :     box[4]*=rY;
     504          19 :     box[5]*=rY;
     505             : 
     506          19 :     box[6]*=rZ;
     507          19 :     box[7]*=rZ;
     508          19 :     box[8]*=rZ;
     509          19 :   }
     510             : 
     511             : ///See the documentation the AtomDistribution
     512          12 :   bool overrideNat(unsigned& natoms) override {
     513          12 :     distribution->overrideNat(natoms);
     514          12 :     natoms *= (rX*rY*rZ);
     515          12 :     return true;
     516             :   }
     517             : };
     518             : 
     519             : ///a decorator for scaling the atomic positions
     520             : class scaledTrajectory: public AtomDistribution {
     521             :   std::unique_ptr<AtomDistribution> distribution;
     522             :   double multiplier;
     523             : public:
     524             :   static constexpr auto id="scale";
     525             :   static constexpr auto doc=R"=(scales the atoms by a certain amount
     526             : usage:
     527             :   scale (optional: mults=2.0))=";
     528             : 
     529          12 :   static std::unique_ptr<AtomDistribution> decorate(std::unique_ptr<AtomDistribution>&& d,
     530             :       std::string_view cmd) {
     531             :     unpackedLine lines;
     532          12 :     Tools::getWordsSimple(lines,cmd);
     533          12 :     plumed_assert(lines.size() <=2) << id << " supports maximum one input";
     534          12 :     if (lines.size()==2) {
     535          12 :       double mult=2.0;
     536          12 :       Tools::convert(std::string(lines[1]), mult);
     537          12 :       return std::make_unique<scaledTrajectory>(std::move(d),mult);
     538             :     }
     539             : 
     540             :     //the default value is specified in the header
     541           0 :     return std::make_unique<scaledTrajectory>(std::move(d));
     542             :   }
     543             : 
     544             :   scaledTrajectory(std::unique_ptr<AtomDistribution>&& d,
     545          12 :                    double mult=2.0):
     546             :     distribution(std::move(d)),
     547          12 :     multiplier(mult)
     548             :   {}
     549             : 
     550          12 :   void frame(View<Vector> posToUpdate,
     551             :              View<double,9> box,
     552             :              unsigned step,
     553             :              Random& rng) override {
     554          12 :     distribution->frame(posToUpdate,box,step,rng);
     555             : 
     556        2412 :     for (auto& p:posToUpdate) {
     557        2400 :       p*=multiplier;
     558             :     }
     559             : 
     560         120 :     for (auto& b:box) {
     561         108 :       b*=multiplier;
     562             :     }
     563          12 :   }
     564             : 
     565           0 :   bool overrideNat(unsigned& natoms) override {
     566           0 :     return distribution->overrideNat(natoms);
     567             :   }
     568             : };
     569             : 
     570             : 
     571             : 
     572             : /// A decorator for displacing the atoms from the generated underline distribution
     573             : class wiggleTrajectory: public AtomDistribution {
     574             :   std::unique_ptr<AtomDistribution> distribution;
     575             :   double radius;
     576             : public:
     577             :   static constexpr auto id="wiggle";
     578             :   static constexpr auto doc=R"=(displaces all the atoms by a certain amount
     579             : usage:
     580             :   wiggle (optional: sphere radius=0.1))=";
     581           1 :   static std::unique_ptr<AtomDistribution> decorate(
     582             :     std::unique_ptr<AtomDistribution>&& d,
     583             :     std::string_view cmd) {
     584             :     unpackedLine lines;
     585           1 :     Tools::getWordsSimple(lines,cmd);
     586           1 :     plumed_assert(lines.size() <=2) << id << " supports maximum one input";
     587           1 :     if (lines.size()==2) {
     588           1 :       double displacement=0.1;
     589           1 :       Tools::convert(std::string(lines[1]), displacement);
     590           1 :       return std::make_unique<wiggleTrajectory>(std::move(d),displacement);
     591             :     }
     592             :     //the default value is specified in the header
     593           0 :     return std::make_unique<wiggleTrajectory>(std::move(d));
     594             :   }
     595             : 
     596             :   wiggleTrajectory(std::unique_ptr<AtomDistribution>&& d,
     597           1 :                    double amount=0.1) :
     598             :     distribution(std::move(d)),
     599           1 :     radius(amount)
     600             :   {}
     601             : 
     602           1 :   void frame(View<Vector> posToUpdate,
     603             :              View<double,9> box,
     604             :              unsigned step,
     605             :              Random& rng) override {
     606           1 :     distribution->frame(posToUpdate,box,step,rng);
     607             : 
     608           1 :     UniformSphericalVector usv(radius);
     609         201 :     for (auto& v: posToUpdate) {
     610         200 :       v+= usv(rng);
     611             :     }
     612           1 :   }
     613           0 :   bool overrideNat(unsigned& natoms) override {
     614           0 :     return distribution->overrideNat(natoms);
     615             :   }
     616             : };
     617             : 
     618             : 
     619             : 
     620             : /// A decorator for forcing a box in the trajectory
     621             : class forceBoxTrajectory: public AtomDistribution {
     622             :   std::unique_ptr<AtomDistribution> distribution;
     623             :   std::array<double,9> fixedbox;
     624             : public:
     625             :   static constexpr auto id="box";
     626             :   static constexpr auto doc=R"=(Forces a box on the atom distribution
     627             : usage:
     628             :   box xx yy zz
     629             :  or
     630             :   box xx xy xy yx yy yz zx zy zz)=";
     631             : 
     632           5 :   static std::unique_ptr<AtomDistribution> decorate(
     633             :     std::unique_ptr<AtomDistribution>&& d,
     634             :     std::string_view cmd) {
     635             :     unpackedLine lines;
     636           5 :     Tools::getWordsSimple(lines,cmd);
     637           5 :     plumed_assert(lines.size() == 4 || lines.size() == 10) << id << " supports either 3 or 9 numbers as input";
     638           5 :     std::array<double,9> newBox= {0.0,0.0,0.0,
     639             :                                   0.0,0.0,0.0,
     640             :                                   0.0,0.0,0.0
     641             :                                  };
     642           5 :     if (lines.size()==4) {
     643           6 :       Tools::convert(std::string(lines[1]), newBox[0]);
     644           6 :       Tools::convert(std::string(lines[2]), newBox[4]);
     645           6 :       Tools::convert(std::string(lines[3]), newBox[8]);
     646             :     } else {// we now is 9
     647           4 :       Tools::convert(std::string(lines[1]), newBox[0]);
     648           4 :       Tools::convert(std::string(lines[2]), newBox[1]);
     649           4 :       Tools::convert(std::string(lines[3]), newBox[2]);
     650           4 :       Tools::convert(std::string(lines[4]), newBox[3]);
     651           4 :       Tools::convert(std::string(lines[5]), newBox[4]);
     652           4 :       Tools::convert(std::string(lines[6]), newBox[5]);
     653           4 :       Tools::convert(std::string(lines[7]), newBox[6]);
     654           4 :       Tools::convert(std::string(lines[8]), newBox[7]);
     655           4 :       Tools::convert(std::string(lines[9]), newBox[8]);
     656             :     }
     657             :     //the default value is specified in the header
     658           5 :     return std::make_unique<forceBoxTrajectory>(std::move(d),newBox);
     659             :   }
     660             :   forceBoxTrajectory(std::unique_ptr<AtomDistribution>&& d,
     661           5 :                      const std::array<double,9>& mybox):
     662             :     distribution(std::move(d)),
     663           5 :     fixedbox(mybox)
     664             :   {}
     665             : 
     666           7 :   void frame(View<Vector> posToUpdate,
     667             :              View<double,9> box,
     668             :              unsigned step,
     669             :              Random& rng) override {
     670           7 :     distribution->frame(posToUpdate,box,step,rng);
     671           7 :     box[0] = fixedbox[0];
     672           7 :     box[1] = fixedbox[1];
     673           7 :     box[2] = fixedbox[2];
     674           7 :     box[3] = fixedbox[3];
     675           7 :     box[4] = fixedbox[4];
     676           7 :     box[5] = fixedbox[5];
     677           7 :     box[6] = fixedbox[6];
     678           7 :     box[7] = fixedbox[7];
     679           7 :     box[8] = fixedbox[8];
     680           7 :   }
     681             : 
     682           1 :   bool overrideNat(unsigned& natoms) override {
     683           1 :     return distribution->overrideNat(natoms);
     684             :   }
     685             : };
     686             : 
     687             : /// A decorator for fixing the state of the trajectory
     688             : class fixedTrajectory: public AtomDistribution {
     689             :   std::unique_ptr<AtomDistribution> distribution;
     690             :   std::array<double,9> fixedBox= {0.0,0.0,0.0,
     691             :                                   0.0,0.0,0.0,
     692             :                                   0.0,0.0,0.0
     693             :                                  };
     694             :   std::vector<Vector> positions= {};
     695             :   unsigned stride=0;
     696             :   bool generate=true;
     697             : public:
     698             :   static constexpr auto id="fix";
     699             :   static constexpr auto doc=R"=(Fixes the trajectory for stride steps, or forever if stride is 0
     700             : usage:
     701             :   fix (optional stride=0))=";
     702             : 
     703           5 :   static std::unique_ptr<AtomDistribution> decorate(
     704             :     std::unique_ptr<AtomDistribution>&& d,
     705             :     std::string_view cmd) {
     706             :     unpackedLine lines;
     707           5 :     Tools::getWordsSimple(lines,cmd);
     708           5 :     plumed_assert(lines.size() <= 2) << id << " supports only one optional parameter for the stride";
     709             : 
     710           5 :     if (lines.size()==2) {
     711           0 :       unsigned newStride=0;
     712           0 :       Tools::convert(std::string(lines[1]), newStride);
     713           0 :       return std::make_unique<fixedTrajectory>(std::move(d),newStride);
     714             :     }
     715             :     //the default value is specified in the header
     716           5 :     return std::make_unique<fixedTrajectory>(std::move(d));
     717             :   }
     718             :   fixedTrajectory(std::unique_ptr<AtomDistribution>&& d,
     719           5 :                   unsigned newStride=0):
     720             :     distribution(std::move(d)),
     721           5 :     stride(newStride)
     722             :   {}
     723             : 
     724          10 :   void frame(View<Vector> posToUpdate,
     725             :              View<double,9> box,
     726             :              unsigned step,
     727             :              Random& rng) override {
     728          10 :     generate = (!generate && stride>0) ?
     729           0 :                step%stride==0
     730             :                : generate;
     731          10 :     if (generate) {
     732           5 :       positions.resize(posToUpdate.size());
     733           5 :       distribution->frame(
     734             :         make_view(positions),
     735             :         View<double,9>(fixedBox.data()),
     736             :         step,rng);
     737           5 :       generate=false;
     738             :     }
     739          10 :     plumed_assert(positions.size() == posToUpdate.size()) << "fixed atom distributions: the expected number of atoms  changed";
     740             :     std::copy(positions.begin(),positions.end(),posToUpdate.begin());
     741          10 :     box[0] = fixedBox[0];
     742          10 :     box[1] = fixedBox[1];
     743          10 :     box[2] = fixedBox[2];
     744          10 :     box[3] = fixedBox[3];
     745          10 :     box[4] = fixedBox[4];
     746          10 :     box[5] = fixedBox[5];
     747          10 :     box[6] = fixedBox[6];
     748          10 :     box[7] = fixedBox[7];
     749          10 :     box[8] = fixedBox[8];
     750          10 :   }
     751             : 
     752           0 :   bool overrideNat(unsigned& natoms) override {
     753           0 :     return distribution->overrideNat(natoms);
     754             :   }
     755             : };
     756             : //****************************Helpers and managements***************************
     757             : 
     758             : 
     759             : namespace ADHelpers {
     760             : //How to use this:
     761             : //
     762             : //The following variants act as "registers" for the distributions and the decorators
     763             : //
     764             : //To register a new distribution, siply write the struct in the file above
     765             : //then add it to the rigth list
     766             : //
     767             : //Distributions and decorators must have a declared
     768             : //`constexpr auto id="name"` and `constexpr auto doc="documentation"` members,
     769             : //to be correcly listed and parsed
     770             : //
     771             : //To work a distribution simply needs to have a frame method, and no constructor.
     772             : //
     773             : //To add a complex distribution that needs a constructor you need to
     774             : //do some more work like the file one, in its own source AtomDistributionFiles.cpp/h.
     775             : //It is still compatible with the decorators.
     776             : //
     777             : //The decorators needs some more work:
     778             : //a decorator needs to store the unique_ptr of the distribution that modifies
     779             : //a decorator may need to store some extra settings
     780             : //a decorator needs a static method "decorate"
     781             : //  that accepts a rvalue unique_ptr of the distribution that it will modify,
     782             : //  and a string of data to be parsed,
     783             : //  this method will return a unique_ptr with the modified distribution.
     784             : //  Note that the decorator will be the new owner of the original distribution pointer
     785             : //  and that the command string will contain the declared id of the decorator ("id settings")
     786             : //a decorator will needd to override the overrideNat method, ideally only like this:
     787             : //```
     788             : //bool forceBoxTrajectory::overrideNat(unsigned& natoms) {
     789             : //  return distribution->overrideNat(natoms);
     790             : //}
     791             : //```
     792             : //  To propagate the changes in the number of atoms by all the decorators
     793             : //  applied to a distribution, it can be more complex (like in "reply")
     794             : //a decorator must overload the frame method, and call the one of the
     795             : //  stored distribution and eventually modify the results of it
     796             : //
     797             : //Note that as time of writing this, there is no check implemented to verify
     798             : // if two distributuin have the same id
     799             : 
     800             : //add a new base distribution here, and the compiler will automagically
     801             : //set up the parser and the documentation for you with some template shenanigans
     802             : using baseDistributions = std::variant<
     803             :                           theLine,
     804             :                           uniformCube,
     805             :                           uniformSphere,
     806             :                           twoGlobs,
     807             :                           tiledSimpleCubic,
     808             :                           tiledBodyCenteredCubic,
     809             :                           tiledFaceCenteredCubic,
     810             :                           inscribedBodyCenteredCubic,
     811             :                           inscribedFaceCenteredCubic>;
     812             : 
     813             : //add new decorators here:
     814             : using decoratorDistribuitions = std::variant<
     815             :                                 repliedTrajectory,
     816             :                                 scaledTrajectory,
     817             :                                 wiggleTrajectory,
     818             :                                 forceBoxTrajectory,
     819             :                                 fixedTrajectory>;
     820             : 
     821             : template <size_t I=0>
     822         589 : std::optional<std::unique_ptr<AtomDistribution>> getAD(std::string_view atomicDistr) {
     823             :   if constexpr ( I < std::variant_size_v<baseDistributions>) {
     824             :     using myAD=typename std::variant_alternative_t<I,baseDistributions>;
     825         589 :     if (atomicDistr==myAD::id) {
     826         124 :       return std::make_unique<myAD>();
     827             :     }
     828         465 :     return getAD<I+1>(atomicDistr);
     829             :   }
     830           0 :   return std::nullopt;
     831             : }
     832             : 
     833             : using parser=std::function<std::unique_ptr<AtomDistribution>(std::unique_ptr<AtomDistribution>&& d,
     834             :              std::string_view cmd)>;
     835             : 
     836             : template <size_t I=0>
     837          88 : std::optional<parser> getDecorator( std::string_view decoratorDistr) {
     838             :   if constexpr ( I < std::variant_size_v<decoratorDistribuitions>) {
     839          88 :     auto name=decoratorDistr.substr(0,decoratorDistr.find(' '));
     840             :     using myAD=typename std::variant_alternative_t<I,decoratorDistribuitions>;
     841          88 :     if (name==myAD::id) {
     842             :       return myAD::decorate;
     843             :     }
     844          49 :     return getDecorator<I+1>(decoratorDistr);
     845             :   }
     846           0 :   return std::nullopt;
     847             : }
     848             : 
     849             : template <size_t I=0>
     850           0 : void getDistributionList(std::vector<std::string>& list) {
     851             :   if constexpr ( I < std::variant_size_v<baseDistributions>) {
     852             :     using myAD=typename std::variant_alternative_t<I,baseDistributions>;
     853           0 :     list.push_back(myAD::id);
     854           0 :     getDistributionList<I+1>(list);
     855             :   }
     856           0 : }
     857             : 
     858             : template <size_t I=0>
     859           9 : void getDistributionDocumentation( std::vector<AtomDistribution::documentation>& list) {
     860             :   if constexpr ( I < std::variant_size_v<baseDistributions>) {
     861             :     using myAD=typename std::variant_alternative_t<I,baseDistributions>;
     862           9 :     list.push_back({myAD::id,myAD::doc});
     863           8 :     getDistributionDocumentation<I+1>(list);
     864             :   }
     865          18 : }
     866             : 
     867             : template <size_t I=0>
     868           0 : void getDecoratorList(std::vector<std::string>& list) {
     869             :   if constexpr ( I < std::variant_size_v<decoratorDistribuitions>) {
     870             :     using myAD=typename std::variant_alternative_t<I,decoratorDistribuitions>;
     871           0 :     list.push_back(myAD::id);
     872           0 :     getDecoratorList<I+1>(list);
     873             :   }
     874           0 : }
     875             : 
     876             : template <size_t I=0>
     877           5 : void getDecoratorDocumentation(std::vector<AtomDistribution::documentation>& list) {
     878             :   if constexpr ( I < std::variant_size_v<decoratorDistribuitions>) {
     879             :     using myAD=typename std::variant_alternative_t<I,decoratorDistribuitions>;
     880           5 :     list.push_back({myAD::id,myAD::doc});
     881           4 :     getDecoratorDocumentation<I+1>(list);
     882             :   }
     883          10 : }
     884             : 
     885           0 : std::string getDistributionList_forError() {
     886           0 :   auto list = AtomDistribution::getDistributionList();
     887           0 :   std::string toRet="";
     888           0 :   std::string pre="\"";
     889           0 :   for (const auto & distr:list) {
     890           0 :     toRet += pre+distr+ "\"";
     891             :     pre=", \"";
     892             :   }
     893           0 :   return toRet;
     894           0 : }
     895             : 
     896           0 : std::string getDecoratorList_forError() {
     897           0 :   auto list = AtomDistribution::getDecoratorsList();
     898           0 :   std::string toRet="";
     899           0 :   std::string pre="\"";
     900           0 :   for (const auto& distr:list) {
     901           0 :     toRet += pre+distr+ "\"";
     902             :     pre=", \"";
     903             :   }
     904           0 :   return toRet;
     905           0 : }
     906             : } // namespace ADHelpers
     907             : 
     908         124 : std::unique_ptr<AtomDistribution> AtomDistribution::getAtomDistribution(std::string_view atomicDistr) {
     909         124 :   auto pipePos = atomicDistr.find("|");
     910         124 :   auto base=atomicDistr.substr(0,pipePos);
     911         124 :   std::unique_ptr<AtomDistribution> distribution;
     912         124 :   if(auto d = ADHelpers::getAD(base); d) {
     913             :     distribution = std::move(*d);
     914             :   } else {
     915           0 :     plumed_error() << "The atomic distribution can be only "<<ADHelpers::getDistributionList_forError()
     916             :                    << ", but the input was \""
     917           0 :                    << base <<'"';
     918             :   }
     919         124 :   if (pipePos != std::string_view::npos) {
     920             :     return decorateAtomDistribution(std::move(distribution),
     921          12 :                                     atomicDistr.substr(pipePos+1));
     922             :   }
     923             :   return distribution;
     924         124 : }
     925             : 
     926          37 : std::unique_ptr<AtomDistribution> AtomDistribution::decorateAtomDistribution(
     927             :   std::unique_ptr<AtomDistribution> && ad,
     928             :   std::string_view decoratorDistr) {
     929             :   unpackedLine lines;
     930          37 :   Tools::getWordsSimple(lines,decoratorDistr,"|");
     931             :   std::unique_ptr<AtomDistribution> distribution=std::move(ad);
     932          76 :   for(auto i=0u; i < lines.size(); ++i) {
     933          39 :     auto decorator=ADHelpers::getDecorator(lines[i]);
     934          39 :     if (decorator) {
     935             :       auto tmp=std::move(distribution);
     936          78 :       distribution = (*decorator)(std::move(tmp),lines[i]);
     937          39 :     } else {
     938             : 
     939           0 :       plumed_error() << "The atomic distribution decorators be only "<<ADHelpers::getDecoratorList_forError()
     940             :                      << ", but the input was \""
     941           0 :                      << lines[i] <<'"';
     942             :     }
     943             : 
     944             :   }
     945          37 :   return distribution;
     946           0 : }
     947             : 
     948           0 : std::vector<std::string> AtomDistribution::getDistributionList() {
     949           0 :   std::vector<std::string> list;
     950           0 :   ADHelpers::getDistributionList(list);
     951           0 :   return list;
     952           0 : }
     953           1 : std::vector<AtomDistribution::documentation> AtomDistribution::getDistributionDocumentation() {
     954           1 :   std::vector<documentation> list;
     955           1 :   ADHelpers::getDistributionDocumentation(list);
     956           1 :   return list;
     957           0 : }
     958             : 
     959           0 : std::vector<std::string> AtomDistribution::getDecoratorsList() {
     960           0 :   std::vector<std::string> list;
     961           0 :   ADHelpers::getDecoratorList(list);
     962           0 :   return list;
     963           0 : }
     964             : 
     965           1 : std::vector<AtomDistribution::documentation> AtomDistribution::getDecoratorsDocumentation() {
     966           1 :   std::vector<documentation> list;
     967           1 :   ADHelpers::getDecoratorDocumentation(list);
     968           1 :   return list;
     969           0 : }
     970             : 
     971             : } //namespace PLMD

Generated by: LCOV version 1.16