1 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2    Copyright (c) 2011-2021 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 "Pbc.h"
23 #include "Tools.h"
24 #include "Exception.h"
25 #include "LatticeReduction.h"
26 #include <iostream>
27 #include "Random.h"
28 #include <cmath>
29 
30 namespace PLMD {
31 
Pbc()32 Pbc::Pbc():
33   type(unset)
34 {
35   box.zero();
36   invBox.zero();
37 }
38 
buildShifts(std::vector<Vector> shifts[2][2][2]) const39 void Pbc::buildShifts(std::vector<Vector> shifts[2][2][2])const {
40   const double small=1e-28;
41 
42 // clear all shifts
43   for(int i=0; i<2; i++) for(int j=0; j<2; j++) for(int k=0; k<2; k++) shifts[i][j][k].clear();
44 
45 // enumerate all possible shifts
46 // since box is reduced, only 27 shifts have to be attempted
47   for(int l=-1; l<=1; l++) for(int m=-1; m<=1; m++) for(int n=-1; n<=1; n++) {
48 
49 // int/double shift vectors
50         int ishift[3]= {l,m,n};
51         Vector dshift(l,m,n);
52 
53 // count how many components are != 0
54         unsigned count=0;
55         for(int s=0; s<3; s++) if(ishift[s]!=0) count++;
56 
57 // skips trivial (0,0,0) and cases with three shifts
58 // only 18 shifts survive past this point
59         if(count==0 || count==3) continue;
60 
61 // check if that Wigner-Seitz face is perpendicular to the axis.
62 // this allows to eliminate shifts in symmetric cells.
63 // e.g., if one lactice vector is orthogonal to the plane spanned
64 // by the other two vectors, that shift should never be tried
65         Vector cosdir=matmul(reduced,transpose(reduced),dshift);
66         double dp=dotProduct(dshift,cosdir);
67         double ref=modulo2(dshift)*modulo2(cosdir);
68         if(std::fabs(ref-dp*dp)<small) continue;
69 
70 // here we start pruning depending on the sign of the scaled coordinate
71         for(int i=0; i<2; i++) for(int j=0; j<2; j++) for(int k=0; k<2; k++) {
72 
73               int block[3]= {2*i-1,2*j-1,2*k-1};
74 
75 // skip cases where shift would bring too far from origin
76               bool skip=false;
77               for(int s=0; s<3; s++) if(ishift[s]*block[s]>0) skip=true;
78               if(skip) continue;
79               skip=true;
80               for(int s=0; s<3; s++) {
81 // check that the components of cosdir along the non-shifted directions
82 // have the proper sign
83                 if(((1-ishift[s]*ishift[s])*block[s])*cosdir[s]<-small) skip=false;
84               }
85               if(skip)continue;
86 
87 // if we arrive to this point, shift is eligible and is added to the list
88               shifts[i][j][k].push_back(matmul(transpose(reduced),dshift));
89             }
90       }
91 }
92 
fullSearch(Vector & d) const93 void Pbc::fullSearch(Vector&d)const {
94   if(type==unset) return;
95   Vector s=matmul(invReduced.transpose(),d);
96   for(int i=0; i<3; i++) s[i]=Tools::pbc(s[i]);
97   d=matmul(reduced.transpose(),s);
98   const int smax=4;
99   Vector a0(reduced.getRow(0));
100   Vector a1(reduced.getRow(1));
101   Vector a2(reduced.getRow(2));
102   Vector best(d);
103   double lbest=d.modulo2();
104   for(int i=-smax; i<=smax; i++) for(int j=-smax; j<=smax; j++) for(int k=-smax; k<=smax; k++) {
105         Vector trial=d+i*a0+j*a1+k*a2;
106         double ltrial=trial.modulo2();
107         if(ltrial<lbest) {
108           best=trial;
109           lbest=ltrial;
110         }
111       }
112   d=best;
113 }
114 
setBox(const Tensor & b)115 void Pbc::setBox(const Tensor&b) {
116   box=b;
117 // detect type:
118   const double epsilon=1e-28;
119 
120   type=unset;
121   double det=box.determinant();
122   if(det*det<epsilon) return;
123 
124   bool cxy=false;
125   bool cxz=false;
126   bool cyz=false;
127   if(box(0,1)*box(0,1)<epsilon && box(1,0)*box(1,0)<epsilon) cxy=true;
128   if(box(0,2)*box(0,2)<epsilon && box(2,0)*box(2,0)<epsilon) cxz=true;
129   if(box(1,2)*box(1,2)<epsilon && box(2,1)*box(2,1)<epsilon) cyz=true;
130 
131   invBox=box.inverse();
132 
133   if(cxy && cxz && cyz) type=orthorombic;
134   else type=generic;
135 
136   if(type==orthorombic) {
137     reduced=box;
138     invReduced=inverse(reduced);
139     for(unsigned i=0; i<3; i++) {
140       diag[i]=box[i][i];
141       hdiag[i]=0.5*box[i][i];
142       mdiag[i]=-0.5*box[i][i];
143     }
144   } else {
145     reduced=box;
146     LatticeReduction::reduce(reduced);
147     invReduced=inverse(reduced);
148     buildShifts(shifts);
149   }
150 
151 }
152 
distance(const bool pbc,const Vector & v1,const Vector & v2) const153 double Pbc::distance( const bool pbc, const Vector& v1, const Vector& v2 ) const {
154   if(pbc) { return ( distance(v1,v2) ).modulo(); }
155   else { return ( delta(v1,v2) ).modulo(); }
156 }
157 
apply(std::vector<Vector> & dlist,unsigned max_index) const158 void Pbc::apply(std::vector<Vector>& dlist, unsigned max_index) const {
159   if (max_index==0) max_index=dlist.size();
160   if(type==unset) {
161     // do nothing
162   } else if(type==orthorombic) {
163 #ifdef __PLUMED_PBC_WHILE
164     for(unsigned k=0; k<max_index; ++k) {
165       while(dlist[k][0]>hdiag[0])   dlist[k][0]-=diag[0];
166       while(dlist[k][0]<=mdiag[0])  dlist[k][0]+=diag[0];
167       while(dlist[k][1]>hdiag[1])   dlist[k][1]-=diag[1];
168       while(dlist[k][1]<=mdiag[1])  dlist[k][1]+=diag[1];
169       while(dlist[k][2]>hdiag[2])   dlist[k][2]-=diag[2];
170       while(dlist[k][2]<=mdiag[2])  dlist[k][2]+=diag[2];
171     }
172 #else
173     for(unsigned k=0; k<max_index; ++k) for(int i=0; i<3; i++) dlist[k][i]=Tools::pbc(dlist[k][i]*invBox(i,i))*box(i,i);
174 #endif
175   } else if(type==generic) {
176     for(unsigned k=0; k<max_index; ++k) dlist[k]=distance(Vector(0.0,0.0,0.0),dlist[k]);
177   } else plumed_merror("unknown pbc type");
178 }
179 
distance(const Vector & v1,const Vector & v2,int * nshifts) const180 Vector Pbc::distance(const Vector&v1,const Vector&v2,int*nshifts)const {
181   Vector d=delta(v1,v2);
182   if(type==unset) {
183     // do nothing
184   } else if(type==orthorombic) {
185 #ifdef __PLUMED_PBC_WHILE
186     for(unsigned i=0; i<3; i++) {
187       while(d[i]>hdiag[i]) d[i]-=diag[i];
188       while(d[i]<=mdiag[i]) d[i]+=diag[i];
189     }
190 #else
191     for(int i=0; i<3; i++) d[i]=Tools::pbc(d[i]*invBox(i,i))*box(i,i);
192 #endif
193   } else if(type==generic) {
194     Vector s=matmul(d,invReduced);
195 // check if images have to be computed:
196 //    if((std::fabs(s[0])+std::fabs(s[1])+std::fabs(s[2])>0.5)){
197 // NOTICE: the check in the previous line, albeit correct, is breaking many regtest
198 //         since it does not apply Tools::pbc in many cases. Moreover, it does not
199 //         introduce a significant gain. I thus leave it out for the moment.
200     if(true) {
201 // bring to -0.5,+0.5 region in scaled coordinates:
202       for(int i=0; i<3; i++) s[i]=Tools::pbc(s[i]);
203       d=matmul(s,reduced);
204 // check if shifts have to be attempted:
205       if((std::fabs(s[0])+std::fabs(s[1])+std::fabs(s[2])>0.5)) {
206 // list of shifts is specific for that "octant" (depends on signs of s[i]):
207         const std::vector<Vector> & myshifts(shifts[(s[0]>0?1:0)][(s[1]>0?1:0)][(s[2]>0?1:0)]);
208         Vector best(d);
209         double lbest(modulo2(best));
210 // loop over possible shifts:
211         if(nshifts) *nshifts+=myshifts.size();
212         for(unsigned i=0; i<myshifts.size(); i++) {
213           Vector trial=d+myshifts[i];
214           double ltrial=modulo2(trial);
215           if(ltrial<lbest) {
216             lbest=ltrial;
217             best=trial;
218           }
219         }
220         d=best;
221       }
222     }
223   } else plumed_merror("unknown pbc type");
224   return d;
225 }
226 
realToScaled(const Vector & d) const227 Vector Pbc::realToScaled(const Vector&d)const {
228   return matmul(invBox.transpose(),d);
229 }
230 
scaledToReal(const Vector & d) const231 Vector Pbc::scaledToReal(const Vector&d)const {
232   return matmul(box.transpose(),d);
233 }
234 
isOrthorombic() const235 bool Pbc::isOrthorombic()const {
236   return type==orthorombic;
237 }
238 
getBox() const239 const Tensor& Pbc::getBox()const {
240   return box;
241 }
242 
getInvBox() const243 const Tensor& Pbc::getInvBox()const {
244   return invBox;
245 }
246 
247 }
248