MueLu  Version of the Day
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
MueLu_AvatarInterface.cpp
Go to the documentation of this file.
1 // @HEADER
2 // *****************************************************************************
3 // MueLu: A package for multigrid based preconditioning
4 //
5 // Copyright 2012 NTESS and the MueLu contributors.
6 // SPDX-License-Identifier: BSD-3-Clause
7 // *****************************************************************************
8 // @HEADER
9 
11 
12 #include <string>
13 #include <fstream>
14 #include <sstream>
15 #include <vector>
16 #include "Teuchos_Array.hpp"
17 #include "Teuchos_CommHelpers.hpp"
18 #include "MueLu_BaseClass.hpp"
19 #include "Teuchos_RawParameterListHelpers.hpp"
20 
21 // ***********************************************************************
22 /* Notional Parameterlist Structure
23  "avatar: filestem" "{'mystem1','mystem2'}"
24  "avatar: decision tree files" "{'mystem1.trees','mystem2.trees'}"
25  "avatar: names files" "{'mystem1.names','mystem2.names'}"
26  "avatar: good class" "1"
27  "avatar: heuristic" "1"
28  "avatar: bounds file" "{'bounds.data'}"
29  "avatar: muelu parameter mapping"
30  - "param0'
31  - "muelu parameter" "aggregation: threshold"
32  - "avatar parameter" "DROP_TOL"
33  - "muelu values" "{0,1e-4,1e-3,1e-2}"
34  - "avatar values" "{1,2,3,4}
35  - "param1'
36  - "muelu parameter" "smoother: sweeps"
37  - "avatar parameter" "SWEEPS"
38  - "muelu values" "{1,2,3}"
39  - "avatar values" "{1,2,3}"
40 
41 
42  Notional SetMueLuParameters "problemFeatures" Structure
43  "my feature #1" "246.01"
44  "my feature #2" "123.45"
45 
46  */
47 
48 /*
49 TODO List:
50 Modify MueLu
51  Parameter name checking (make sure names match between Avatar’s names file and the parameter / feature names that MueLu sees).
52 */
53 
54 #ifdef HAVE_MUELU_AVATAR
55 
56 extern "C" {
57 #include "avatar_api.h"
58 }
59 
60 namespace MueLu {
61 
62 // ***********************************************************************
63 RCP<const ParameterList> AvatarInterface::GetValidParameterList() const {
64  RCP<ParameterList> validParamList = rcp(new ParameterList());
65 
66  Teuchos::ParameterList pl_dummy;
68  int int_dummy;
69 
70  // Files from which to read Avatar trees
71  validParamList->set<Teuchos::Array<std::string>>("avatar: decision tree files", ar_dummy, "Names of Avatar decision tree files");
72 
73  // Strings from which to read Avatar trees
74  validParamList->set<Teuchos::Array<std::string>>("avatar: decision tree strings", ar_dummy, "Avatar decision tree strings");
75 
76  // Files from which to read Avatar names
77  validParamList->set<Teuchos::Array<std::string>>("avatar: names files", ar_dummy, "Names of Avatar decision names files");
78 
79  // Strings from which to read Avatar names
80  validParamList->set<Teuchos::Array<std::string>>("avatar: names strings", ar_dummy, "Avatar decision names strings");
81 
82  // Filestem arg for Avatar
83  validParamList->set<Teuchos::Array<std::string>>("avatar: filestem", ar_dummy, "Filestem for the files Avatar requires");
84 
85  // This should be a MueLu parameter-to-Avatar parameter mapping (e.g. if Avatar doesn't like spaces)
86  validParamList->set<Teuchos::ParameterList>("avatar: muelu parameter mapping", pl_dummy, "Mapping of MueLu to Avatar Parameters");
87 
88  // "Good" Class ID for Avatar
89  validParamList->set<int>("avatar: good class", int_dummy, "Numeric code for class Avatar considers to be good");
90 
91  // Which drop tol choice heuristic to use
92  validParamList->set<int>("avatar: heuristic", int_dummy, "Numeric code for which heuristic we want to use");
93 
94  // Bounds file for extrapolation risk
95  validParamList->set<Teuchos::Array<std::string>>("avatar: bounds file", ar_dummy, "Bounds file for Avatar extrapolation risk");
96 
97  // Add dummy variables at the start
98  validParamList->set<int>("avatar: initial dummy variables", int_dummy, "Number of dummy variables to add at the start");
99 
100  // Add dummy variables before the class
101  validParamList->set<int>("avatar: pre-class dummy variables", int_dummy, "Number of dummy variables to add at the before the class");
102 
103  // Value of the dummy variables
104  validParamList->set<int>("avatar: dummy value", int_dummy, "Value of the dummy variables to add at the before/after the class");
105 
106  return validParamList;
107 }
108 
109 // ***********************************************************************
110 Teuchos::ArrayRCP<std::string> AvatarInterface::ReadFromFiles(const char* paramName) const {
111  // const Teuchos::Array<std::string> & tf = params_.get<const Teuchos::Array<std::string> >(paramName);
112  Teuchos::Array<std::string>& tf = params_.get<Teuchos::Array<std::string>>(paramName);
114  // Only Proc 0 will read the files and print the strings
115  if (comm_->getRank() == 0) {
116  treelist.resize(tf.size());
117  for (Teuchos_Ordinal i = 0; i < tf.size(); i++) {
118  std::fstream file;
119  std::stringstream ss;
120  file.open(tf[i]);
121  ss << file.rdbuf();
122  treelist[i] = ss.str();
123  file.close();
124  }
125  }
126  return treelist;
127 }
128 
129 // ***********************************************************************
130 void AvatarInterface::Setup() {
131  // Sanity check
132  if (comm_.is_null()) throw std::runtime_error("MueLu::AvatarInterface::Setup(): Communicator cannot be null");
133 
134  // Get the avatar strings (NOTE: Only exist on proc 0)
135  if (params_.isParameter("avatar: decision tree strings"))
136  avatarStrings_ = Teuchos::arcpFromArray(params_.get<Teuchos::Array<std::string>>("avatar: decision tree strings"));
137  else
138  avatarStrings_ = ReadFromFiles("avatar: decision tree files");
139 
140  if (params_.isParameter("avatar: names strings"))
141  namesStrings_ = Teuchos::arcpFromArray(params_.get<Teuchos::Array<std::string>>("avatar: names strings"));
142  else
143  namesStrings_ = ReadFromFiles("avatar: names files");
144 
145  if (params_.isParameter("avatar: bounds file"))
146  boundsString_ = ReadFromFiles("avatar: bounds file");
147 
148  filestem_ = params_.get<Teuchos::Array<std::string>>("avatar: filestem");
149 
150  if (comm_->getRank() == 0) {
151  // Now actually set up avatar - Avatar's cleanup routine will free the pointer
152  // Avatar_handle* avatar_train(int argc, char **argv, char* names_file, int names_file_is_a_string, char* train_file, int train_file_is_a_string);
153  const int namesfile_is_a_string = 1;
154  const int treesfile_is_a_string = 1;
155  avatarHandle_ = avatar_load(const_cast<char*>(filestem_[0].c_str()), const_cast<char*>(namesStrings_[0].c_str()), namesfile_is_a_string, const_cast<char*>(avatarStrings_[0].c_str()), treesfile_is_a_string);
156  }
157 
158  // Which class does Avatar consider "good"
159  avatarGoodClass_ = params_.get<int>("avatar: good class");
160 
161  heuristicToUse_ = params_.get<int>("avatar: heuristic");
162 
163  // Unpack the MueLu Mapping into something actionable
164  UnpackMueLuMapping();
165 }
166 
167 // ***********************************************************************
168 void AvatarInterface::Cleanup() {
169  avatar_cleanup(avatarHandle_);
170  avatarHandle_ = 0;
171 }
172 
173 // ***********************************************************************
174 void AvatarInterface::GenerateFeatureString(const Teuchos::ParameterList& problemFeatures, std::string& featureString) const {
175  // NOTE: Assumes that the features are in the same order Avatar wants them.
176  std::stringstream ss;
177 
178  // Initial Dummy Variables
179  if (params_.isParameter("avatar: initial dummy variables")) {
180  int num_dummy = params_.get<int>("avatar: initial dummy variables");
181  int dummy_value = params_.get("avatar: dummy value", 666);
182 
183  for (int i = 0; i < num_dummy; i++)
184  ss << dummy_value << ",";
185  }
186 
187  for (Teuchos::ParameterList::ConstIterator i = problemFeatures.begin(); i != problemFeatures.end(); i++) {
188  // const std::string& name = problemFeatures.name(i);
189  const Teuchos::ParameterEntry& entry = problemFeatures.entry(i);
190  if (i != problemFeatures.begin()) ss << ",";
191  entry.leftshift(ss, false); // Because ss<<entry prints out '[unused]' and we don't want that.
192  }
193  featureString = ss.str();
194 }
195 
196 // ***********************************************************************
197 void AvatarInterface::UnpackMueLuMapping() {
198  const Teuchos::ParameterList& mapping = params_.get<Teuchos::ParameterList>("avatar: muelu parameter mapping");
199  // Each MueLu/Avatar parameter pair gets its own sublist. These must be numerically ordered with no gap
200 
201  bool done = false;
202  int idx = 0;
203  int numParams = mapping.numParams();
204 
205  mueluParameterName_.resize(numParams);
206  avatarParameterName_.resize(numParams);
207  mueluParameterValues_.resize(numParams);
208  avatarParameterValues_.resize(numParams);
209 
210  while (!done) {
211  std::stringstream ss;
212  ss << "param" << idx;
213  if (mapping.isSublist(ss.str())) {
214  const Teuchos::ParameterList& sublist = mapping.sublist(ss.str());
215 
216  // Get the names
217  mueluParameterName_[idx] = sublist.get<std::string>("muelu parameter");
218  avatarParameterName_[idx] = sublist.get<std::string>("avatar parameter");
219 
220  // Get the values
221  // FIXME: For now we assume that all of these guys are doubles and their Avatar analogues are doubles
222  mueluParameterValues_[idx] = sublist.get<Teuchos::Array<double>>("muelu values");
223  avatarParameterValues_[idx] = sublist.get<Teuchos::Array<double>>("avatar values");
224 
225  idx++;
226  } else {
227  done = true;
228  }
229  }
230 
231  if (idx != numParams)
232  throw std::runtime_error("MueLu::AvatarInterface::UnpackMueLuMapping(): 'avatar: muelu parameter mapping' has unknown fields");
233 }
234 // ***********************************************************************
235 std::string AvatarInterface::ParamsToString(const std::vector<int>& indices) const {
236  std::stringstream ss;
237  for (Teuchos_Ordinal i = 0; i < avatarParameterValues_.size(); i++) {
238  ss << "," << avatarParameterValues_[i][indices[i]];
239  }
240 
241  // Pre-Class dummy variables
242  if (params_.isParameter("avatar: pre-class dummy variables")) {
243  int num_dummy = params_.get<int>("avatar: pre-class dummy variables");
244  int dummy_value = params_.get("avatar: dummy value", 666);
245  for (int i = 0; i < num_dummy; i++)
246  ss << "," << dummy_value;
247  }
248 
249  return ss.str();
250 }
251 
252 // ***********************************************************************
253 void AvatarInterface::SetIndices(int id, std::vector<int>& indices) const {
254  // The combo numbering here starts with the first guy
255  int numParams = (int)avatarParameterValues_.size();
256  int curr_id = id;
257  for (int i = 0; i < numParams; i++) {
258  int div = avatarParameterValues_[i].size();
259  int mod = curr_id % div;
260  indices[i] = mod;
261  curr_id = (curr_id - mod) / div;
262  }
263 }
264 
265 // ***********************************************************************
266 void AvatarInterface::GenerateMueLuParametersFromIndex(int id, Teuchos::ParameterList& pl) const {
267  // The combo numbering here starts with the first guy
268  int numParams = (int)avatarParameterValues_.size();
269  int curr_id = id;
270  for (int i = 0; i < numParams; i++) {
271  int div = avatarParameterValues_[i].size();
272  int mod = curr_id % div;
273  pl.set(mueluParameterName_[i], mueluParameterValues_[i][mod]);
274  curr_id = (curr_id - mod) / div;
275  }
276 }
277 
278 // ***********************************************************************
279 void AvatarInterface::SetMueLuParameters(const Teuchos::ParameterList& problemFeatures, Teuchos::ParameterList& mueluParams, bool overwrite) const {
280  Teuchos::ParameterList avatarParams;
281  std::string paramString;
282 
283  if (comm_->getRank() == 0) {
284  // Only Rank 0 calls Avatar
285  if (!avatarHandle_) throw std::runtime_error("MueLu::AvatarInterface::SetMueLuParameters(): Setup has not been run");
286 
287  // Turn the problem features into a "trial" string for Avatar
288  std::string trialString;
289  GenerateFeatureString(problemFeatures, trialString);
290 
291  // Compute the number of things we need to test
292  int numParams = (int)avatarParameterValues_.size();
293  std::vector<int> indices(numParams);
294  std::vector<int> sizes(numParams);
295  int num_combos = 1;
296  for (int i = 0; i < numParams; i++) {
297  sizes[i] = avatarParameterValues_[i].size();
298  num_combos *= avatarParameterValues_[i].size();
299  }
300  GetOStream(Runtime0) << "MueLu::AvatarInterface: Testing " << num_combos << " option combinations" << std::endl;
301 
302  // For each input parameter to avatar we iterate over its allowable values and then compute the list of options which Avatar
303  // views as acceptable
304  // FIXME: Find alternative to hard coding malloc size (input deck?)
305  int num_classes = avatar_num_classes(avatarHandle_);
306  std::vector<int> predictions(num_combos, 0);
307  std::vector<float> probabilities(num_classes * num_combos, 0);
308 
309  std::string testString;
310  for (int i = 0; i < num_combos; i++) {
311  SetIndices(i, indices);
312  // Now we add the MueLu parameters into one, enormous Avatar trial string and run avatar once
313  testString += trialString + ParamsToString(indices) + ",0\n";
314  }
315 
316  std::cout << "** Avatar TestString ***\n"
317  << testString << std::endl; // DEBUG
318 
319  int bound_check = true;
320  if (params_.isParameter("avatar: bounds file"))
321  bound_check = checkBounds(testString, boundsString_);
322 
323  // FIXME: Only send in first tree's string
324  // int* avatar_test(Avatar_handle* a, char* test_data_file, int test_data_is_a_string);
325  const int test_data_is_a_string = 1;
326  avatar_test(avatarHandle_, const_cast<char*>(testString.c_str()), test_data_is_a_string, predictions.data(), probabilities.data());
327 
328  // Look at the list of acceptable combinations of options
329  std::vector<int> acceptableCombos;
330  acceptableCombos.reserve(100);
331  for (int i = 0; i < num_combos; i++) {
332  if (predictions[i] == avatarGoodClass_) acceptableCombos.push_back(i);
333  }
334  GetOStream(Runtime0) << "MueLu::AvatarInterface: " << acceptableCombos.size() << " acceptable option combinations found" << std::endl;
335 
336  // Did we have any good combos at all?
337  int chosen_option_id = 0;
338  if (acceptableCombos.size() == 0) {
339  GetOStream(Runtime0) << "WARNING: MueLu::AvatarInterface: found *no* combinations of options which it believes will perform well on this problem" << std::endl
340  << " An arbitrary set of options will be chosen instead" << std::endl;
341  } else {
342  // If there is only one acceptable combination, use it;
343  // otherwise, find the parameter choice with the highest
344  // probability of success
345  if (acceptableCombos.size() == 1) {
346  chosen_option_id = acceptableCombos[0];
347  } else {
348  switch (heuristicToUse_) {
349  case 1:
350  chosen_option_id = hybrid(probabilities.data(), acceptableCombos);
351  break;
352  case 2:
353  chosen_option_id = highProb(probabilities.data(), acceptableCombos);
354  break;
355  case 3:
356  // Choose the first option in the list of acceptable
357  // combinations; the lowest drop tolerance among the
358  // acceptable combinations
359  chosen_option_id = acceptableCombos[0];
360  break;
361  case 4:
362  chosen_option_id = lowCrash(probabilities.data(), acceptableCombos);
363  break;
364  case 5:
365  chosen_option_id = weighted(probabilities.data(), acceptableCombos);
366  break;
367  }
368  }
369  }
370 
371  // If mesh parameters are outside bounding box, set drop tolerance
372  // to 0, otherwise use avatar recommended drop tolerance
373  if (bound_check == 0) {
374  GetOStream(Runtime0) << "WARNING: Extrapolation risk detected, setting drop tolerance to 0" << std::endl;
375  GenerateMueLuParametersFromIndex(0, avatarParams);
376  } else {
377  GenerateMueLuParametersFromIndex(chosen_option_id, avatarParams);
378  }
379  }
380 
381  Teuchos::updateParametersAndBroadcast(outArg(avatarParams), outArg(mueluParams), *comm_, 0, overwrite);
382 }
383 
384 int AvatarInterface::checkBounds(std::string trialString, Teuchos::ArrayRCP<std::string> boundsString) const {
385  std::stringstream ss(trialString);
386  std::vector<double> vect;
387 
388  double b;
389  while (ss >> b) {
390  vect.push_back(b);
391  if (ss.peek() == ',') ss.ignore();
392  }
393 
394  std::stringstream ssBounds(boundsString[0]);
395  std::vector<double> boundsVect;
396 
397  while (ssBounds >> b) {
398  boundsVect.push_back(b);
399  if (ssBounds.peek() == ',') ssBounds.ignore();
400  }
401 
402  int min_idx = (int)std::min(vect.size(), boundsVect.size() / 2);
403 
404  bool inbounds = true;
405  for (int i = 0; inbounds && i < min_idx; i++)
406  inbounds = boundsVect[2 * i] <= vect[i] && vect[i] <= boundsVect[2 * i + 1];
407 
408  return (int)inbounds;
409 }
410 
411 int AvatarInterface::hybrid(float* probabilities, std::vector<int> acceptableCombos) const {
412  float low_crash = probabilities[0];
413  float best_prob = probabilities[2];
414  float diff;
415  int this_combo;
416  int chosen_option_id = acceptableCombos[0];
417  for (int x = 0; x < (int)acceptableCombos.size(); x++) {
418  this_combo = acceptableCombos[x] * 3;
419  diff = probabilities[this_combo] - low_crash;
420  // If this parameter combination has a crash
421  // probability .2 lower than the current "best", we
422  // will use this drop tolerance
423  if (diff < -.2) {
424  low_crash = probabilities[this_combo];
425  best_prob = probabilities[this_combo + 2];
426  chosen_option_id = acceptableCombos[x];
427  }
428  // If this parameter combination has the same
429  // or slightly lower crash probability than the
430  // current best, we compare their "GOOD" probabilities
431  else if (diff <= 0 && probabilities[this_combo + 2] > best_prob) {
432  low_crash = probabilities[this_combo];
433  best_prob = probabilities[this_combo + 2];
434  chosen_option_id = acceptableCombos[x];
435  }
436  }
437  return chosen_option_id;
438 }
439 
440 int AvatarInterface::highProb(float* probabilities, std::vector<int> acceptableCombos) const {
441  float high_prob = probabilities[2];
442  int this_combo;
443  int chosen_option_id = acceptableCombos[0];
444  for (int x = 0; x < (int)acceptableCombos.size(); x++) {
445  this_combo = acceptableCombos[x] * 3;
446  // If this parameter combination has a higher "GOOD"
447  // probability, use this combination
448  if (probabilities[this_combo + 2] > high_prob) {
449  high_prob = probabilities[this_combo + 2];
450  chosen_option_id = acceptableCombos[x];
451  }
452  }
453  return chosen_option_id;
454 }
455 
456 int AvatarInterface::lowCrash(float* probabilities, std::vector<int> acceptableCombos) const {
457  float low_crash = probabilities[0];
458  int this_combo;
459  int chosen_option_id = acceptableCombos[0];
460  for (int x = 0; x < (int)acceptableCombos.size(); x++) {
461  this_combo = acceptableCombos[x] * 3;
462  // If this parameter combination has a lower "CRASH"
463  // probability, use this combination
464  if (probabilities[this_combo] < low_crash) {
465  low_crash = probabilities[this_combo];
466  chosen_option_id = acceptableCombos[x];
467  }
468  }
469  return chosen_option_id;
470 }
471 
472 int AvatarInterface::weighted(float* probabilities, std::vector<int> acceptableCombos) const {
473  float low_crash = probabilities[0];
474  float best_prob = probabilities[2];
475  float diff;
476  int this_combo;
477  int chosen_option_id = acceptableCombos[0];
478  for (int x = 0; x < (int)acceptableCombos.size(); x++) {
479  this_combo = acceptableCombos[x] * 3;
480  diff = probabilities[this_combo] - low_crash;
481  // If this parameter combination has a crash
482  // probability .2 lower than the current "best", we
483  // will use this drop tolerance
484  if (diff < -.2) {
485  low_crash = probabilities[this_combo];
486  best_prob = probabilities[this_combo + 2];
487  chosen_option_id = acceptableCombos[x];
488  }
489  // If this parameter combination is within .1
490  // or has a slightly lower crash probability than the
491  // current best, we compare their "GOOD" probabilities
492  else if (diff <= .1 && probabilities[this_combo + 2] > best_prob) {
493  low_crash = probabilities[this_combo];
494  best_prob = probabilities[this_combo + 2];
495  chosen_option_id = acceptableCombos[x];
496  }
497  }
498  return chosen_option_id;
499 }
500 
501 } // namespace MueLu
502 
503 #endif // HAVE_MUELU_AVATAR
ConstIterator end() const
std::ostream & leftshift(std::ostream &os, bool printFlags=true) const
T & get(const std::string &name, T def_value)
One-liner description of what is happening.
Ordinal numParams() const
ParameterList & set(std::string const &name, T &&value, std::string const &docString="", RCP< const ParameterEntryValidator > const &validator=null)
TEUCHOS_DEPRECATED RCP< T > rcp(T *p, Dealloc_T dealloc, bool owns_mem)
bool isSublist(const std::string &name) const
params_t::ConstIterator ConstIterator
ConstIterator begin() const
void resize(size_type new_size, const value_type &x=value_type())
const ParameterEntry & entry(ConstIterator i) const
size_type size() const
ParameterList & sublist(const std::string &name, bool mustAlreadyExist=false, const std::string &docString="")