1 /*  _______________________________________________________________________
2 
3     DAKOTA: Design Analysis Kit for Optimization and Terascale Applications
4     Copyright 2014-2020 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
5     This software is distributed under the GNU Lesser General Public License.
6     For more information, see the README file in the top Dakota directory.
7     _______________________________________________________________________ */
8 
9 //- Description: A mock simulator main for testing Dakota in library mode.
10 //-              Uses alternative instantiation syntax as described in the
11 //-              library mode docs within the Developers Manual.
12 //- Owner:       Mike Eldred
13 //- Checked by:  Brian Adams
14 //- Version: $Id: library_mode.cpp 5063 2008-06-05 02:08:06Z mseldre $
15 
16 /** \file library_mode.cpp
17     \brief file containing a mock simulator main for testing Dakota in
18     library mode */
19 
20 #include "ParallelLibrary.hpp"
21 #include "ProblemDescDB.hpp"
22 #include "LibraryEnvironment.hpp"
23 #include "DakotaModel.hpp"
24 #include "DakotaInterface.hpp"
25 #include "PluginSerialDirectApplicInterface.hpp"
26 #include "PluginParallelDirectApplicInterface.hpp"
27 
28 #ifdef HAVE_AMPL
29 /** Floating-point initialization from AMPL: switch to 53-bit rounding
30     if appropriate, to eliminate some cross-platform differences. */
31 extern "C" void fpinit_ASL();
32 #endif
33 
34 #ifndef DAKOTA_HAVE_MPI
35 #define MPI_COMM_WORLD 0
36 #endif // not DAKOTA_HAVE_MPI
37 
38 /// Run a Dakota LibraryEnvironment, mode 1: parsing an input file
39 void run_dakota_parse(const char* dakota_input_file);
40 
41 //namespace Dakota {
42 /// Run a Dakota LibraryEnvironment, mode 2: from C++ API inserted data
43 void run_dakota_data();
44 //} // namespace Dakota
45 
46 /// Run a Dakota LibraryEnvironment, from string or input file input,
47 /// supplemented with additional C++ API adjustments
48 void run_dakota_mixed(const char* dakota_input_file, bool mpirun_flag);
49 
50 /// Convenience function with simplest example of interface plugin: plugin a serial
51 /// DirectApplicInterface that can be constructed independent of Dakota's
52 /// configuration details.
53 void serial_interface_plugin(Dakota::LibraryEnvironment& env);
54 
55 /// Convenience function to plug a library client's interface into the appropriate
56 /// model, demonstrating use of Dakota parallel configuration in constructing the
57 /// plugin Interface on the right MPI_Comm
58 void parallel_interface_plugin(Dakota::LibraryEnvironment& env);
59 
60 /** Data structure to pass application-specific values through Dakota
61     back to the callback function, for example to convey late updates
62     to bounds, initial points, etc., to Dakota. */
63 struct callback_data {
64   /// upper bound value to pass through parser to callback function
65   double rosen_cdv_upper_bd;
66 };
67 
68 /// Example: user-provided post-parse callback (Dakota::DbCallbackFunction)
69 static void callback_function(Dakota::ProblemDescDB* db, void *ptr);
70 
71 
72 /// A mock simulator main for testing Dakota in library mode.
73 
74 /** Overall Usage: dakota_library_mode [-mixed] [dakota.in]
75 
76     Uses alternative instantiation syntax as described in the library
77     mode documentation within the Developers Manual.  Tests several
78     problem specification modes:
79 
80     (1) run_dakota_parse: reads all problem specification data from a
81         Dakota input file.  Usage:
82 	  dakota_library_mode dakota.in
83 
84     (2) run_dakota_data: creates all problem specification from direct
85         Data instance instantiations in the C++ code. Usage:
86 	  dakota_library_mode
87 
88     (3) run_dakota_mixed: a mixture of input parsing and direct data updates,
89         where the data updates occur:
90         (a) via the DB during Environment instantiation, and
91         (b) via Iterators/Models following Environment instantiation.
92         Usage:
93 	  dakota_library_mode -mixed            (input from default string)
94 	  dakota_library_mode -mixed dakota.in  (input from specified file)
95 
96     Serial cases use a plugin rosenbrock model, while parallel cases
97     use textbook.
98 */
main(int argc,char * argv[])99 int main(int argc, char* argv[])
100 {
101 #ifdef HAVE_AMPL
102   // Switch to 53-bit rounding if appropriate, to eliminate some
103   // cross-platform differences.
104   fpinit_ASL();
105 #endif
106 
107   // whether running in parallel
108   bool parallel = Dakota::MPIManager::detect_parallel_launch(argc, argv);
109 
110   // Define MPI_DEBUG in dakota_global_defs.cpp to cause a hold here
111   Dakota::mpi_debug_hold();
112 
113 #ifdef DAKOTA_HAVE_MPI
114   if (parallel)
115     MPI_Init(&argc, &argv); // initialize MPI
116 #endif // DAKOTA_HAVE_MPI
117 
118   // Allow MPI to extract its command line arguments first in detect above,
119   // then detect "-mixed" and dakota_input_file
120   bool mixed_input = false;
121   const char *dakota_input_file = NULL;
122   if (argc > 1) {
123     if (!strcmp(argv[1],"-mixed")) {
124       mixed_input = true;
125       if (argc > 2)
126 	dakota_input_file = argv[2];
127     }
128     else
129       dakota_input_file = argv[1];
130   }
131 
132   if (mixed_input)
133     run_dakota_mixed(dakota_input_file, parallel); // mode 3: mixed
134   else if (dakota_input_file)
135     run_dakota_parse(dakota_input_file); // mode 1: parse
136   else
137     /*Dakota::*/run_dakota_data();       // mode 2: data
138 
139   // Note: Dakota objects created in above function calls need to go
140   // out of scope prior to MPI_Finalize so that MPI code in
141   // destructors works properly in library mode.
142 
143 #ifdef DAKOTA_HAVE_MPI
144   if (parallel)
145     MPI_Finalize(); // finalize MPI
146 #endif // DAKOTA_HAVE_MPI
147 
148   return 0;
149 }
150 
151 
152 // Default input for mixed cases where input file not used.  The strings may
153 // include comments, provided a \n follows each comment.  Before each new
154 // keyword, some white space (a blank or newline) must appear.
155 
156 /// Default Dakota input string for serial case (rosenbrock):
157 static const char serial_input[] =
158   "	method,"
159   "		optpp_q_newton"
160   "		  max_iterations = 50"
161   "		  convergence_tolerance = 1e-4"
162   "	variables,"
163   "		continuous_design = 2"
164   "		  descriptors 'x1' 'x2'"
165   "	interface,"
166   "		direct"
167   "		  analysis_driver = 'plugin_rosenbrock'"
168   "	responses,"
169   "		num_objective_functions = 1"
170   "		analytic_gradients"
171   "		no_hessians";
172 
173 /// Default Dakota input string for parallel case (text_book)
174 static const char parallel_input[] =
175   "	method,"
176   "		optpp_q_newton"
177   "		  max_iterations = 50"
178   "		  convergence_tolerance = 1e-4"
179   "	variables,"
180   "		continuous_design = 2"
181   "		  descriptors 'x1' 'x2'"
182   "	interface,"
183   "		direct"
184   "		  analysis_driver = 'plugin_text_book'"
185   "	responses,"
186   "		num_objective_functions = 1"
187   "		num_nonlinear_inequality_constraints = 2"
188   "		analytic_gradients"
189   "		no_hessians";
190 
191 
192 /** Simplest library case: this function parses from an input file to define the
193     ProblemDescDB data. */
run_dakota_parse(const char * dakota_input_file)194 void run_dakota_parse(const char* dakota_input_file)
195 {
196   // Parse input and construct Dakota LibraryEnvironment, performing
197   // input data checks
198   Dakota::ProgramOptions opts;
199   opts.input_file(dakota_input_file);
200 
201   // Defaults constructs the MPIManager, which assumes COMM_WORLD
202   Dakota::LibraryEnvironment env(opts);
203 
204   if (env.mpi_manager().world_rank() == 0)
205     Cout << "Library mode 1: run_dakota_parse()\n";
206 
207   // plug the client's interface (function evaluator) into the Dakota
208   // environment; in serial case, demonstrate the simpler plugin method
209   if (env.mpi_manager().mpirun_flag())
210     parallel_interface_plugin(env);
211   else
212     serial_interface_plugin(env);
213 
214   // Execute the environment
215   env.execute();
216 }
217 
218 
219 /** Rather than parsing from an input file, this function populates
220     Data class objects directly using a minimal specification and
221     relies on constructor defaults and post-processing in
222     post_process() to fill in the rest. */
run_dakota_data()223 void /*Dakota::*/run_dakota_data()
224 {
225   // Instantiate the LibraryEnvironment and underlying ProblemDescDB
226 
227   // No input file set --> no parsing.  Could set other command line
228   // options such as restart in opts:
229   Dakota::ProgramOptions opts;
230 
231   // delay validation/sync of the Dakota database and iterator
232   // construction to allow update after all data is populated
233   bool check_bcast_construct = false;
234 
235   // set up a Dakota instance, with the right MPI configuration if a
236   // parallel run (don't need to pass the MPI comm here, just doing to
237   // demonstrate/test).
238   Dakota::LibraryEnvironment env(MPI_COMM_WORLD, opts, check_bcast_construct);
239 
240   // configure Dakota to throw a std::runtime_error instead of calling exit
241   env.exit_mode("throw");
242 
243   // Now set the various data to specify the Dakota study
244   Dakota::DataMethod   dme; Dakota::DataModel    dmo;
245   Dakota::DataVariables dv; Dakota::DataInterface di; Dakota::DataResponses dr;
246   Dakota::ParallelLibrary& parallel_lib = env.parallel_library();
247   if (parallel_lib.world_rank() == 0) {
248     Cout << "Library mode 2: run_dakota_data()\n";
249     // This version uses direct Data instance population.  Initial instantiation
250     // populates all the defaults.  Default Environment and Model data are used.
251     Dakota::DataMethodRep& dmr = *dme.data_rep();
252     Dakota::DataVariablesRep& dvr = *dv.data_rep();
253     Dakota::DataInterfaceRep& dir = *di.data_rep();
254     Dakota::DataResponsesRep& drr = *dr.data_rep();
255     // Set any non-default values: mimic default_input
256     dmr.methodName = Dakota::OPTPP_Q_NEWTON;
257     dvr.numContinuousDesVars = 2;
258     dir.interfaceType = Dakota::TEST_INTERFACE;
259     if (parallel_lib.mpirun_flag()) {
260       dir.analysisDrivers.push_back("plugin_text_book");
261       drr.numObjectiveFunctions = 1;
262       drr.numNonlinearIneqConstraints = 2;
263     }
264     else {
265       dir.analysisDrivers.push_back("plugin_rosenbrock");
266       drr.numObjectiveFunctions = 1;
267     }
268     drr.gradientType = "analytic";
269     drr.hessianType  = "none";
270   }
271   env.insert_nodes(dme, dmo, dv, di, dr);
272 
273   // once done with changes: check database, broadcast, and construct iterators
274   env.done_modifying_db();
275 
276   // plug the client's interface (function evaluator) into the Dakota
277   // environment; in serial case, demonstrate the simpler plugin method
278   if (env.mpi_manager().mpirun_flag())
279     parallel_interface_plugin(env);
280   else
281     serial_interface_plugin(env);
282 
283   // Execute the environment
284   env.execute();
285 }
286 
287 
288 
289 /// Function to encapsulate the Dakota object instantiations for
290 /// mode 3: mixed parsing and direct updating
291 
292 /** This function showcases multiple features.  For parsing, either an
293     input file (dakota_input_file != NULL) or a default input string
294     (dakota_input_file == NULL) are shown.  This parsed input is then
295     mixed with input from three sources: (1) input from a
296     user-supplied callback function, (2) updates to the DB prior to
297     Environment instantiation, (3) updates directly to Iterators/Models
298     following Environment instantiation. */
run_dakota_mixed(const char * dakota_input_file,bool mpirun_flag)299 void run_dakota_mixed(const char* dakota_input_file, bool mpirun_flag)
300 {
301   Dakota::ProgramOptions opts;
302   // Could specify output redirection & restart processing in opts if needed
303   opts.echo_input(true);
304 
305   // in this use case, input file may be null:
306   if (dakota_input_file)
307     opts.input_file(dakota_input_file);
308 
309   // when no input file, use input string appropraite for MPI mode
310   if (!dakota_input_file) {
311     if (mpirun_flag)
312       opts.input_string(parallel_input);
313     else
314       opts.input_string(serial_input);
315   }
316 
317   // Setup client data to be available during callback: upper variable bound
318   callback_data data;
319   data.rosen_cdv_upper_bd = 2.0;
320 
321   // Construct library environment, parsing input file or string, then
322   // calling back to the callback_function, passing data to it.
323 
324   // However delay braodcast and validation of the db due to further
325   // data manipulations below, e.g., to avoid large default vector
326   // creation)
327   bool done_with_db = false;
328 
329   Dakota::LibraryEnvironment env(opts, done_with_db, callback_function, &data);
330 
331   Dakota::ParallelLibrary& parallel_lib = env.parallel_library();
332   int world_rank = parallel_lib.world_rank();
333   if (world_rank == 0)
334     Cout << "Library mode 3: run_dakota_mixed()\n";
335 
336   // Demonstrate changes to DB data initially set by parse_inputs():
337   // if we're using rosenbrock, change the initial guess.  This update is
338   // performed only on rank 0
339   Dakota::ProblemDescDB&   problem_db   = env.problem_description_db();
340   if (world_rank == 0) {
341     problem_db.resolve_top_method(); // allow DB set/get operations
342     const Dakota::StringArray& drivers
343       = problem_db.get_sa("interface.application.analysis_drivers");
344     if (drivers.size() == 1 && drivers[0] == "plugin_rosenbrock") {
345       Dakota::RealVector ip(2);
346       ip[0] =  1.1;  ip[1] = -1.3;
347       problem_db.set("variables.continuous_design.initial_point", ip);
348     }
349   }
350 
351   // check, broadcast to sync DB data across ranks , and construct
352   // iterators/models
353   env.done_modifying_db();
354 
355   // plug the client's interface (function evaluator) into the Dakota
356   // environment; in serial case, demonstrate the simpler plugin method
357   if (env.mpi_manager().mpirun_flag())
358     parallel_interface_plugin(env);
359   else
360     serial_interface_plugin(env);
361 
362   // Demonstrate changes to data after the Environment has been instantiated.
363   // In this case, the DB is not updated since its data has already been
364   // extracted; rather, we must update the Environment's Iterators and Models
365   // directly.  Iterator updates should be performed only on the Iterator
366   // master processor, but Model updates are performed on all processors.
367   Dakota::ModelList models
368     = env.filtered_model_list("simulation", "direct", "plugin_text_book");
369   Dakota::ModelLIter ml_iter;
370   for (ml_iter = models.begin(); ml_iter != models.end(); ml_iter++) {
371     const Dakota::StringArray& drivers
372       = ml_iter->derived_interface().analysis_drivers();
373     if (drivers.size() == 1 && drivers[0] == "plugin_text_book") {
374       // Change initial guess:
375       //ml_iter->continuous_variables(T);
376       // Change a lower bound:
377       Dakota::RealVector lb(ml_iter->continuous_lower_bounds()); // copy
378       lb[0] += 0.1;
379       ml_iter->continuous_lower_bounds(lb);
380     }
381   }
382 
383   // Execute the environment
384   env.execute();
385 }
386 
387 
388 /** Demonstration of simple plugin where client code doesn't require
389     access to detailed Dakota data (such as Model-based parallel
390     configuration information) to construct the DirectApplicInterface.
391     This example plugs-in a derived serial direct application
392     interface instance ("plugin_rosenbrock"). */
serial_interface_plugin(Dakota::LibraryEnvironment & env)393 void serial_interface_plugin(Dakota::LibraryEnvironment& env)
394 {
395   std::string model_type(""); // demo: empty string will match any model type
396   std::string interf_type("direct");
397   std::string an_driver("plugin_rosenbrock");
398 
399   Dakota::ProblemDescDB& problem_db = env.problem_description_db();
400   Dakota::Interface* serial_iface =
401     new SIM::SerialDirectApplicInterface(problem_db);
402 
403   bool plugged_in =
404     env.plugin_interface(model_type, interf_type, an_driver, serial_iface);
405 
406   if (!plugged_in) {
407     Cerr << "Error: no serial interface plugin performed.  Check "
408 	 << "compatibility between parallel\n       configuration and "
409 	 << "selected analysis_driver." << std::endl;
410     Dakota::abort_handler(-1);
411   }
412 }
413 
414 
415 /** From a filtered list of Model candidates, plug-in a derived direct
416     application interface instance ("plugin_text_book" for parallel).
417     This approach provides more complete access to the Model, e.g.,
418     for access to analysis communicators. */
parallel_interface_plugin(Dakota::LibraryEnvironment & env)419 void parallel_interface_plugin(Dakota::LibraryEnvironment& env)
420 {
421   // get the list of all models matching the specified model, interface, driver:
422   Dakota::ModelList filt_models =
423     env.filtered_model_list("simulation", "direct", "plugin_text_book");
424   if (filt_models.empty()) {
425     Cerr << "Error: no parallel interface plugin performed.  Check "
426 	 << "compatibility between parallel\n       configuration and "
427 	 << "selected analysis_driver." << std::endl;
428     Dakota::abort_handler(-1);
429   }
430 
431   Dakota::ProblemDescDB& problem_db = env.problem_description_db();
432   Dakota::ModelLIter ml_iter;
433   size_t model_index = problem_db.get_db_model_node(); // for restoration
434   for (ml_iter = filt_models.begin(); ml_iter != filt_models.end(); ++ml_iter) {
435     // set DB nodes to input specification for this Model
436     problem_db.set_db_model_nodes(ml_iter->model_id());
437 
438     Dakota::Interface& model_interface = ml_iter->derived_interface();
439 
440     // Parallel case: plug in derived Interface object with an analysisComm.
441     // Note: retrieval and passing of analysisComm is necessary only if
442     // parallel operations will be performed in the derived constructor.
443 
444     // retrieve the currently active analysisComm from the Model.  In the most
445     // general case, need an array of Comms to cover all Model configurations.
446     const MPI_Comm& analysis_comm = ml_iter->analysis_comm();
447 
448     // don't increment ref count since no other envelope shares this letter
449     model_interface.assign_rep(std::make_shared<SIM::ParallelDirectApplicInterface>
450 			       (problem_db, analysis_comm));
451   }
452   problem_db.set_db_model_nodes(model_index);            // restore
453 }
454 
455 
456 /** Example of user-provided callback function (an instance of
457     Dakota::DbCallbackFunction) to override input provided by parsed Dakota
458     input file or input string data.  */
callback_function(Dakota::ProblemDescDB * db,void * ptr)459 static void callback_function(Dakota::ProblemDescDB* db, void *ptr)
460 {
461   callback_data *my_data = (callback_data*)ptr;
462   double my_rosen_ub = my_data->rosen_cdv_upper_bd;
463 
464   // Do something to put the DB in a usable set/get state (unlock and set list
465   // iterators).  The approach below is sufficient for simple input files, but
466   // more advanced usage would require set_db_list_nodes() or equivalent.
467   db->resolve_top_method();
468 
469   if ( !(db->get_ushort("interface.type") & DIRECT_INTERFACE_BIT) )
470     return;
471 
472   // supply labels, initial_point, and bounds
473   // Both Rosenbrock and text_book have the same number of variables (2).
474   Dakota::RealVector rv(2);
475   const Dakota::StringArray& drivers
476     = db->get_sa("interface.application.analysis_drivers");
477   if (Dakota::contains(drivers, "plugin_rosenbrock")) {
478     // Rosenbrock
479     rv[0] = -1.2; rv[1] =  1.;
480     db->set("variables.continuous_design.initial_point", rv);
481     rv[0] = -2.;  rv[1] = -2.;
482     db->set("variables.continuous_design.lower_bounds", rv);
483     rv[0] =  my_rosen_ub;
484     rv[1] =  my_rosen_ub;
485     db->set("variables.continuous_design.upper_bounds", rv);
486   }
487   else if (Dakota::contains(drivers, "plugin_text_book")) {
488     // text_book
489     rv[0] =  0.2;  rv[1] =  1.1;
490     db->set("variables.continuous_design.initial_point", rv);
491     rv[0] =  0.5;  rv[1] = -2.9;
492     db->set("variables.continuous_design.lower_bounds", rv);
493     rv[0] =  5.8;  rv[1] =  2.9;
494     db->set("variables.continuous_design.upper_bounds", rv);
495   }
496 }
497 
498 
499