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