Line data Source code
1 : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 : Copyright (c) 2011-2023 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 "ActionWithArguments.h"
23 : #include "ActionWithValue.h"
24 : #include "ActionAtomistic.h"
25 : #include "ActionForInterface.h"
26 : #include "ActionWithVector.h"
27 : #include "ActionWithVirtualAtom.h"
28 : #include "ActionShortcut.h"
29 : #include "tools/PDB.h"
30 : #include "PlumedMain.h"
31 : #include "ActionSet.h"
32 : #include <iostream>
33 : #include <regex>
34 :
35 : namespace PLMD {
36 :
37 22686 : void ActionWithArguments::registerKeywords(Keywords& keys) {
38 : // keys.reserve("numbered","ARG","the input for this action is the scalar output from one or more other actions. The particular scalars that you will use "
39 : // "are referenced using the label of the action. If the label appears on its own then it is assumed that the Action calculates "
40 : // "a single scalar value. The value of this scalar is thus used as the input to this new action. If * or *.* appears the "
41 : // "scalars calculated by all the proceeding actions in the input file are taken. Some actions have multi-component outputs and "
42 : // "each component of the output has a specific label. For example a \\ref DISTANCE action labelled dist may have three components "
43 : // "x, y and z. To take just the x component you should use dist.x, if you wish to take all three components then use dist.*."
44 : // "More information on the referencing of Actions can be found in the section of the manual on the PLUMED \\ref Syntax. "
45 : // "Scalar values can also be "
46 : // "referenced using POSIX regular expressions as detailed in the section on \\ref Regex. To use this feature you you must compile "
47 : // "PLUMED with the appropriate flag.");
48 22686 : }
49 :
50 16202 : void ActionWithArguments::parseArgumentList(const std::string&key,std::vector<Value*>&arg) {
51 32404 : if( keywords.getArgumentType(key).length()==0 ) {
52 72 : warning("keyword " + key + " for reading arguments is registered using Keyword::add rather than Keyword::addInputKeyword. The keyword will thus not appear in the correct place in the manual");
53 : }
54 : std::string def;
55 16202 : std::vector<std::string> c;
56 16202 : arg.clear();
57 16202 : parseVector(key,c);
58 23463 : if( c.size()==0 && (keywords.style(key,"compulsory") || keywords.style(key,"hidden")) ) {
59 8 : if( keywords.getDefaultValue(key,def) ) {
60 0 : c.push_back( def );
61 : } else {
62 : return;
63 : }
64 : }
65 16194 : interpretArgumentList(c,plumed.getActionSet(),this,arg);
66 16202 : }
67 :
68 183 : bool ActionWithArguments::parseArgumentList(const std::string&key,int i,std::vector<Value*>&arg) {
69 366 : if( keywords.getArgumentType(key).length()==0 ) {
70 0 : warning("keyword " + key + " for reading argument is registered using Keyword::add rather than Keyword::addInputKeyword. The keyword will thus not appear in the correct place in the manual");
71 : }
72 183 : std::vector<std::string> c;
73 183 : arg.clear();
74 183 : if(parseNumberedVector(key,i,c)) {
75 67 : interpretArgumentList(c,plumed.getActionSet(),this,arg);
76 : return true;
77 : } else {
78 : return false;
79 : }
80 183 : }
81 :
82 : // True if readact cannot use v because not all the elements of v are computed (the action that computes v uses a mask)
83 40582 : static bool maskedValueNotAllowed( const Action* readact, Value* v ) {
84 40582 : if( v->getRank()==0 ) {
85 : return false;
86 : }
87 2373 : if( readact->keywords.exists("MASKED_INPUT_ALLOWED") || readact->keywords.exists("IS_SHORTCUT") || readact->keywords.exists("MASK") ) {
88 0 : return false;
89 : }
90 2373 : ActionWithVector* av=dynamic_cast<ActionWithVector*>( v->getPntrToAction() );
91 2373 : return av && av->getNumberOfMasks()>=0;
92 : }
93 :
94 21898 : void ActionWithArguments::interpretArgumentList(const std::vector<std::string>& c, const ActionSet& as, Action* readact, std::vector<Value*>&arg) {
95 : // ARG=* and ARG=*.* take the values of all the actions, including the ones that the shortcuts create.
96 : // The values that this action cannot use (a type it does not accept, or a vector of which only some elements
97 : // are computed, e.g. the strand pairs of PARABETARMSD with STRANDS_CUTOFF) are left out, so that e.g.
98 : // PRINT ARG=* works as in previous versions.
99 21898 : unsigned nskipped=0;
100 762 : auto addFromWildcard=[&]( Value* v ) {
101 762 : if( !readact->keywords.checkArgumentType( v->getRank(), v->hasDerivatives() ) || maskedValueNotAllowed( readact, v ) ) {
102 4 : nskipped++;
103 4 : return;
104 : }
105 758 : arg.push_back(v);
106 21898 : };
107 53531 : for(unsigned i=0; i<c.size(); i++) {
108 : // is a regex? then just interpret it. The signal is ()
109 31636 : if(!c[i].compare(0,1,"(")) {
110 251 : unsigned l=c[i].length();
111 251 : if(!c[i].compare(l-1,1,")")) {
112 : // start regex parsing
113 : bool found_something=false;
114 : // take the string enclosed in quotes and put in round brackets
115 250 : std::string myregex=c[i];
116 250 : std::vector<ActionWithValue*> all=as.select<ActionWithValue*>();
117 250 : if( all.empty() ) {
118 1 : readact->error("your input file is not telling plumed to calculate anything");
119 : }
120 :
121 : try {
122 250 : std::regex txt_regex(myregex,std::regex::extended);
123 249 : plumed_massert(txt_regex.mark_count()==1,"I can parse with only one subexpression");
124 24618 : for(unsigned j=0; j<all.size(); j++) {
125 24369 : std::vector<std::string> ss=all[j]->getComponentsVector();
126 352871 : for(unsigned k=0; k<ss.size(); ++k) {
127 328502 : if(std::regex_match(ss[k],txt_regex)) {
128 21994 : arg.push_back(all[j]->copyOutput(ss[k]));
129 : found_something=true;
130 : }
131 : }
132 24369 : }
133 250 : } catch(std::regex_error & e) {
134 3 : plumed_error()<<"Error parsing regular expression: "<<e.what();
135 1 : }
136 249 : if(!found_something) {
137 0 : plumed_error()<<"There isn't any action matching your regex " << myregex;
138 : }
139 : } else {
140 2 : plumed_merror("did you want to use regexp to input arguments? enclose it between two round braces (...) with no spaces!");
141 : }
142 : } else {
143 : std::size_t dot=c[i].find_first_of('.');
144 31385 : std::string a=c[i].substr(0,dot);
145 31385 : std::string name=c[i].substr(dot+1);
146 31385 : if(c[i].find(".")!=std::string::npos) { // if it contains a dot:
147 9531 : if(a=="*" && name=="*") {
148 : // Take all values from all actions
149 1 : std::vector<ActionWithValue*> all=as.select<ActionWithValue*>();
150 1 : if( all.empty() ) {
151 0 : readact->error("your input file is not telling plumed to calculate anything");
152 : }
153 17 : for(unsigned j=0; j<all.size(); j++) {
154 16 : plumed_assert(all[j]); // needed for following calls, see #1046
155 16 : ActionForInterface* ap=all[j]->castToActionForInterface();
156 16 : if( ap ) {
157 8 : continue;
158 : }
159 18 : for(unsigned k=0; k<all[j]->getNumberOfComponents(); ++k) {
160 10 : addFromWildcard(all[j]->copyOutput(k));
161 : }
162 : }
163 9530 : } else if ( name=="*") {
164 : unsigned carg=arg.size();
165 : // Take all the values from an action with a specific name
166 778 : ActionShortcut* shortcut=as.getShortcutActionWithLabel(a);
167 778 : if( shortcut ) {
168 952 : shortcut->interpretDataLabel( a + "." + name, readact, arg );
169 : }
170 778 : if( arg.size()==carg ) {
171 : // Take all the values from an action with a specific name
172 517 : ActionWithValue* action=as.selectWithLabel<ActionWithValue*>(a);
173 517 : if(!action) {
174 0 : std::string str=" (hint! the actions with value in this ActionSet are: ";
175 0 : str+=as.getLabelList<ActionWithValue*>()+")";
176 0 : readact->error("cannot find action named " + a + str);
177 : }
178 517 : if( action->getNumberOfComponents()==0 ) {
179 0 : readact->error("found " + a +".* indicating use all components calculated by action with label " + a + " but this action has no components");
180 : }
181 7773 : for(unsigned k=0; k<action->getNumberOfComponents(); ++k) {
182 7256 : arg.push_back(action->copyOutput(k));
183 : }
184 : }
185 8752 : } else if ( a=="*" ) {
186 17 : std::vector<ActionShortcut*> shortcuts=as.select<ActionShortcut*>();
187 : // Take components from all actions with a specific name
188 17 : std::vector<ActionWithValue*> all=as.select<ActionWithValue*>();
189 17 : if( all.empty() ) {
190 0 : readact->error("your input file is not telling plumed to calculate anything");
191 : }
192 : unsigned carg=arg.size();
193 171 : for(unsigned j=0; j<shortcuts.size(); ++j) {
194 308 : shortcuts[j]->interpretDataLabel( shortcuts[j]->getShortcutLabel() + "." + name, readact, arg );
195 : }
196 : unsigned nval=0;
197 333 : for(unsigned j=0; j<all.size(); j++) {
198 : std::string flab;
199 632 : flab=all[j]->getLabel() + "." + name;
200 316 : if( all[j]->exists(flab) ) {
201 44 : arg.push_back(all[j]->copyOutput(flab));
202 44 : nval++;
203 : }
204 : }
205 17 : if(nval==0 && arg.size()==carg) {
206 0 : readact->error("found no actions with a component called " + name );
207 : }
208 : } else {
209 : // Take values with a specific name
210 8735 : ActionWithValue* action=as.selectWithLabel<ActionWithValue*>(a);
211 8735 : ActionShortcut* shortcut=as.getShortcutActionWithLabel(a);
212 8735 : if( !shortcut && !action ) {
213 0 : std::string str=" (hint! the actions with value in this ActionSet are: ";
214 0 : str+=as.getLabelList<ActionWithValue*>()+")";
215 0 : readact->error("cannot find action named " + a +str);
216 8735 : } else if( action && action->exists(c[i]) ) {
217 8621 : arg.push_back(action->copyOutput(c[i]));
218 114 : } else if( shortcut ) {
219 : unsigned narg=arg.size();
220 228 : shortcut->interpretDataLabel( a + "." + name, readact, arg );
221 114 : if( arg.size()==narg ) {
222 0 : readact->error("found no element in " + a + " with label " + name );
223 : }
224 : } else {
225 0 : std::string str=" (hint! the components in this actions are: ";
226 0 : str+=action->getComponentsList()+")";
227 0 : readact->error("action " + a + " has no component named " + name + str);
228 : }
229 : }
230 : } else { // if it doesn't contain a dot
231 21854 : if(c[i]=="*") {
232 : // Take all values from all actions
233 113 : std::vector<ActionWithValue*> all=as.select<ActionWithValue*>();
234 113 : if( all.empty() ) {
235 0 : readact->error("your input file is not telling plumed to calculate anything");
236 : }
237 1711 : for(unsigned j=0; j<all.size(); j++) {
238 1598 : plumed_assert(all[j]); // needed for following calls, see #1046
239 1598 : ActionWithVirtualAtom* av=all[j]->castToActionWithVirtualAtom();
240 1598 : if( av ) {
241 57 : continue;
242 : }
243 1541 : ActionForInterface* ap=all[j]->castToActionForInterface();
244 1541 : if( ap && all[j]->getName()!="ENERGY" ) {
245 882 : continue;
246 : }
247 1411 : for(unsigned k=0; k<all[j]->getNumberOfComponents(); ++k) {
248 752 : addFromWildcard(all[j]->copyOutput(k));
249 : }
250 : }
251 : } else {
252 21741 : ActionWithValue* action=as.selectWithLabel<ActionWithValue*>(c[i]);
253 21741 : if(!action) {
254 1 : std::string str=" (hint! the actions with value in this ActionSet are: ";
255 1 : str+=as.getLabelList<ActionWithValue*>()+")";
256 2 : readact->error("cannot find action named " + c[i] + str );
257 : }
258 21740 : if( !(action->exists(c[i])) ) {
259 0 : std::string str=" (hint! the components in this actions are: ";
260 0 : str+=action->getComponentsList()+")";
261 0 : readact->error("action " + c[i] + " has no component named " + c[i] +str);
262 : };
263 21741 : arg.push_back(action->copyOutput(c[i]));
264 : }
265 : }
266 : }
267 : }
268 21895 : if( nskipped>0 ) {
269 1 : readact->log.printf(" %u values selected with * are not used: this action cannot use their type, or not all their elements are computed\n", nskipped);
270 : }
271 82744 : for(unsigned i=0; i<arg.size(); ++i) {
272 60850 : if( !readact->keywords.checkArgumentType( arg[i]->getRank(), arg[i]->hasDerivatives() ) ) {
273 : std::string type;
274 1 : if( arg[i]->getRank()>0 && arg[i]->hasDerivatives() ) {
275 : type = "function on a grid";
276 1 : } else if( arg[i]->getRank()==0 ) {
277 : type = "scalar";
278 1 : } else if( arg[i]->getRank()==1 ) {
279 : type = "vector";
280 0 : } else if( arg[i]->getRank()==2 ) {
281 : type = "matrix";
282 : } else {
283 1 : type = "value of rank " + std::to_string(arg[i]->getRank());
284 : }
285 : std::string accepted;
286 2 : for(const auto & kw : readact->keywords.getArgumentKeys() ) {
287 3 : accepted += " " + kw + " (" + readact->keywords.getArgumentType(kw) + ")";
288 1 : }
289 5 : readact->error("argument " + arg[i]->getName() + " is a " + type + ", which is not accepted by " + readact->getName() + ". Accepted types:" + accepted );
290 : }
291 : }
292 21894 : if( readact->keywords.exists("MASKED_INPUT_ALLOWED") || readact->keywords.exists("IS_SHORTCUT") || readact->keywords.exists("MASK") ) {
293 14608 : return;
294 : }
295 47106 : for(unsigned i=0; i<arg.size(); ++i) {
296 39820 : if( maskedValueNotAllowed( readact, arg[i] ) ) {
297 0 : readact->error("cannot use argument " + arg[i]->getName() + " in input as not all elements are computed");
298 : }
299 : }
300 : }
301 :
302 0 : void ActionWithArguments::expandArgKeywordInPDB( const PDB& pdb ) {
303 0 : std::vector<std::string> arg_names = pdb.getArgumentNames();
304 0 : if( arg_names.size()>0 ) {
305 0 : std::vector<Value*> arg_vals;
306 0 : interpretArgumentList( arg_names, plumed.getActionSet(), this, arg_vals );
307 : }
308 0 : }
309 :
310 13478 : void ActionWithArguments::requestArguments(const std::vector<Value*> &arg) {
311 13478 : plumed_massert(!lockRequestArguments,"requested argument list can only be changed in the prepare() method");
312 13478 : arguments=arg;
313 13478 : clearDependencies();
314 : std::string fullname;
315 : std::string argName;
316 57792 : for(unsigned i=0; i<arguments.size(); i++) {
317 44314 : fullname=arguments[i]->getName();
318 44314 : if(fullname.find(".")!=std::string::npos) {
319 : std::size_t dot=fullname.find_first_of('.');
320 52616 : argName=fullname.substr(0,dot);
321 : } else {
322 : argName=fullname;
323 : }
324 44314 : ActionWithValue* action=plumed.getActionSet().selectWithLabel<ActionWithValue*>(argName);
325 44314 : plumed_massert(action,"cannot find action named (in requestArguments - this is weird)" + argName);
326 44314 : addDependency(action);
327 : }
328 13478 : ActionWithValue* av=dynamic_cast<ActionWithValue*>(this);
329 13478 : if(av) {
330 950 : av->firststep=true;
331 : }
332 13478 : }
333 :
334 38 : void ActionWithArguments::requestExtraDependencies(const std::vector<Value*> &extra) {
335 38 : plumed_massert(!lockRequestArguments,"requested argument list can only be changed in the prepare() method");
336 : std::string fullname;
337 : std::string argName;
338 77 : for(unsigned i=0; i<extra.size(); i++) {
339 39 : fullname=extra[i]->getName();
340 39 : if(fullname.find(".")!=std::string::npos) {
341 : std::size_t dot=fullname.find_first_of('.');
342 64 : argName=fullname.substr(0,dot);
343 : } else {
344 : argName=fullname;
345 : }
346 39 : ActionWithValue* action=plumed.getActionSet().selectWithLabel<ActionWithValue*>(argName);
347 39 : plumed_massert(action,"cannot find action named (in requestArguments - this is weird)" + argName);
348 39 : addDependency(action);
349 : }
350 38 : }
351 :
352 13409 : ActionWithArguments::ActionWithArguments(const ActionOptions&ao):
353 : Action(ao),
354 13409 : lockRequestArguments(false) {
355 13410 : if( keywords.exists("ARG") ) {
356 12517 : std::vector<Value*> arg;
357 25033 : parseArgumentList("ARG",arg);
358 :
359 12516 : if(!arg.empty()) {
360 12057 : log.printf(" with arguments : \n");
361 53747 : for(unsigned i=0; i<arg.size(); i++) {
362 41690 : if( arg[i]->hasDerivatives() && arg[i]->getRank()>0 ) {
363 446 : log.printf(" function on grid with label %s \n",arg[i]->getName().c_str());
364 41244 : } else if( arg[i]->getRank()==2 ) {
365 2877 : log.printf(" matrix with label %s \n",arg[i]->getName().c_str());
366 38367 : } else if( arg[i]->getRank()==1 ) {
367 6846 : log.printf(" vector with label %s \n",arg[i]->getName().c_str());
368 31521 : } else if( arg[i]->getRank()==0 ) {
369 31521 : log.printf(" scalar with label %s \n",arg[i]->getName().c_str());
370 : } else {
371 1 : error("type of argument does not make sense");
372 : }
373 : }
374 : }
375 12516 : requestArguments(arg);
376 : }
377 13408 : }
378 :
379 58 : void ActionWithArguments::calculateNumericalDerivatives( ActionWithValue* a ) {
380 58 : if(!a) {
381 58 : a=castToActionWithValue();
382 58 : plumed_massert(a,"cannot compute numerical derivatives for an action without values");
383 : }
384 :
385 : const size_t nval=a->getNumberOfComponents();
386 : const size_t npar=arguments.size();
387 58 : std::vector<double> value (nval*npar);
388 161 : for(unsigned i=0; i<npar; i++) {
389 103 : double arg0=arguments[i]->get();
390 103 : arguments[i]->set(arg0+std::sqrt(epsilon));
391 103 : a->calculate();
392 103 : arguments[i]->set(arg0);
393 1367 : for(unsigned j=0; j<nval; j++) {
394 1264 : value[i*nval+j]=a->getOutputQuantity(j);
395 : }
396 : }
397 58 : a->calculate();
398 58 : a->clearDerivatives();
399 1192 : for(unsigned j=0; j<nval; j++) {
400 1134 : Value* v=a->copyOutput(j);
401 1134 : if( v->hasDerivatives() )
402 286 : for(unsigned i=0; i<npar; i++) {
403 208 : v->addDerivative(i,(value[i*nval+j]-a->getOutputQuantity(j))/std::sqrt(epsilon));
404 : }
405 : }
406 58 : }
407 :
408 3048 : double ActionWithArguments::getProjection(unsigned i,unsigned j)const {
409 3048 : plumed_massert(i<arguments.size()," making projections with an index which is too large");
410 3048 : plumed_massert(j<arguments.size()," making projections with an index which is too large");
411 3048 : const Value* v1=arguments[i];
412 3048 : const Value* v2=arguments[j];
413 3048 : return Value::projection(*v1,*v2);
414 : }
415 :
416 230728 : void ActionWithArguments::addForcesOnArguments( const unsigned& argstart, const std::vector<double>& forces, unsigned& ind ) {
417 230728 : unsigned nargs=arguments.size();
418 230728 : const ActionWithVector* av=dynamic_cast<const ActionWithVector*>( this );
419 230728 : if( av && av->getNumberOfMasks()>0 ) {
420 1088 : nargs=nargs-av->getNumberOfMasks();
421 : }
422 542911 : for(unsigned i=0; i<nargs; ++i) {
423 312183 : ind += arguments[i]->addForces(View(&forces[ind],forces.size()-ind));
424 : }
425 230728 : }
426 :
427 1672 : void ActionWithArguments::setGradients( Value* myval, unsigned& start ) const {
428 1672 : if( !myval->hasDeriv ) {
429 : return;
430 : }
431 1012 : plumed_assert( myval->getRank()==0 );
432 :
433 : bool scalar=true;
434 3145 : for(unsigned i=0; i<arguments.size(); ++i ) {
435 2133 : if( arguments[i]->getRank()!=0 ) {
436 : scalar=false;
437 : break;
438 : }
439 : }
440 1012 : if( !scalar ) {
441 : bool constant=true;
442 0 : for(unsigned i=0; i<arguments.size(); ++i ) {
443 0 : if( !arguments[i]->isConstant() ) {
444 : constant=false;
445 : break;
446 : } else {
447 0 : start += arguments[i]->getNumberOfValues();
448 : }
449 : }
450 0 : if( !constant ) {
451 0 : error("cannot set gradient as unable to handle non-constant actions that take vectors/matrices/grids in input");
452 : }
453 : }
454 : // Now pass the gradients
455 3145 : for(unsigned i=0; i<arguments.size(); ++i ) {
456 2133 : arguments[i]->passGradients( myval->getDerivative(i), myval->gradients );
457 : }
458 : }
459 :
460 24705 : bool ActionWithArguments::calculateConstantValues( const bool& haveatoms ) {
461 24705 : ActionWithValue* awval = castToActionWithValue();
462 24705 : if( !awval || arguments.size()==0 ) {
463 : return false;
464 : }
465 : bool constant = true, atoms=false;
466 19449 : for(unsigned i=0; i<arguments.size(); ++i) {
467 19126 : auto * ptr=arguments[i]->getPntrToAction();
468 19126 : plumed_assert(ptr); // needed for following calls, see #1046
469 19126 : ActionAtomistic* aa=ptr->castToActionAtomistic();
470 19126 : if( aa ) {
471 12765 : ActionWithVector* awvec=dynamic_cast<ActionWithVector*>( arguments[i]->getPntrToAction() );
472 12765 : if( !awvec || aa->getNumberOfAtoms()>0 ) {
473 : atoms=true;
474 : }
475 : }
476 19126 : if( !arguments[i]->isConstant() ) {
477 : constant=false;
478 : break;
479 : }
480 : }
481 18631 : if( constant ) {
482 : // Set everything constant first as we need to set the shape
483 670 : for(unsigned i=0; i<awval->getNumberOfComponents(); ++i) {
484 347 : (awval->copyOutput(i))->setConstant();
485 : }
486 323 : if( !haveatoms ) {
487 305 : log.printf(" values stored by this action are computed during startup and stay fixed during the simulation\n");
488 : }
489 323 : if( atoms ) {
490 36 : return haveatoms;
491 : }
492 : }
493 : // Now do the calculation and store the values if we don't need anything from the atoms
494 287 : if( constant && !haveatoms ) {
495 : plumed_assert( !atoms );
496 287 : activate();
497 287 : calculate();
498 287 : deactivate();
499 598 : for(unsigned i=0; i<awval->getNumberOfComponents(); ++i) {
500 311 : unsigned nv = awval->copyOutput(i)->getNumberOfValues();
501 311 : log.printf(" %d values stored in component labelled %s are : ", nv, (awval->copyOutput(i))->getName().c_str() );
502 6789 : for(unsigned j=0; j<nv; ++j) {
503 6478 : log.printf(" %f", (awval->copyOutput(i))->get(j) );
504 : }
505 311 : log.printf("\n");
506 : }
507 : }
508 : return constant;
509 : }
510 :
511 : }
|