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: An API for launching DAKOTA from a DLL.
10 //- Owner:       Bill Hart
11 //- Checked by:
12 //- Version: $Id$
13 
14 /** \file dakota_dll_api.cpp
15     \brief This file contains a DakotaRunner class, which launches DAKOTA. */
16 
17 #include "dakota_windows.h"
18 #include "dakota_system_defs.hpp"
19 #include "ProgramOptions.hpp"
20 #include "LibraryEnvironment.hpp"
21 #include "ProblemDescDB.hpp"
22 #include "PRPMultiIndex.hpp"
23 #include "DakotaModel.hpp"
24 #include "DakotaInterface.hpp"
25 #include "PluginSerialDirectApplicInterface.hpp"
26 #include "dakota_global_defs.hpp"
27 #include "dakota_dll_api.h"
28 #include <string>
29 
30 #if defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__)
31 #define BUILDING_DAKOTA_DLL
32 #endif
33 
34 
35 namespace Dakota {
36   extern PRPCache data_pairs;
37 }
38 
39 using namespace Dakota;
40 
41 namespace {
42 
43 /// initialize signal handlers (not using Dakota's helper function
44 /// since DLL may need different behavior.)
signal_init()45 void signal_init()
46 {
47 #if defined(__MINGW32__) || defined(_MSC_VER)
48   std::signal(SIGBREAK, abort_handler);
49 #else
50   std::signal(SIGKILL, abort_handler);
51 #endif
52   std::signal(SIGTERM, abort_handler);
53   std::signal(SIGINT,  abort_handler);
54 }
55 
56 /// Class to manage an instance of Dakota's library interface for
57 /// presentation to the Dakota DLL interface.
58 class DakotaRunner
59 {
60 public:
61 
62   /// Construct a runner object, setting output/error file names with logname
DakotaRunner(std::string logname)63   DakotaRunner(std::string logname)
64     : dakotaEnv(NULL), numVars(0), varNames(NULL), numResp(0), respNames(NULL)
65   {
66     signal_init();
67 
68     // Add -output and -error arguments to the command line with
69     // filename.log and filename.err, respectively.
70     progOpts.output_file(logname + ".log");
71     progOpts.error_file(logname + ".err");
72   }
73 
74   /// Destroy the runner object, freeing any allocated memory
~DakotaRunner()75   ~DakotaRunner()
76   {
77     if (respNames) {
78       for (size_t i=0; i<numResp; i++) {
79 	// memory allocated with strdup requires free, not delete
80 	std::free(respNames[i]);
81       }
82       delete[] respNames;
83     }
84 
85     if (varNames) {
86       for (size_t i=0; i<numVars; i++) {
87 	// memory allocated with strdup requires free, not delete
88 	std::free(varNames[i]);
89       }
90       delete[] varNames;
91     }
92 
93     if (dakotaEnv) {
94       delete dakotaEnv;
95       dakotaEnv = NULL;
96     }
97   }
98 
99   /// Set the input file and parse it, creating a Dakota
100   /// LibraryEnvironment instance
read_input(const char * dakota_input)101   void read_input(const char* dakota_input)
102   {
103     progOpts.input_file(dakota_input);
104 
105     /// this shouldn't happen, but was a safeguard in historical code
106     if (dakotaEnv)
107       delete dakotaEnv;
108     dakotaEnv = new LibraryEnvironment(progOpts);
109 
110     if (!dakotaEnv)
111       throw std::logic_error("DakotaRunner: could not instantiate LibraryEnvironment");
112 
113     // initialize variable and response names
114     initialize_names();
115   }
116 
initialize_names()117   void initialize_names()
118   {
119 
120     ProblemDescDB& problem_db = dakotaEnv->problem_description_db();
121 
122     // set the variable names
123     const VariablesList& vlist = problem_db.variables_list();
124     VariablesList::const_iterator vlist_it;
125     VariablesList::const_iterator vlist_end = vlist.end();
126 
127     // calculate total number of vars by iterating over each set
128     // overly cautious check for non-empty labels (shouldn't they have
129     // defaults?)
130     numVars = 0;
131     for (vlist_it = vlist.begin(); vlist_it != vlist_end; ++vlist_it)
132       numVars += vlist_it->all_continuous_variable_labels().size() +
133 	vlist_it->all_discrete_int_variable_labels().size() +
134 	vlist_it->all_discrete_real_variable_labels().size();
135     // if appropriate, populate name array
136     if (numVars > 0) {
137       varNames = new char* [numVars];
138       size_t j, idx = 0;
139       for (vlist_it = vlist.begin(); vlist_it != vlist_end; ++vlist_it) {
140 	StringMultiArrayConstView acv_labels
141 	  = vlist_it->all_continuous_variable_labels();
142 	StringMultiArrayConstView adiv_labels
143 	  = vlist_it->all_discrete_int_variable_labels();
144 	StringMultiArrayConstView adrv_labels
145 	  = vlist_it->all_discrete_real_variable_labels();
146 	for (j=0; j<acv_labels.size(); ++j, ++idx)
147 	  varNames[idx] = strdup(acv_labels[j].c_str());
148 	for (j=0; j<adiv_labels.size(); ++j, ++idx)
149 	  varNames[idx] = strdup(adiv_labels[j].c_str());
150 	for (j=0; j<adrv_labels.size(); ++j, ++idx)
151 	  varNames[idx] = strdup(adrv_labels[j].c_str());
152       }
153     }
154 
155     // set the response names
156     const ResponseList& rlist = problem_db.response_list();
157     ResponseList::const_iterator rlist_it;
158     ResponseList::const_iterator rlist_end = rlist.end();
159 
160     // calculate total number of responses by iterating over each set
161     numResp = 0;
162     for (rlist_it = rlist.begin(); rlist_it != rlist_end; ++rlist_it)
163       numResp += rlist_it->function_labels().size();
164     // if appropriate, populate name array
165     if (numResp > 0) {
166       respNames = new char* [numResp];
167       size_t j, idx = 0;
168       for (rlist_it = rlist.begin(); rlist_it != rlist_end; ++rlist_it) {
169 	const StringArray& fn_labels = rlist_it->function_labels();
170 	for (j=0; j<fn_labels.size(); ++j, ++idx)
171 	  respNames[idx] = strdup(fn_labels[j].c_str());
172       }
173     }
174 
175   }
176 
177   /// Plugin interfaces and execute strategy
178   void start();
179 
180   // for tracking variable and response names
181   int numVars;       ///< number of variables active in DAKOTA
182   char** varNames;   ///< array of strings of variable names
183   int numResp;       ///< number of responses active in DAKOTA
184   char** respNames;  ///< array of strings of response names
185 
186   static int id_ctr; ///< counter for next instance ID to return
187 
188 private:
189 
190   /// don't allow default construction due to memory management concerns
191   DakotaRunner();
192   // TOOD: disallow copy/assign as well
193 
194   /// Options to control the behavior of the Dakota instance
195   ProgramOptions progOpts;
196   /// Pointer to the Dakota instance
197   LibraryEnvironment* dakotaEnv;
198 
199 };
200 
201 int DakotaRunner::id_ctr = 0;
202 
start()203 void DakotaRunner::start()
204 {
205   // Any library mode plug-ins would go here.
206   // Refer to the library mode documentation in the Developers Manual.
207   ProblemDescDB& problem_db = dakotaEnv->problem_description_db();
208   ModelList& models = problem_db.model_list();
209   size_t model_index = problem_db.get_db_model_node(); // for restoration
210   for (ModelLIter ml_iter = models.begin(); ml_iter != models.end(); ml_iter++){
211     Interface& model_interface = ml_iter->derived_interface();
212     if ( (model_interface.interface_type() & DIRECT_INTERFACE_BIT) &&
213 	 contains(model_interface.analysis_drivers(), "plugin_rosenbrock") ) {
214       // set the DB nodes to that of the existing Model specification
215       problem_db.set_db_model_nodes(ml_iter->model_id());
216       // plug in the new derived Interface object
217       model_interface.assign_rep(std::make_shared<SIM::SerialDirectApplicInterface>
218 				 (problem_db));
219     }
220   }
221   problem_db.set_db_model_nodes(model_index);            // restore
222 
223   // Execute the Dakota environment assume proceeding beyond help/version/check
224   if (!dakotaEnv->check()) {
225 
226     // In case we're running a sequence of DAKOTA problems, make sure
227     // the global evaluation cache is cleared in between runs.
228     // Ideally, we'd manage this with interface IDs from the caller
229     // instead of this aggressive clear.
230     data_pairs.clear();
231 
232     dakotaEnv->execute();
233 
234   }
235 }
236 
237 /// map from DakotaRunner id to instance
238 std::map<int ,DakotaRunner*> runners;
239 
240 } // end global namespace
241 
dakota_create(int * dakota_ptr_int,const char * logname)242 extern "C" void DAKOTA_DLL_FN dakota_create(int* dakota_ptr_int, const char* logname)
243 {
244   // logname is the base filename for output and error to .log and .err
245   std::string str_logname = logname ? logname : "dakota_dll";
246   DakotaRunner* pDakota = new DakotaRunner(str_logname);
247   // increment the runner id and return to the caller
248   int id = DakotaRunner::id_ctr++;
249   runners[id] = pDakota;
250   *dakota_ptr_int = id;
251 }
252 
dakota_readInput(int id,const char * dakotaInput)253 extern "C" int DAKOTA_DLL_FN dakota_readInput(int id, const char* dakotaInput)
254 {
255   try {
256     runners[id]->read_input(dakotaInput);
257   }
258   catch (std::logic_error le) {
259     Cout << "Dakota::dll_api readInput caught " << le.what() << std::endl;
260     return(-2);
261   }
262   return(0);
263 }
264 
265 extern "C" void DAKOTA_DLL_FN
dakota_get_variable_info(int id,char *** pVarNames,int * pNumVarNames,char *** pRespNames,int * pNumRespNames)266 dakota_get_variable_info(int id,
267 			 char*** pVarNames, int* pNumVarNames,
268 			 char*** pRespNames, int* pNumRespNames)
269 {
270   *pNumVarNames = runners[id]->numVars;
271   *pVarNames = runners[id]->varNames;
272   *pNumRespNames = runners[id]->numResp;
273   *pRespNames = runners[id]->respNames;
274 }
275 
276 
dakota_start(int id)277 extern "C" int DAKOTA_DLL_FN dakota_start(int id)
278 {
279   try {
280     runners[id]->start();
281   }
282   catch (std::logic_error le) {
283     Cout << "Dakota::dll_api start caught " << le.what() << std::endl;
284     return(-1);
285   }
286   return(0);
287 }
288 
dakota_destroy(int id)289 extern "C" void DAKOTA_DLL_FN dakota_destroy (int id)
290 {
291   delete runners[id];
292   runners.erase(id);
293 }
294 
dakota_stop(int * id)295 extern "C" void DAKOTA_DLL_FN dakota_stop(int* id)
296 {
297 /** TODO: trick application to quit through the syscall interface or
298     throw exception. **/
299 }
300 
dakota_getStatus(int id)301 extern "C" const char* DAKOTA_DLL_FN dakota_getStatus(int id)
302 {
303   static std::string tmp;
304   tmp = "<DakotaOutput>None</DakotaOutput>";
305   return tmp.c_str();
306 }
307 
get_mc_ptr_int()308 extern "C" int get_mc_ptr_int()
309 {
310 #ifdef DAKOTA_MODELCENTER
311   return Dakota::mc_ptr_int;
312 #else
313   return 0;
314 #endif
315 }
316 
set_mc_ptr_int(int ptr_int)317 extern "C" void set_mc_ptr_int(int ptr_int)
318 {
319 #ifdef DAKOTA_MODELCENTER
320   Dakota::mc_ptr_int = ptr_int;
321 #endif
322 }
323 
get_dc_ptr_int()324 extern "C" int get_dc_ptr_int()
325 {
326 #ifdef DAKOTA_MODELCENTER
327   return Dakota::dc_ptr_int;
328 #else
329   return 0;
330 #endif
331 }
332 
set_dc_ptr_int(int ptr_int)333 extern "C" void set_dc_ptr_int(int ptr_int)
334 {
335 #ifdef DAKOTA_MODELCENTER
336   Dakota::dc_ptr_int = ptr_int;
337 #endif
338 }
339