1 // -*- c++ -*-
2 
3 // This file is part of the Collective Variables module (Colvars).
4 // The original version of Colvars and its updates are located at:
5 // https://github.com/Colvars/colvars
6 // Please update all Colvars source files before making any changes.
7 // If you wish to distribute your changes, please submit them to the
8 // Colvars repository at GitHub.
9 
10 
11 #include "colvarmodule.h"
12 #include "colvarproxy.h"
13 #include "colvardeps.h"
14 
15 
colvardeps()16 colvardeps::colvardeps()
17 {
18   time_step_factor = 1;
19 }
20 
21 
~colvardeps()22 colvardeps::~colvardeps() {
23   size_t i;
24 
25   // Protest if we are deleting an object while a parent object may still depend on it
26   if (parents.size()) {
27     cvm::log("Warning: destroying \"" + description + "\" before its parents objects:");
28     for (i=0; i<parents.size(); i++) {
29       cvm::log(parents[i]->description);
30     }
31   }
32 
33   // Do not delete features if it's a static object
34   // may change in the future though
35 //     for (i=0; i<features.size(); i++) {
36 //       if (features[i] != NULL) delete features[i];
37 //     }
38 
39   remove_all_children();
40 }
41 
42 
free_children_deps()43 void colvardeps::free_children_deps() {
44   // Dereference children requirements of all enabled features
45   // Useful when object is destroyed or set inactive
46   // CAUTION: when setting the parent object inactive, disable "active" first
47   // then call this, to avoid double-dereferencing the deps of "active"
48 
49   // Cannot be in the base class destructor because it needs the derived class features()
50   size_t i,j,fid;
51 
52   if (cvm::debug()) cvm::log("DEPS: freeing children deps for " + description + "\n");
53 
54   cvm::increase_depth();
55   for (fid = 0; fid < feature_states.size(); fid++) {
56     if (is_enabled(fid)) {
57       for (i=0; i<features()[fid]->requires_children.size(); i++) {
58         int g = features()[fid]->requires_children[i];
59         for (j=0; j<children.size(); j++) {
60           if (cvm::debug()) cvm::log("DEPS: dereferencing children's "
61             + children[j]->features()[g]->description + "\n");
62           children[j]->decr_ref_count(g);
63         }
64       }
65     }
66   }
67   cvm::decrease_depth();
68 }
69 
70 
71 // re-enable children features (and increase ref count accordingly)
72 // So free_children_deps() can be called whenever an object becomes inactive
restore_children_deps()73 void colvardeps::restore_children_deps() {
74   size_t i,j,fid;
75 
76   cvm::increase_depth();
77   for (fid = 0; fid < feature_states.size(); fid++) {
78     if (is_enabled(fid)) {
79       for (i=0; i<features()[fid]->requires_children.size(); i++) {
80         int g = features()[fid]->requires_children[i];
81         for (j=0; j<children.size(); j++) {
82           if (cvm::debug()) cvm::log("DEPS: re-enabling children's "
83             + children[j]->features()[g]->description + "\n");
84           children[j]->enable(g, false, false);
85         }
86       }
87     }
88   }
89   cvm::decrease_depth();
90 }
91 
92 
provide(int feature_id,bool truefalse)93 void colvardeps::provide(int feature_id, bool truefalse) {
94   feature_states[feature_id].available = truefalse;
95 }
96 
97 
set_enabled(int feature_id,bool truefalse)98 void colvardeps::set_enabled(int feature_id, bool truefalse) {
99   if (truefalse) {
100     enable(feature_id);
101   } else {
102     disable(feature_id);
103   }
104 }
105 
106 
get_keyval_feature(colvarparse * cvp,std::string const & conf,char const * key,int feature_id,bool const & def_value,colvarparse::Parse_Mode const parse_mode)107 bool colvardeps::get_keyval_feature(colvarparse *cvp,
108                                     std::string const &conf, char const *key,
109                                     int feature_id, bool const &def_value,
110                                     colvarparse::Parse_Mode const parse_mode)
111 {
112   if (!is_user(feature_id)) {
113     cvm::error("Cannot set feature \"" + features()[feature_id]->description + "\" from user input in \"" + description + "\".\n");
114     return false;
115   }
116   bool value;
117   bool const found = cvp->get_keyval(conf, key, value, def_value, parse_mode);
118   // If the default value is on, this function should be able to disable the feature!
119   set_enabled(feature_id, value);
120 
121   return found;
122 }
123 
124 
enable(int feature_id,bool dry_run,bool toplevel)125 int colvardeps::enable(int feature_id,
126                        bool dry_run /* default: false */,
127                        bool toplevel /* default: true */)
128 {
129   int res;
130   size_t i, j;
131   bool ok;
132   feature *f = features()[feature_id];
133   feature_state *fs = &feature_states[feature_id];
134 
135   if (cvm::debug()) {
136     cvm::log("DEPS: " + description +
137       (dry_run ? " testing " : " enabling ") +
138       "\"" + f->description +"\"\n");
139   }
140 
141   if (fs->enabled) {
142     if (!(dry_run || toplevel)) {
143       // This is a dependency: prevent disabling this feature as long
144       // as requirement is enabled
145       fs->ref_count++;
146       if (cvm::debug())
147         cvm::log("DEPS: bumping ref_count to " + cvm::to_str(fs->ref_count) + "\n");
148     }
149     // Do not try to further resolve deps
150     return COLVARS_OK;
151   }
152 
153   std::string feature_type_descr = is_static(feature_id) ? "Static" :
154     (is_dynamic(feature_id) ? "Dynamic" : "User-controlled");
155 
156   if (!fs->available) {
157     if (!dry_run) {
158       if (toplevel) {
159         cvm::error("Error: " + feature_type_descr + " feature unavailable: \""
160           + f->description + "\" in " + description + ".");
161       } else {
162         cvm::log(feature_type_descr + " feature unavailable: \""
163           + f->description + "\" in " + description + ".");
164       }
165     }
166     return COLVARS_ERROR;
167   }
168 
169   if (!toplevel && !is_dynamic(feature_id)) {
170     if (!dry_run) {
171       cvm::log(feature_type_descr + " feature \"" + f->description
172         + "\" cannot be enabled automatically in " + description + ".");
173       if (is_user(feature_id)) {
174         cvm::log("Try setting it manually.\n");
175       }
176     }
177     return COLVARS_ERROR;
178   }
179 
180   // 1) enforce exclusions
181   // reminder: exclusions must be mutual for this to work
182   for (i=0; i<f->requires_exclude.size(); i++) {
183     feature *g = features()[f->requires_exclude[i]];
184     if (cvm::debug())
185       cvm::log(f->description + " requires exclude " + g->description);
186     if (is_enabled(f->requires_exclude[i])) {
187       if (!dry_run) {
188         cvm::log("Feature \"" + f->description + "\" is incompatible with \""
189         + g->description + "\" in " + description + ".");
190         if (toplevel) {
191           cvm::error("Error: Failed dependency in " + description + ".");
192         }
193       }
194       return COLVARS_ERROR;
195     }
196   }
197 
198   // 2) solve internal deps (self)
199   for (i=0; i<f->requires_self.size(); i++) {
200     if (cvm::debug())
201       cvm::log(f->description + " requires self " + features()[f->requires_self[i]]->description);
202     res = enable(f->requires_self[i], dry_run, false);
203     if (res != COLVARS_OK) {
204       if (!dry_run) {
205         cvm::log("...required by \"" + f->description + "\" in " + description);
206         if (toplevel) {
207           cvm::error("Error: Failed dependency in " + description + ".");
208         }
209       }
210       return res;
211     }
212   }
213 
214   // 3) solve internal alternate deps
215   for (i=0; i<f->requires_alt.size(); i++) {
216 
217     // test if one is available; if yes, enable and exit w/ success
218     ok = false;
219     for (j=0; j<f->requires_alt[i].size(); j++) {
220       int g = f->requires_alt[i][j];
221       if (cvm::debug())
222         cvm::log(f->description + " requires alt " + features()[g]->description);
223       res = enable(g, true, false);  // see if available
224       if (res == COLVARS_OK) {
225         ok = true;
226         if (!dry_run) {
227           enable(g, false, false); // Require again, for real
228           fs->alternate_refs.push_back(g); // We remember we enabled this
229           // so we can free it if this feature gets disabled
230         }
231         break;
232       }
233     }
234     if (!ok) {
235       if (!dry_run) {
236         cvm::log("\"" + f->description + "\" in " + description
237           + " requires one of the following features, none of which can be enabled:\n");
238         cvm::log("-----------------------------------------\n");
239         cvm::increase_depth();
240         for (j=0; j<f->requires_alt[i].size(); j++) {
241           int g = f->requires_alt[i][j];
242           cvm::log(cvm::to_str(j+1) + ". " + features()[g]->description);
243           enable(g, false, false); // Just for printing error output
244         }
245         cvm::decrease_depth();
246         cvm::log("-----------------------------------------\n");
247         if (toplevel) {
248           cvm::error("Error: Failed dependency in " + description + ".");
249         }
250       }
251       return COLVARS_ERROR;
252     }
253   }
254 
255   // 4) solve deps in children
256   // if the object is inactive, we solve but do not enable: will be enabled
257   // when the object becomes active
258   cvm::increase_depth();
259   for (i=0; i<f->requires_children.size(); i++) {
260     int g = f->requires_children[i];
261     for (j=0; j<children.size(); j++) {
262       res = children[j]->enable(g, dry_run || !is_enabled(), false);
263       if (res != COLVARS_OK) {
264         if (!dry_run) {
265           cvm::log("...required by \"" + f->description + "\" in " + description);
266           if (toplevel) {
267             cvm::error("Error: Failed dependency in " + description + ".");
268           }
269         }
270         return res;
271       }
272     }
273   }
274   cvm::decrease_depth();
275 
276   // Actually enable feature only once everything checks out
277   if (!dry_run) {
278     fs->enabled = true;
279     // This should be the only reference
280     if (!toplevel) fs->ref_count = 1;
281     if (feature_id == 0) {
282       // Waking up this object, enable all deps in children
283       restore_children_deps();
284     }
285     do_feature_side_effects(feature_id);
286     if (cvm::debug())
287       cvm::log("DEPS: feature \"" + f->description + "\" in "
288         + description + " enabled, ref_count = 1." + "\n");
289   }
290   return COLVARS_OK;
291 }
292 
293 
disable(int feature_id)294 int colvardeps::disable(int feature_id) {
295   size_t i, j;
296   feature *f = features()[feature_id];
297   feature_state *fs = &feature_states[feature_id];
298 
299   if (cvm::debug()) cvm::log("DEPS: disabling feature \""
300       + f->description + "\" in " + description + "\n");
301 
302   if (fs->enabled == false) {
303     return COLVARS_OK;
304   }
305 
306   if (fs->ref_count > 1) {
307     cvm::error("Error: cannot disable feature \"" + f->description
308      + "\" in " + description + " because of " + cvm::to_str(fs->ref_count-1)
309      + " remaining references.\n" );
310     return COLVARS_ERROR;
311   }
312 
313   // internal deps (self)
314   for (i=0; i<f->requires_self.size(); i++) {
315     if (cvm::debug()) cvm::log("DEPS: dereferencing self "
316       + features()[f->requires_self[i]]->description + "\n");
317     decr_ref_count(f->requires_self[i]);
318   }
319 
320   // alternates
321   for (i=0; i<fs->alternate_refs.size(); i++) {
322     if (cvm::debug()) cvm::log("DEPS: dereferencing alt "
323       + features()[fs->alternate_refs[i]]->description + "\n");
324     decr_ref_count(fs->alternate_refs[i]);
325   }
326   // Forget these, now that they are dereferenced
327   fs->alternate_refs.clear();
328 
329   // deps in children
330   // except if the object is inactive, then children dependencies
331   // have already been dereferenced by this function
332   // (or never referenced if feature was enabled while the object
333   // was inactive)
334   if (is_enabled()) {
335     cvm::increase_depth();
336     for (i=0; i<f->requires_children.size(); i++) {
337       int g = f->requires_children[i];
338       for (j=0; j<children.size(); j++) {
339         if (cvm::debug()) cvm::log("DEPS: dereferencing children's "
340           + children[j]->features()[g]->description + "\n");
341         children[j]->decr_ref_count(g);
342       }
343     }
344     cvm::decrease_depth();
345   }
346 
347   fs->enabled = false;
348   fs->ref_count = 0;
349   if (feature_id == 0) {
350     // Putting this object to sleep
351     free_children_deps();
352   }
353   return COLVARS_OK;
354 }
355 
356 
decr_ref_count(int feature_id)357 int colvardeps::decr_ref_count(int feature_id) {
358   int &rc = feature_states[feature_id].ref_count;
359   feature *f = features()[feature_id];
360 
361   if (cvm::debug())
362       cvm::log("DEPS: decreasing reference count of \"" + f->description
363         + "\" in " + description + ".\n");
364 
365   if (rc <= 0) {
366     cvm::error("Error: cannot decrease reference count of feature \"" + f->description
367       +  "\" in " + description + ", which is " + cvm::to_str(rc) + ".\n");
368     return COLVARS_ERROR;
369   }
370 
371   rc--;
372   if (rc == 0 && f->is_dynamic()) {
373     // we can auto-disable this feature
374     if (cvm::debug())
375       cvm::log("DEPS will now auto-disable dynamic feature \"" + f->description
376      + "\" in " + description + ".\n");
377     disable(feature_id);
378   }
379   return COLVARS_OK;
380 }
381 
382 
init_feature(int feature_id,const char * description_in,feature_type type)383 void colvardeps::init_feature(int feature_id, const char *description_in, feature_type type) {
384   modify_features()[feature_id]->description = description_in;
385   modify_features()[feature_id]->type = type;
386 }
387 
388 
389 // Shorthand functions for describing dependencies
require_feature_self(int f,int g)390 void colvardeps::require_feature_self(int f, int g) {
391   features()[f]->requires_self.push_back(g);
392 }
393 
394 
395 // Ensure that exclusions are symmetric
exclude_feature_self(int f,int g)396 void colvardeps::exclude_feature_self(int f, int g) {
397   features()[f]->requires_exclude.push_back(g);
398   features()[g]->requires_exclude.push_back(f);
399 }
400 
401 
require_feature_children(int f,int g)402 void colvardeps::require_feature_children(int f, int g) {
403   features()[f]->requires_children.push_back(g);
404 }
405 
406 
require_feature_alt(int f,int g,int h)407 void colvardeps::require_feature_alt(int f, int g, int h) {
408   features()[f]->requires_alt.push_back(std::vector<int>(2));
409   features()[f]->requires_alt.back()[0] = g;
410   features()[f]->requires_alt.back()[1] = h;
411 }
412 
413 
require_feature_alt(int f,int g,int h,int i)414 void colvardeps::require_feature_alt(int f, int g, int h, int i) {
415   features()[f]->requires_alt.push_back(std::vector<int>(3));
416   features()[f]->requires_alt.back()[0] = g;
417   features()[f]->requires_alt.back()[1] = h;
418   features()[f]->requires_alt.back()[2] = i;
419 }
420 
421 
require_feature_alt(int f,int g,int h,int i,int j)422 void colvardeps::require_feature_alt(int f, int g, int h, int i, int j) {
423   features()[f]->requires_alt.push_back(std::vector<int>(4));
424   features()[f]->requires_alt.back()[0] = g;
425   features()[f]->requires_alt.back()[1] = h;
426   features()[f]->requires_alt.back()[2] = i;
427   features()[f]->requires_alt.back()[3] = j;
428 }
429 
430 
print_state()431 void colvardeps::print_state() {
432   size_t i;
433   cvm::log("Features of \"" + description + "\" (refcount)\n");
434   for (i = 0; i < feature_states.size(); i++) {
435     std::string onoff = is_enabled(i) ? "ON " : "   ";
436     // Only display refcount if non-zero for less clutter
437     std::string refcount = feature_states[i].ref_count != 0 ?
438       " (" + cvm::to_str(feature_states[i].ref_count) + ") " : "";
439     cvm::log("- " + onoff + features()[i]->description + refcount + "\n");
440   }
441   cvm::increase_depth();
442   for (i=0; i<children.size(); i++) {
443     cvm::log("* child " + cvm::to_str(i+1));
444     children[i]->print_state();
445   }
446   cvm::decrease_depth();
447 }
448 
449 
add_child(colvardeps * child)450 void colvardeps::add_child(colvardeps *child) {
451 
452   children.push_back(child);
453   child->parents.push_back(this);
454 
455   // Solve dependencies of already enabled parent features
456   // in the new child
457 
458   size_t i, fid;
459   cvm::increase_depth();
460   for (fid = 0; fid < feature_states.size(); fid++) {
461     if (is_enabled(fid)) {
462       for (i=0; i<features()[fid]->requires_children.size(); i++) {
463         int g = features()[fid]->requires_children[i];
464         if (cvm::debug()) cvm::log("DEPS: re-enabling children's "
465           + child->features()[g]->description + "\n");
466         child->enable(g, false, false);
467       }
468     }
469   }
470   cvm::decrease_depth();
471 }
472 
473 
remove_child(colvardeps * child)474 void colvardeps::remove_child(colvardeps *child) {
475   int i;
476   bool found = false;
477 
478   for (i = children.size()-1; i>=0; --i) {
479     if (children[i] == child) {
480       children.erase(children.begin() + i);
481       found = true;
482       break;
483     }
484   }
485   if (!found) {
486     cvm::error("Trying to remove missing child reference from " + description + "\n");
487   }
488   found = false;
489   for (i = child->parents.size()-1; i>=0; --i) {
490     if (child->parents[i] == this) {
491       child->parents.erase(child->parents.begin() + i);
492       found = true;
493       break;
494     }
495   }
496   if (!found) {
497     cvm::error("Trying to remove missing parent reference from " + child->description + "\n");
498   }
499 }
500 
501 
remove_all_children()502 void colvardeps::remove_all_children() {
503   size_t i;
504   int j;
505   bool found;
506 
507   for (i = 0; i < children.size(); ++i) {
508     found = false;
509     for (j = children[i]->parents.size()-1; j>=0; --j) {
510       if (children[i]->parents[j] == this) {
511         children[i]->parents.erase(children[i]->parents.begin() + j);
512         found = true;
513         break;
514       }
515     }
516     if (!found) {
517       cvm::error("Trying to remove missing parent reference from " + children[i]->description + "\n");
518     }
519   }
520   children.clear();
521 }
522