Line data Source code
1 : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 : Copyright (c) 2011-2019 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 "ActionRegister.h"
23 : #include "Function.h"
24 :
25 : #include "lepton/Lepton.h"
26 :
27 : using namespace std;
28 :
29 : namespace PLMD {
30 : namespace function {
31 :
32 : //+PLUMEDOC FUNCTION CUSTOM
33 : /*
34 : Calculate a combination of variables using a custom expression.
35 :
36 : This action computes an arbitrary function of one or more
37 : collective variables. Arguments are chosen with the ARG keyword,
38 : and the function is provided with the FUNC string. Notice that this
39 : string should contain no space. Within FUNC, one can refer to the
40 : arguments as x,y,z, and t (up to four variables provided as ARG).
41 : This names can be customized using the VAR keyword (see examples below).
42 :
43 : This function is implemented using the Lepton library, that allows to evaluate
44 : algebraic expressions and to automatically differentiate them.
45 :
46 : If you want a function that depends not only on collective variables
47 : but also on time you can use the \subpage TIME action.
48 :
49 : \par Examples
50 :
51 : The following input tells plumed to perform a metadynamics
52 : using as a CV the difference between two distances.
53 : \plumedfile
54 : dAB: DISTANCE ATOMS=10,12
55 : dAC: DISTANCE ATOMS=10,15
56 : diff: CUSTOM ARG=dAB,dAC FUNC=y-x PERIODIC=NO
57 : # notice: the previous line could be replaced with the following
58 : # diff: COMBINE ARG=dAB,dAC COEFFICIENTS=-1,1
59 : METAD ARG=diff SIGMA=0.1 HEIGHT=0.5 BIASFACTOR=10 PACE=100
60 : \endplumedfile
61 : (see also \ref DISTANCE, \ref COMBINE, and \ref METAD).
62 : Notice that forces applied to diff will be correctly propagated
63 : to atoms 10, 12, and 15.
64 : Also notice that since CUSTOM is used without the VAR option
65 : the two arguments should be referred to as x and y in the expression FUNC.
66 : For simple functions
67 : such as this one it is possible to use \ref COMBINE.
68 :
69 : The following input tells plumed to print the angle between vectors
70 : identified by atoms 1,2 and atoms 2,3
71 : its square (as computed from the x,y,z components) and the distance
72 : again as computed from the square root of the square.
73 : \plumedfile
74 : DISTANCE LABEL=d1 ATOMS=1,2 COMPONENTS
75 : DISTANCE LABEL=d2 ATOMS=2,3 COMPONENTS
76 : CUSTOM ...
77 : LABEL=theta
78 : ARG=d1.x,d1.y,d1.z,d2.x,d2.y,d2.z
79 : VAR=ax,ay,az,bx,by,bz
80 : FUNC=acos((ax*bx+ay*by+az*bz)/sqrt((ax*ax+ay*ay+az*az)*(bx*bx+by*by+bz*bz)))
81 : PERIODIC=NO
82 : ... CUSTOM
83 : PRINT ARG=theta
84 : \endplumedfile
85 : (See also \ref PRINT and \ref DISTANCE).
86 :
87 : Notice that this action implements a large number of functions (trigonometric, exp, log, etc).
88 : Among the useful functions, have a look at the step function (that is the Heaviside function).
89 : `step(x)` is defined as 1 when `x` is positive and `0` when x is negative. This allows for
90 : a straightforward implementation of if clauses.
91 :
92 : For example, imagine that you want to implement a restraint that only acts when a
93 : distance is larger than 0.5. You can do it with
94 : \plumedfile
95 : d: DISTANCE ATOMS=10,15
96 : m: CUSTOM ARG=d FUNC=0.5*step(0.5-x)+x*step(x-0.5) PERIODIC=NO
97 : # check the function you are applying:
98 : PRINT ARG=d,m FILE=checkme
99 : RESTRAINT ARG=d AT=0.5 KAPPA=10.0
100 : \endplumedfile
101 : (see also \ref DISTANCE, \ref PRINT, and \ref RESTRAINT)
102 :
103 : The meaning of the function `0.5*step(0.5-x)+x*step(x-0.5)` is:
104 : - If x<0.5 (step(0.5-x)!=0) use 0.5
105 : - If x>0.5 (step(x-0.5)!=0) use x
106 : Notice that the same could have been obtained using an \ref UPPER_WALLS
107 : However, with CUSTOM you can create way more complex definitions.
108 :
109 : \warning If you apply forces on the variable (as in the previous example) you should
110 : make sure that the variable is continuous!
111 : Conversely, if you are just analyzing a trajectory you can safely use
112 : discontinuous variables.
113 :
114 : A possible continuity check with gnuplot is
115 : \verbatim
116 : # this allow to step function to be used in gnuplot:
117 : gnuplot> step(x)=0.5*(erf(x*10000000)+1)
118 : # here you can test your function
119 : gnuplot> p 0.5*step(0.5-x)+x*step(x-0.5)
120 : \endverbatim
121 :
122 : Also notice that you can easily make logical operations on the conditions that you
123 : create. The equivalent of the AND operator is the product: `step(1.0-x)*step(x-0.5)` is
124 : only equal to 1 when x is between 0.5 and 1.0. By combining negation and AND you can obtain an OR. That is,
125 : `1-step(1.0-x)*step(x-0.5)` is only equal to 1 when x is outside the 0.5-1.0 interval.
126 :
127 : CUSTOM can be used in combination with \ref DISTANCE to implement variants of the
128 : DISTANCE keyword that were present in PLUMED 1.3 and that allowed to compute
129 : the distance of a point from a line defined by two other points, or the progression
130 : along that line.
131 : \plumedfile
132 : # take center of atoms 1 to 10 as reference point 1
133 : p1: CENTER ATOMS=1-10
134 : # take center of atoms 11 to 20 as reference point 2
135 : p2: CENTER ATOMS=11-20
136 : # take center of atoms 21 to 30 as reference point 3
137 : p3: CENTER ATOMS=21-30
138 :
139 : # compute distances
140 : d12: DISTANCE ATOMS=p1,p2
141 : d13: DISTANCE ATOMS=p1,p3
142 : d23: DISTANCE ATOMS=p2,p3
143 :
144 : # compute progress variable of the projection of point p3
145 : # along the vector joining p1 and p2
146 : # notice that progress is measured from the middle point
147 : onaxis: CUSTOM ARG=d13,d23,d12 FUNC=(0.5*(y^2-x^2)/z) PERIODIC=NO
148 :
149 : # compute between point p3 and the vector joining p1 and p2
150 : fromaxis: CUSTOM ARG=d13,d23,d12,onaxis VAR=x,y,z,o FUNC=(0.5*(y^2+x^2)-o^2-0.25*z^2) PERIODIC=NO
151 :
152 : PRINT ARG=onaxis,fromaxis
153 :
154 : \endplumedfile
155 :
156 : Notice that these equations have been used to combine \ref RMSD
157 : from different snapshots of a protein so as to define
158 : progression (S) and distance (Z) variables \cite perez2015atp.
159 :
160 :
161 : */
162 : //+ENDPLUMEDOC
163 :
164 :
165 840 : class Custom :
166 : public Function
167 : {
168 : lepton::CompiledExpression expression;
169 : std::vector<lepton::CompiledExpression> expression_deriv;
170 : vector<string> var;
171 : string func;
172 : vector<double> values;
173 : vector<char*> names;
174 : vector<double*> lepton_ref;
175 : vector<double*> lepton_ref_deriv;
176 : public:
177 : explicit Custom(const ActionOptions&);
178 : void calculate() override;
179 : static void registerKeywords(Keywords& keys);
180 : };
181 :
182 7840 : PLUMED_REGISTER_ACTION(Custom,"CUSTOM")
183 :
184 : //+PLUMEDOC FUNCTION MATHEVAL
185 : /*
186 : An alias to the \ref CUSTOM function.
187 :
188 : This alias is kept in order to maintain compatibility with previous PLUMED versions.
189 : However, notice that as of PLUMED 2.5 the libmatheval library is not linked anymore,
190 : and the \ref MATHEVAL function is implemented using the Lepton library.
191 :
192 : \par Examples
193 :
194 : Just replace \ref CUSTOM with \ref MATHEVAL.
195 :
196 : \plumedfile
197 : d: DISTANCE ATOMS=10,15
198 : m: MATHEVAL ARG=d FUNC=0.5*step(0.5-x)+x*step(x-0.5) PERIODIC=NO
199 : # check the function you are applying:
200 : PRINT ARG=d,m FILE=checkme
201 : RESTRAINT ARG=d AT=0.5 KAPPA=10.0
202 : \endplumedfile
203 : (see also \ref DISTANCE, \ref PRINT, and \ref RESTRAINT)
204 :
205 : */
206 : //+ENDPLUMEDOC
207 :
208 : class Matheval :
209 : public Custom {
210 : };
211 :
212 8384 : PLUMED_REGISTER_ACTION(Custom,"MATHEVAL")
213 :
214 282 : void Custom::registerKeywords(Keywords& keys) {
215 282 : Function::registerKeywords(keys);
216 846 : keys.use("ARG"); keys.use("PERIODIC");
217 1128 : keys.add("compulsory","FUNC","the function you wish to evaluate");
218 1128 : keys.add("optional","VAR","the names to give each of the arguments in the function. If you have up to three arguments in your function you can use x, y and z to refer to them. Otherwise you must use this flag to give your variables names.");
219 282 : }
220 :
221 280 : Custom::Custom(const ActionOptions&ao):
222 : Action(ao),
223 : Function(ao),
224 : expression_deriv(getNumberOfArguments()),
225 : values(getNumberOfArguments()),
226 : names(getNumberOfArguments()),
227 : lepton_ref(getNumberOfArguments(),nullptr),
228 1680 : lepton_ref_deriv(getNumberOfArguments()*getNumberOfArguments(),nullptr)
229 : {
230 560 : parseVector("VAR",var);
231 280 : if(var.size()==0) {
232 267 : var.resize(getNumberOfArguments());
233 267 : if(getNumberOfArguments()>3)
234 0 : error("Using more than 3 arguments you should explicitly write their names with VAR");
235 267 : if(var.size()>0) var[0]="x";
236 267 : if(var.size()>1) var[1]="y";
237 267 : if(var.size()>2) var[2]="z";
238 : }
239 280 : if(var.size()!=getNumberOfArguments())
240 0 : error("Size of VAR array should be the same as number of arguments");
241 560 : parse("FUNC",func);
242 280 : addValueWithDerivatives();
243 280 : checkRead();
244 :
245 280 : log.printf(" with function : %s\n",func.c_str());
246 280 : log.printf(" with variables :");
247 1040 : for(unsigned i=0; i<var.size(); i++) log.printf(" %s",var[i].c_str());
248 280 : log.printf("\n");
249 :
250 560 : lepton::ParsedExpression pe=lepton::Parser::parse(func).optimize(lepton::Constants());
251 280 : log<<" function as parsed by lepton: "<<pe<<"\n";
252 280 : expression=pe.createCompiledExpression();
253 1204 : for(auto &p: expression.getVariables()) {
254 364 : if(std::find(var.begin(),var.end(),p)==var.end()) {
255 0 : error("variable " + p + " is not defined");
256 : }
257 : }
258 280 : log<<" derivatives as computed by lepton:\n";
259 1040 : for(unsigned i=0; i<getNumberOfArguments(); i++) {
260 1520 : lepton::ParsedExpression pe=lepton::Parser::parse(func).differentiate(var[i]).optimize(lepton::Constants());
261 380 : log<<" "<<pe<<"\n";
262 760 : expression_deriv[i]=pe.createCompiledExpression();
263 : }
264 :
265 1040 : for(unsigned i=0; i<getNumberOfArguments(); i++) {
266 : try {
267 760 : lepton_ref[i]=&expression.getVariableReference(var[i]);
268 16 : } catch(const PLMD::lepton::Exception& exc) {
269 : // this is necessary since in some cases lepton things a variable is not present even though it is present
270 : // e.g. func=0*x
271 : }
272 : }
273 1040 : for(unsigned i=0; i<getNumberOfArguments(); i++) {
274 2056 : for(unsigned j=0; j<getNumberOfArguments(); j++) {
275 : try {
276 3352 : lepton_ref_deriv[i*getNumberOfArguments()+j]=&expression_deriv[i].getVariableReference(var[j]);
277 436 : } catch(const PLMD::lepton::Exception& exc) {
278 : // this is necessary since in some cases lepton things a variable is not present even though it is present
279 : // e.g. func=0*x
280 : }
281 : }
282 : }
283 280 : }
284 :
285 25790 : void Custom::calculate() {
286 52221 : for(unsigned i=0; i<getNumberOfArguments(); i++) {
287 79213 : if(lepton_ref[i]) *lepton_ref[i]=getArgument(i);
288 : }
289 25790 : setValue(expression.evaluate());
290 52221 : for(unsigned i=0; i<getNumberOfArguments(); i++) {
291 29189 : for(unsigned j=0; j<getNumberOfArguments(); j++) {
292 109216 : if(lepton_ref_deriv[i*getNumberOfArguments()+j]) *lepton_ref_deriv[i*getNumberOfArguments()+j]=getArgument(j);
293 : }
294 52862 : setDerivative(i,expression_deriv[i].evaluate());
295 : }
296 25790 : }
297 :
298 : }
299 5874 : }
300 :
301 :
|