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 //- Class:       SurrBasedGlobalMinimizer
10 //- Description: Implementation code for the SurrBasedGlobalMinimizer class
11 //- Owner:       John Eddy, Laura Swiler
12 //- Checked by:
13 
14 #include "SurrBasedGlobalMinimizer.hpp"
15 #include "ProblemDescDB.hpp"
16 #include "ParallelLibrary.hpp"
17 #include "ParamResponsePair.hpp"
18 #include "DakotaGraphics.hpp"
19 #include "dakota_system_defs.hpp"
20 #include "DakotaInterface.hpp"
21 #include "DakotaApproximation.hpp"
22 #include <algorithm>
23 #include <iostream>
24 #include <string>
25 
26 static const char rcsId[]="@(#) $Id: SurrBasedGlobalMinimizer.cpp 7031 2010-10-22 16:23:52Z mseldre $";
27 
28 namespace Dakota {
29 
30 
31 SurrBasedGlobalMinimizer::
SurrBasedGlobalMinimizer(ProblemDescDB & problem_db,Model & model)32 SurrBasedGlobalMinimizer(ProblemDescDB& problem_db, Model& model):
33   SurrBasedMinimizer(problem_db, model, std::shared_ptr<TraitsBase>(new SurrBasedGlobalTraits())),
34   replacePoints(probDescDB.get_bool("method.sbg.replace_points"))
35 {
36   // Verify that iteratedModel is a surrogate model so that
37   // approximation-related functions are defined.
38   if (iteratedModel.model_type() != "surrogate") {
39     Cerr << "Error: SurrBasedGlobalMinimizer::iteratedModel must be a "
40 	 << "surrogate model." << std::endl;
41     abort_handler(-1);
42   }
43 
44   // historical default convergence tolerance
45   if (convergenceTol < 0.0) convergenceTol = 1.0e-4;
46 
47   // While this copy will be replaced in best update, initialize here
48   // since relied on in Minimizer::initialize_run when a sub-iterator
49   bestVariablesArray.push_back(
50     iteratedModel.truth_model().current_variables().copy());
51 
52   // Instantiate the approximate sub-problem minimizer
53   const String& approx_method_ptr
54     = probDescDB.get_string("method.sub_method_pointer");
55   const String& approx_method_name
56     = probDescDB.get_string("method.sub_method_name");
57   if (!approx_method_ptr.empty()) {
58     // Approach 1: method spec support for approxSubProbMinimizer
59     const String& model_ptr = probDescDB.get_string("method.model_pointer");
60     size_t method_index = probDescDB.get_db_method_node(); // for restoration
61     probDescDB.set_db_method_node(approx_method_ptr); // method only
62     // sub-problem minimizer will use shallow copy of iteratedModel
63     // (from problem_db.get_model())
64     approxSubProbMinimizer = probDescDB.get_iterator();//(iteratedModel);
65     // suppress DB ctor default and don't output summary info
66     approxSubProbMinimizer.summary_output(false);
67     // verify approx method's modelPointer is empty or consistent
68     const String& am_model_ptr = probDescDB.get_string("method.model_pointer");
69     if (!am_model_ptr.empty() && am_model_ptr != model_ptr)
70       Cerr << "Warning: SBO approx_method_pointer specification includes an\n"
71 	   << "         inconsistent model_pointer that will be ignored."
72 	   << std::endl;
73     probDescDB.set_db_method_node(method_index); // restore method only
74   }
75   else if (!approx_method_name.empty())
76     // Approach 2: instantiate on-the-fly w/o method spec support
77     approxSubProbMinimizer
78       = probDescDB.get_iterator(approx_method_name, iteratedModel);
79 }
80 
81 
~SurrBasedGlobalMinimizer()82 SurrBasedGlobalMinimizer::~SurrBasedGlobalMinimizer()
83 { }
84 
85 
86 /** This just specializes the Iterator implementation to perform
87     default tabulation on the truth model instead of surrogate
88     model. */
initialize_graphics(int iterator_server_id)89 void SurrBasedGlobalMinimizer::initialize_graphics(int iterator_server_id)
90 {
91   Model& truth_model = iteratedModel.truth_model();
92   OutputManager& mgr = parallelLib.output_manager();
93   Graphics& dakota_graphics = mgr.graphics();
94   const Variables& vars = truth_model.current_variables();
95   const Response&  resp = truth_model.current_response();
96   bool auto_log = false;
97 
98   // For graphics, limit (currently) to server id 1, for both ded master
99   // (parent partition rank 1) and peer partitions (parent partition rank 0)
100   if (mgr.graph2DFlag && iterator_server_id == 1) { // initialize the 2D plots
101     dakota_graphics.create_plots_2d(vars, resp);
102     auto_log = true;
103   }
104 
105   // For output/restart/tabular data, all Iterator masters stream output
106   if (mgr.tabularDataFlag) { // initialize data tabulation
107     mgr.create_tabular_datastream(vars, resp);
108     auto_log = true;
109   }
110 
111   if (auto_log)
112     truth_model.auto_graphics(true);
113 }
114 
115 
core_run()116 void SurrBasedGlobalMinimizer::core_run()
117 {
118   // Extract subIterator/subModel(s) from the SurrogateModel
119   Model&    truth_model   = iteratedModel.truth_model();
120   Model&    approx_model  = iteratedModel.surrogate_model();
121   Iterator& dace_iterator = iteratedModel.subordinate_iterator();
122 
123   // This flag controls the method by which we introduce new results data
124   // into the surrogate for updating.  Right now, there are two methods
125   // supported.  The first is to create each subsequent surrogate using every
126   // truth point evaluated in all previous iterations.  This is identified with
127   // the string "append".  The second is to rebuild the surrogate at each
128   // iteration using the original truth samples and the truth solutions from
129   // the previous iteration only.  This is identified with the string "replace".
130 
131   // Update DACE settings for global approximations.  Check that dace_iterator
132   // is defined (a dace_iterator specification is not required when the data
133   // samples are read in from a file rather than obtained from sampling).
134   if (!dace_iterator.is_null())
135     dace_iterator.active_set_request_values(1);
136 
137   // get data points using sampling, file read, or whatever.
138   iteratedModel.build_approximation();
139 
140   // The points obtained using sampling will not change from here on out nor
141   // will the arrays that store them.  We will keep them here for use in
142   // rebuilding the surrogate if using "replace" for example.
143 
144   bool returns_multipoint = approxSubProbMinimizer.returns_multiple_points(),
145        accepts_multipoint = approxSubProbMinimizer.accepts_multiple_points(),
146        truth_asynch_flag  = truth_model.asynch_flag();
147 
148   // This flag will be used to indicate when we are finished iterating.  An
149   // iteration is a solution of the approximate model followed by an update
150   // of the surrogate.
151   while (globalIterCount < maxIterations) {
152 
153     // Test how well the surrogate matches up with the truth model.  For this
154     // test, we currently use R-squared as a measure of goodness of fit,
155     // although we can easily generalize it.  Also, currently we state that if
156     // R-squared is < 0.5 or > 1.1 (R-squared can be greater than one in
157     // abnormal cases for surrogates that are not polynomial regression models),
158     // we stop the surrogate-based global minimization process because it will
159     // not work with such an inaccurate model.
160     std::vector<Approximation>& approxs
161       = approx_model.derived_interface().approximations();
162     std::vector<Approximation>::iterator it;
163     for (it=approxs.begin(); it!=approxs.end(); ++it) {
164       if (it->diagnostics_available()) {
165 
166 	// Start the check with the r-squared value.
167 	Real r2_diagnostic = it->diagnostic("rsquared");
168 	if (outputLevel > NORMAL_OUTPUT)
169 	  Cout << "R-squared = " << r2_diagnostic << std::endl;
170 
171 	// If outside of tolerable range, report and abort.
172 	// TODO: report function index along with diagnostic.
173 	if (r2_diagnostic < 0.5 || r2_diagnostic > 1.1) {
174 	  Cerr << "Surrogate approximation is not accurate enough for "
175 	       << "the surrogate-based global minimization.\n"
176 	       << "The minimization has quit before the requested number "
177 	       << "of iterations due to poor surrogate fit." << std::endl;
178 	  abort_handler(-1);
179 	}
180 
181 	// Add some additional diagnostics?
182       }
183     }
184 
185     // use the iterator to solve the approximate subproblem.  On the first
186     // iteration, the surrogate is built using only the original truth
187     // samples.  At each subsequent iteration, the surrogate includes
188     // additional truth samples from validation of subproblem solutions.
189     ParLevLIter pl_iter = methodPCIter->mi_parallel_level_iterator(miPLIndex);
190     approxSubProbMinimizer.run(pl_iter);
191 
192     // Get the results from the iterator execution.
193     VariablesArray vars_results;
194     if (returns_multipoint)
195       vars_results = approxSubProbMinimizer.variables_array_results();
196     else
197       vars_results.push_back(approxSubProbMinimizer.variables_results());
198     size_t i, num_results = vars_results.size();
199 
200     // Variable/response results were generated using the current approximate
201     // model.  For appending to the current approximate model, we must evaluate
202     // the variable results with the truth model.
203     iteratedModel.component_parallel_mode(TRUTH_MODEL_MODE);
204     IntResponseMap truth_resp_results;
205     for (i=0; i<num_results; i++) {
206       // set the current values of the active variables in the truth model
207       truth_model.active_variables(vars_results[i]);
208 
209       // request the evaluation in synchronous or asyncronous mode.
210       if (truth_asynch_flag)
211         truth_model.evaluate_nowait();
212       else {
213         truth_model.evaluate();
214 	truth_resp_results[truth_model.evaluation_id()]
215 	  = truth_model.current_response().copy();
216       }
217     }
218     // If we did our evaluations asynchronously, use synchronize to block
219     // until all the results are available and then store these responses.
220     if (truth_asynch_flag)
221       truth_resp_results = truth_model.synchronize();
222 
223     // Beyond this point, we will want to know if this is the last iteration.
224     // We will use this information to prevent updating of the surrogate since
225     // it will not be used again.
226     bool last_iter = ++globalIterCount >= maxIterations;
227 
228     if (outputLevel > QUIET_OUTPUT) {
229       // In here we want to write the truth values into a simple tab delimited
230       // file so that we can easily compare them with the surrogate values of
231       // the points returned by the iterator.
232       std::string ofname("finaldatatruth" + std::to_string(globalIterCount) +
233 			 ".dat");
234       std::ofstream ofile(ofname);
235       ofile.precision(12);
236       IntRespMCIter it = truth_resp_results.begin();
237       for (i=0; i<num_results; ++i, ++it) {
238 	const RealVector&  c_vars = vars_results[i].continuous_variables();
239 	const IntVector&  di_vars = vars_results[i].discrete_int_variables();
240 	const RealVector& dr_vars = vars_results[i].discrete_real_variables();
241 	const RealVector& fn_vals = it->second.function_values();
242 	std::copy(c_vars.values(), c_vars.values() + c_vars.length(),
243 	          std::ostream_iterator<Real>(ofile,"\t"));
244 	std::copy(di_vars.values(), di_vars.values() + di_vars.length(),
245 	          std::ostream_iterator<int>(ofile,"\t"));
246 	std::copy(dr_vars.values(), dr_vars.values() + dr_vars.length(),
247 	          std::ostream_iterator<Real>(ofile,"\t"));
248 	std::copy(fn_vals.values(), fn_vals.values() + fn_vals.length(),
249 	          std::ostream_iterator<Real>(ofile,"\t"));
250 	ofile << '\n';
251       }
252       ofile.close();
253     }
254 
255     // See if we are done
256     if (last_iter) { // catalogue final results in best{Variables,Response}Array
257       if (returns_multipoint) {
258 	bestVariablesArray = vars_results;
259 	copy_data(truth_resp_results, bestResponseArray);
260       }
261       else {
262 	bestVariablesArray.front().active_variables(vars_results.front());
263 	bestResponseArray.front().function_values(
264 	  approxSubProbMinimizer.response_results().function_values());
265       }
266     }
267     else {
268       // restore state prior to previous append_approximation()
269       if (replacePoints && globalIterCount > 1)
270 	approx_model.pop_approximation(false);// don't store SDP set; no restore
271       // update the data set and rebuild the approximation
272       approx_model.append_approximation(vars_results, truth_resp_results, true);
273 
274       // pass iterator's final vars for use as next set of initial points
275       if (accepts_multipoint)
276 	approxSubProbMinimizer.initial_points(vars_results);
277       else
278 	approx_model.active_variables(vars_results.front());
279     }
280   }
281 }
282 
283 } // namespace Dakota
284