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:       DataTransformModel
10 //- Description: Implementation code for the DataTransformModel class
11 //- Owner:       Brian Adams
12 //- Checked by:
13 
14 #include "DataTransformModel.hpp"
15 #include "ExperimentData.hpp"
16 #include "DakotaMinimizer.hpp"
17 #include "PRPMultiIndex.hpp"
18 #include "ResultsManager.hpp"
19 
20 static const char rcsId[]="@(#) $Id$";
21 
22 namespace Dakota {
23 
24 extern PRPCache data_pairs; // global container
25 
26 /// initialization of static needed by RecastModel
27 DataTransformModel* DataTransformModel::dtModelInstance(NULL);
28 
29 // BMA TODO:
30 // * Construct with the Iterator's verbosity or the Model's?  Models
31 //   default to same as their Iterator...
32 // * Verify that the default variables, active set, and secondary
33 //   response mapping suffice Need test with data and constraints
34 //  * Don't want to output message during recast retrieve... (or do we?)
35 
36 /** This constructor computes various indices and mappings, then
37     updates the properties of the RecastModel.  Hyper-parameters are
38     assumed to trail the active continuous variables when presented to
39     this RecastModel */
40 DataTransformModel::
DataTransformModel(const Model & sub_model,const ExperimentData & exp_data,size_t num_hyper,unsigned short mult_mode,short recast_resp_deriv_order)41 DataTransformModel(const Model& sub_model, const ExperimentData& exp_data,
42                    size_t num_hyper, unsigned short mult_mode,
43                    short recast_resp_deriv_order):
44   // BMA TODO: should the BitArrays be empty or same as submodel?
45   // recast_secondary_offset is the index to the equality constraints within
46   // the secondary responses
47   RecastModel(sub_model, variables_expand(sub_model, num_hyper),
48 	      BitArray(), BitArray(), exp_data.num_total_exppoints(),
49 	      sub_model.num_secondary_fns(),
50 	      sub_model.num_nonlinear_ineq_constraints(),
51               response_order(sub_model, recast_resp_deriv_order)),
52   expData(exp_data), numHyperparams(num_hyper), obsErrorMultiplierMode(mult_mode)
53 {
54   modelId = RecastModel::recast_model_id(root_model_id(), "DATA_TRANSFORM");
55   // register state variables as inactive vars if config vars are present
56   // BMA TODO: correctly manage the view if relaxed, also review recursion
57 
58   // BMA NOTE: This will change the inactive view of any Variables object
59   // sharing the same SharedVariables data as the subModel's Variables
60   size_t num_config_vars = expData.num_config_vars();
61   if (num_config_vars > 0) {
62     subModel.inactive_view(MIXED_STATE);
63     int num_state_vars =
64       subModel.icv() + subModel.idiv() + subModel.idsv() + subModel.idrv();
65     if (num_state_vars != num_config_vars) {
66       Cerr << "\nError: (DataTransformModel) Number of state "
67 	   << "variables = " << num_state_vars << " must match\n       number "
68 	   << "of configuration variables = " << num_config_vars << "\n";
69       abort_handler(-1);
70     }
71   }
72 
73   size_t num_submodel_primary = sub_model.num_primary_fns();
74   // the RecastModel will have one residual per experiment datum
75   size_t num_recast_primary = expData.num_total_exppoints(),
76     num_secondary = sub_model.num_secondary_fns(),
77     num_recast_fns = num_recast_primary + num_secondary;
78 
79   // ---
80   // Variables mapping (one-to-one), truncating trailing hyper-parameters
81   // ---
82 
83   // BMA TODO: what variables subset and ordering convention do we
84   // want to assume here? Do we need to iterate sub-types, or just the
85   // containers...Assuming active for now.
86 
87   // For now, we assume that any hyper-parameters are appended to the
88   // active continuous variables, and that active discrete int,
89   // string, real follow in both the recast and sub-model
90   size_t submodel_cv = sub_model.cv();
91   size_t submodel_dv = sub_model.div() + sub_model.dsv() + sub_model.drv();
92   Sizet2DArray vars_map_indices(submodel_cv + submodel_dv);
93   for (size_t i=0; i<submodel_cv; ++i) {
94     vars_map_indices[i].resize(1);
95     vars_map_indices[i][0] = i;
96   }
97   // skip the trailing continuous hyper-parameters
98   size_t recast_dv_start = submodel_cv + numHyperparams;
99   for (size_t i=0; i<submodel_dv; ++i) {
100     vars_map_indices[submodel_cv + i].resize(1);
101     vars_map_indices[submodel_cv + i][0] = recast_dv_start + i;
102   }
103   bool nonlinear_vars_mapping = false;
104 
105   // mappings from the submodel Response to residual Response
106   BoolDequeArray nonlinear_resp_mapping(num_recast_fns);
107 
108   // ---
109   // Primary mapping
110   // ---
111   Sizet2DArray primary_resp_map_indices(num_recast_primary);
112   const Response& curr_resp = sub_model.current_response();
113   const SharedResponseData& srd = curr_resp.shared_data();
114   gen_primary_resp_map(srd, primary_resp_map_indices, nonlinear_resp_mapping);
115 
116   // ---
117   // Secondary mapping (one-to-one)
118   // ---
119   Sizet2DArray secondary_resp_map_indices(num_secondary);
120   for (size_t i=0; i<num_secondary; i++) {
121     secondary_resp_map_indices[i].resize(1);
122     // the recast constraints just depend on the simulation
123     // constraints, which start at num_submodel_primary
124     secondary_resp_map_indices[i][0] = num_submodel_primary + i;
125     nonlinear_resp_mapping[num_recast_primary + i].resize(1);
126     nonlinear_resp_mapping[num_recast_primary + i][0] = false;
127   }
128 
129   // callbacks for RecastModel transformations: default maps for all but primary
130   void (*variables_map) (const Variables&, Variables&) =
131     (numHyperparams > 0) ? vars_mapping : NULL;
132   void (*set_map)  (const Variables&, const ActiveSet&, ActiveSet&) =
133     (numHyperparams > 0) ? set_mapping : NULL;
134   void (*primary_resp_map) (const Variables&, const Variables&, const Response&,
135 			    Response&) = primary_resp_differencer;
136   void (*secondary_resp_map) (const Variables&, const Variables&,
137 			      const Response&, Response&) = NULL;
138   RecastModel::
139     init_maps(vars_map_indices, nonlinear_vars_mapping, variables_map, set_map,
140 	      primary_resp_map_indices, secondary_resp_map_indices,
141 	      nonlinear_resp_mapping, primary_resp_map, secondary_resp_map);
142 
143 
144   // ---
145   // Expand currentVariables values/labels to account for hyper-parameters
146   // ---
147   init_continuous_vars();
148 
149 
150   // ---
151   // Expand any submodel Response data to the expanded residual size
152   // ---
153 
154   // NOTE: RecastModel pulls ScalingOpts from subModel
155   //
156   //  * CV scales don't change in this recasting; base RecastModel captures them
157   //    BMA TODO: What if there are hyper-parameters active?
158   //  * Overrides are not needed for nonlinear or linear constraints;
159   //    they aren't affected by data transforms
160 
161   // NOTE: The following expansions are conservative.  Could be skipped when
162   // only scalar data present and no replicates.
163   //
164   // For all primary should be able to just leave as 1 or num_elts
165 
166   // Preserve weights through data transformations
167   // Weights are always by group, expanded in DakotaModel; just need to replicate
168   expand_primary_array(sub_model.primary_response_fn_weights().length(),
169 			sub_model.primary_response_fn_weights(),
170 			num_recast_primary, primaryRespFnWts);
171 
172   // TODO: Sense is 1 or group, and NOT currently properly expanded for fields in DakotaModel
173   // Preserve sense through data transformations
174   expand_primary_array(sub_model.primary_response_fn_sense().size(),
175 		       sub_model.primary_response_fn_sense(),
176 		       num_recast_primary, primaryRespFnSense);
177 
178   // Adjust each scaling type to right size, leaving as length 1 if needed
179   expand_primary_array(sub_model.scaling_options().priScaleTypes.size(),
180 		       sub_model.scaling_options().priScaleTypes,
181 		       num_recast_primary, scalingOpts.priScaleTypes);
182   expand_primary_array(sub_model.scaling_options().priScales.length(),
183 		       sub_model.scaling_options().priScales,
184 		       num_recast_primary, scalingOpts.priScales);
185 
186   // For this derivation of RecastModel, all resizing can occur at construct
187   // time --> Variables/Response are up to date for estimate_message_lengths()
188   // within Model::init_communicators().
189 }
190 
191 
~DataTransformModel()192 DataTransformModel::~DataTransformModel()
193 { /* empty dtor */}
194 
195 
196 void DataTransformModel::
data_transform_response(const Variables & sub_model_vars,const Response & sub_model_resp,Response & residual_resp)197 data_transform_response(const Variables& sub_model_vars,
198                         const Response& sub_model_resp,
199                         Response& residual_resp)
200 {
201   // A DataTransform doesn't map variables, but we map them here to
202   // avoid introducing a latent bug
203   Variables recast_vars(current_variables().copy());
204   inverse_transform_variables(sub_model_vars, recast_vars);
205   transform_response(recast_vars, sub_model_vars, sub_model_resp, residual_resp);
206 }
207 
208 
data_resize()209 void DataTransformModel::data_resize()
210 {
211   // Actions from ctor chain to check:
212   // RecastModel ctor
213   // nonlinear_resp_mapping
214   // update primary resp mapping
215   // update secondary resp mapping, nonlinear resp mapping
216 
217   // RecastModel init_maps
218 
219   // expand arrays
220   if (numHyperparams > 0 || obsErrorMultiplierMode > CALIBRATE_NONE) {
221     // TODO: We could support update without size change, or even with
222     // size change in the case of only one multiplier.  Or later could
223     // allow updates including the whole parameter domain change.
224     Cerr << "\nError (DataTransformModel): data updates not supported when "
225 	 << "calibrating\nhyper-parameters.";
226     abort_handler(-1);
227   }
228 
229   // there is no change in variables or derivatives for now
230   reshape_response(expData.num_total_exppoints(),
231 		   subModel.num_secondary_fns());
232 
233 }
234 
235 
236 
237 /** Incorporate the hyper parameters into Variables, assuming they are at the
238     end of the active continuous variables.  For example, append them to
239     continuous design or continuous aleatory uncertain. */
240 SizetArray DataTransformModel::
variables_expand(const Model & sub_model,size_t num_hyper)241 variables_expand(const Model& sub_model, size_t num_hyper)
242 {
243   SizetArray vc_totals;  // default is no size change
244   if (num_hyper > 0) {
245     const SharedVariablesData& svd = sub_model.current_variables().shared_data();
246     vc_totals = svd.components_totals();
247     vc_totals[get_hyperparam_vc_index(sub_model)] += num_hyper;
248   }
249   return vc_totals;
250 }
251 
252 
get_hyperparam_vc_index(const Model & sub_model)253 int DataTransformModel::get_hyperparam_vc_index(const Model& sub_model)
254 {
255   int vc_index = TOTAL_CDV;
256 
257   const SharedVariablesData& svd = sub_model.current_variables().shared_data();
258   const SizetArray& vc_totals = svd.components_totals();
259   short active_view = sub_model.current_variables().view().first;
260   switch (active_view) {
261 
262   case MIXED_DESIGN: case RELAXED_DESIGN:
263     // append to end of continuous design
264     vc_index = TOTAL_CDV;
265 
266   case MIXED_ALEATORY_UNCERTAIN: case RELAXED_ALEATORY_UNCERTAIN:
267     // append to end of continuous aleatory
268     vc_index = TOTAL_CAUV;
269     break;
270 
271   case MIXED_UNCERTAIN: case RELAXED_UNCERTAIN:
272   case MIXED_EPISTEMIC_UNCERTAIN: case RELAXED_EPISTEMIC_UNCERTAIN:
273     // append to end of continuous epistemic (note there may not actually be
274     // any epistemic variables in the *_UNCERTAIN cases)
275     vc_index = TOTAL_CEUV;
276     break;
277 
278   case MIXED_ALL: case RELAXED_ALL: case MIXED_STATE: case RELAXED_STATE:
279     // append to end of continuous state
280     vc_index = TOTAL_CSV;
281     break;
282 
283   default:
284     Cerr << "\nError: invalid active variables view " << active_view
285 	 << " in DataTransformModel.\n";
286     abort_handler(-1);
287     break;
288 
289   }
290 
291   return vc_index;
292 }
293 
294 
295 short DataTransformModel::
response_order(const Model & sub_model,short recast_resp_order)296 response_order(const Model& sub_model, short recast_resp_order)
297 {
298   const Response& curr_resp = sub_model.current_response();
299 
300   // recast resp order at least as much as original response
301   if (!curr_resp.function_gradients().empty()) recast_resp_order |= 2;
302   if (!curr_resp.function_hessians().empty())  recast_resp_order |= 4;
303 
304   return recast_resp_order;
305 }
306 
307 void DataTransformModel::
gen_primary_resp_map(const SharedResponseData & srd,Sizet2DArray & primary_resp_map_indices,BoolDequeArray & nonlinear_resp_map) const308 gen_primary_resp_map(const SharedResponseData& srd,
309 		     Sizet2DArray& primary_resp_map_indices,
310 		     BoolDequeArray& nonlinear_resp_map) const
311 {
312   size_t num_scalar = srd.num_scalar_primary(),
313     num_field_groups = srd.num_field_response_groups();
314   const IntVector& sim_field_lens = srd.field_lengths();
315   size_t num_experiments = expData.num_experiments(), calib_term_ind = 0;
316 
317   for (size_t exp_ind = 0; exp_ind < num_experiments; ++exp_ind) {
318     // field lengths can be different per experiment
319     const IntVector& exp_field_lens = expData.field_lengths(exp_ind);
320     for (size_t scalar_ind = 0; scalar_ind < num_scalar; ++scalar_ind) {
321       // simulation scalars inform calibration terms 1 to 1
322       // (no correlation or interpolation allowed)
323       primary_resp_map_indices[calib_term_ind].resize(1);
324       primary_resp_map_indices[calib_term_ind][0] = scalar_ind; //=calib_term_ind
325       nonlinear_resp_map[calib_term_ind].resize(1);
326       nonlinear_resp_map[calib_term_ind][0] = false;
327       ++calib_term_ind;
328     }
329     // base index for simulation in current field group
330     size_t sim_ind_offset = num_scalar;
331     for (size_t fg_ind = 0; fg_ind < num_field_groups; ++fg_ind) {
332       // each field calibration term depends on all simulation field
333       // entries for this field, due to correlation or interpolation
334       // if no matrix correlation, no interpolation, could skip
335       for (size_t z=0; z<exp_field_lens[fg_ind]; ++z) {
336 	primary_resp_map_indices[calib_term_ind].resize(sim_field_lens[fg_ind]);
337 	nonlinear_resp_map[calib_term_ind].resize(sim_field_lens[fg_ind]);
338 	// this residual depends on all other simulation data for this field
339 	for (size_t sim_ind = 0; sim_ind<sim_field_lens[fg_ind]; ++sim_ind) {
340 	  primary_resp_map_indices[calib_term_ind][sim_ind] =
341 	    sim_ind_offset + sim_ind;
342 	  nonlinear_resp_map[calib_term_ind][sim_ind] = false;
343 	}
344 	++calib_term_ind;
345       }
346       sim_ind_offset += sim_field_lens[fg_ind];
347     }
348   }
349 }
350 
351 
352 /** Blocking evaluation over all experiment configurations to compute
353     a single set of expanded residuals.  If the subModel supports
354     asynchronous evaluate_nowait(), do the configuration evals
355     concurrently and then synchronize. */
derived_evaluate(const ActiveSet & set)356 void DataTransformModel::derived_evaluate(const ActiveSet& set)
357 {
358   // If no configuration variables, use base class implementation
359   if (expData.config_vars().empty())
360     RecastModel::derived_evaluate(set);
361   else {
362     ++recastModelEvalCntr;
363 
364     // transform from recast (Iterator) to sub-model (user) variables;
365     // NOTE: these are the same for all configurations
366     transform_variables(currentVariables, subModel.current_variables());
367 
368     // the incoming set is for the recast problem, which must be converted
369     // back to the underlying response set for evaluation by the subModel.
370     ActiveSet sub_model_set;
371     transform_set(currentVariables, set, sub_model_set);
372     // update currentResponse early as it's used in form_residuals
373     currentResponse.active_set(set);
374 
375     if (outputLevel >= VERBOSE_OUTPUT) {
376       Cout << "\n------------------------------------";
377       Cout << "\nEvaluating model for each experiment";
378       Cout << "\n------------------------------------" << std::endl;
379     }
380 
381     size_t num_exp = expData.num_experiments();
382     for (size_t i=0; i<num_exp; ++i) {
383       // augment the active variables with the configuration variables
384       Model::inactive_variables(expData.config_vars()[i], subModel);
385 
386       if (subModel.asynch_flag()) {
387         subModel.evaluate_nowait(sub_model_set);
388         // be able to map the subModel's evalID back to the right
389         // recastModel eval and omit evals we didn't schedule
390         // Don't need to cache ActiveSet or Variables
391         recastIdMap[subModel.evaluation_id()] = recastModelEvalCntr;
392       }
393       else {
394         // No need to cache when the subModel is synchronous; populate
395         // a subset of residuals for each subModel eval
396         subModel.evaluate(sub_model_set);
397         // recast the subModel response ("user space") into the currentResponse
398         // ("iterator space"); populate one experiment's residuals
399         expData.form_residuals(subModel.current_response(), i, currentResponse);
400       }
401     }
402 
403     if (subModel.asynch_flag()) {
404       // Synchronize and map all configurations to the single eval's residuals
405       // BMA TODO ask MSE: Will these always be in the right order (seems so);
406       // it's important to map the responses in the right order (not
407       // checking yet)
408 
409       // In this case we don't need a mapping to a recast ID, just an
410       // IntResponseMap with the subset of evals we own; filter in-place
411       const IntResponseMap& submodel_resp = filter_submodel_responses();
412       transform_response_map(submodel_resp, currentVariables, currentResponse);
413     }
414     else {
415       // BMA TODO: doesn't need submodel vars...
416       scale_response(subModel.current_variables(), currentVariables,
417                      currentResponse);
418     }
419 
420     print_residual_response(currentResponse);
421 
422     // BMA TODO:
423     // We know that DataTransformModel didn't register a secondary
424     // transformation, but what if the submodel constraints differ per
425     // configuration?  Need to aggregate/expand the constraints!
426     //RecastModel::transform_secondary_response();
427   }
428 }
429 
430 
431 /** Non-blocking evaluation (scheduling) over all experiment
432     configurations.  Assumes that if this model supports nowait, its
433     subModel does too and schedules them all. */
derived_evaluate_nowait(const ActiveSet & set)434 void DataTransformModel::derived_evaluate_nowait(const ActiveSet& set)
435 {
436   // BMA ask MSE: Is it possible RecastModel is asynch, but not subModel?
437 
438   // If no configuration variables, use base class implementation
439   if (expData.config_vars().empty())
440     RecastModel::derived_evaluate_nowait(set);
441   else {
442     ++recastModelEvalCntr;
443 
444     // transform from recast (Iterator) to sub-model (user) variables
445     // NOTE: these are the same for all configurations
446     transform_variables(currentVariables, subModel.current_variables());
447 
448     // the incoming set is for the recast problem, which must be converted
449     // back to the underlying response set for evaluation by the subModel.
450     ActiveSet sub_model_set;
451     transform_set(currentVariables, set, sub_model_set);
452 
453     if (outputLevel >= VERBOSE_OUTPUT) {
454       Cout << "\n------------------------------------";
455       Cout << "\nEvaluating model for each experiment";
456       Cout << "\n------------------------------------" << std::endl;
457     }
458 
459     size_t num_exp = expData.num_experiments();
460     for (size_t i=0; i<num_exp; ++i) {
461       // augment the active variables with the configuration variables
462       Model::inactive_variables(expData.config_vars()[i], subModel);
463 
464       subModel.evaluate_nowait(sub_model_set);
465 
466       // be able to map the subModel's evalID back to the right
467       // recastModel eval
468       recastIdMap[subModel.evaluation_id()] = recastModelEvalCntr;
469     }
470 
471     // bookkeep variables for use in primaryRespMapping/secondaryRespMapping
472     //if (primaryRespMapping || secondaryRespMapping) {
473     recastSetMap[recastModelEvalCntr]  = set;
474     recastVarsMap[recastModelEvalCntr] = currentVariables.copy();
475     // This RecastModel doens't map variables in a way that needs these
476     // if (variablesMapping)
477     // 	subModelVarsMap[recastModelEvalCntr] =
478     // 	  subModel.current_variables().copy();
479     //}
480   }
481 }
482 
483 
484 /** Collect all the subModel evals and build the residual sets for all
485     evaluations.  Like rekey functions in DakotaModel, but many
486     sub-model to one recast-model.  For the blocking synchronize case,
487     we force the subModel to synch and have all needed data. */
derived_synchronize()488 const IntResponseMap& DataTransformModel::derived_synchronize()
489 {
490   if (expData.config_vars().empty())
491     return RecastModel::derived_synchronize();
492   else {
493     // We don't even really need the recast ID lookup nor the cached
494     // data structure, but want to be tolerant of evals that this
495     // Model didn't schedule (and it simplifies the indexing logic).
496     bool deep_copy = false;
497     cache_submodel_responses(subModel.synchronize(), deep_copy);
498 
499     // populate recastResponseMap with all evals
500     bool collect_all = true;
501     collect_residuals(collect_all);
502   }
503 
504   return recastResponseMap;
505 }
506 
507 
508 /** Collect any completed subModel evals and build the residual sets
509     for any fully completed evaluations.  Like rekey functions in
510     DakotaModel, but many sub-model to one recast-model.  We do not
511     force the subModel to synch. */
derived_synchronize_nowait()512 const IntResponseMap& DataTransformModel::derived_synchronize_nowait()
513 {
514   if (expData.config_vars().empty())
515     return RecastModel::derived_synchronize_nowait();
516   else {
517     // need deep copy in case all the configurations aren't yet complete
518     bool deep_copy = true;
519     cache_submodel_responses(subModel.synchronize_nowait(), deep_copy);
520 
521     // populate recastResponseMap with any fully completed evals
522     bool collect_all = false;
523     collect_residuals(collect_all);
524   }
525 
526   return recastResponseMap;
527 }
528 
529 
vars_mapping(const Variables & recast_vars,Variables & submodel_vars)530 void DataTransformModel::vars_mapping(const Variables& recast_vars,
531 				      Variables& submodel_vars)
532 {
533   // Forward the calibration parameters, omitting the hyper-parameters, which
534   // are assumed to trail the active continuous variables.
535   RealVector sm_cv = submodel_vars.continuous_variables_view();
536   copy_data_partial(recast_vars.continuous_variables(), 0,
537 		    (int)submodel_vars.cv(), sm_cv);
538 }
539 
540 
541 /** RecastModel sets up a default set mapping before calling this
542     update, so focus on updating the derivative variables vector */
set_mapping(const Variables & recast_vars,const ActiveSet & recast_set,ActiveSet & sub_model_set)543 void DataTransformModel::set_mapping(const Variables& recast_vars,
544  				     const ActiveSet& recast_set,
545  				     ActiveSet& sub_model_set)
546 {
547   // This transformation should forward DVV requests for derivatives
548   // w.r.t. calibration parameters and discard requests for
549   // derivatives w.r.t. hyper-parameters.
550 
551   // The sub-model should be working with variable IDs from 1 to
552   // number of active continuous variables
553   SizetArray sub_model_dvv;
554   const SizetArray& recast_dvv = recast_set.derivative_vector();
555   size_t max_sm_id = dtModelInstance->subordinate_model().cv();
556   for (size_t i=0; i<recast_dvv.size(); ++i)
557     if (1 <= recast_dvv[i] && recast_dvv[i] <= max_sm_id)
558       sub_model_dvv.push_back(recast_dvv[i]);
559   sub_model_set.derivative_vector(sub_model_dvv);
560 
561   // When calibrating hyper-parameters in a MAP solve, requests for
562   // gradients and Hessians require lower-order data to be present.  This
563   // could be relaxed depending on which derivative vars are requested.
564   if (dtModelInstance->numHyperparams > 0) {
565     ShortArray sub_model_asv = sub_model_set.request_vector();
566     for (size_t i=0; i<sub_model_asv.size(); ++i) {
567       if (sub_model_asv[i] & 4)
568 	sub_model_asv[i] |= 2;
569       if (sub_model_asv[i] & 2)
570 	sub_model_asv[i] |= 1;
571     }
572     sub_model_set.request_vector(sub_model_asv);
573   }
574 }
575 
576 
577 void DataTransformModel::
primary_resp_differencer(const Variables & submodel_vars,const Variables & recast_vars,const Response & submodel_response,Response & recast_response)578 primary_resp_differencer(const Variables& submodel_vars,
579 			 const Variables& recast_vars,
580 			 const Response& submodel_response,
581 			 Response& recast_response)
582 {
583   // BMA REVIEW: data differencing doesn't affect gradients and
584   // Hessians, as long as they use the updated residual in their
585   // computation.  They probably don't!
586 
587   if (dtModelInstance->outputLevel >= VERBOSE_OUTPUT) {
588     Cout << "\n-----------------------------------------------------------";
589     Cout << "\nPost-processing Function Evaluation: Data Transformation";
590     Cout << "\n-----------------------------------------------------------"
591 	 << std::endl;
592   }
593 
594   // form residuals (and gradients/Hessians) from the simulation
595   // response this call has to be careful not to resize gradients and
596   // Hessians in a way that tramples hyper-parameters: only update
597   // submodel cv entries, leaving objects sized
598   dtModelInstance->expData.form_residuals(submodel_response, recast_response);
599 
600   // scale by covariance, including hyper-parameter multipliers
601   dtModelInstance->scale_response(submodel_vars, recast_vars, recast_response);
602 
603   if (dtModelInstance->outputLevel >= VERBOSE_OUTPUT &&
604       dtModelInstance->subordinate_model().num_primary_fns() > 0) {
605     Cout << "Calibration data transformation; residuals:\n";
606     write_data(Cout, recast_response.function_values(),
607 	       recast_response.function_labels());
608     Cout << std::endl;
609   }
610   if (dtModelInstance->outputLevel >= DEBUG_OUTPUT &&
611       dtModelInstance->subordinate_model().num_primary_fns() > 0) {
612     Cout << "Calibration data transformation; full response:\n"
613 	 << recast_response << std::endl;
614   }
615 
616 }
617 
618 
619 /** (We don't quite want the rekey behavior since multiple subModel
620     evals map to one recast eval.) */
filter_submodel_responses()621 const IntResponseMap& DataTransformModel::filter_submodel_responses()
622 {
623   const IntResponseMap& sm_resp_map = subModel.synchronize();
624   // Not using BOOST_FOREACH due to potential for iterator invalidation in
625   // erase in cache_unmatched_response (shouldn't be a concern with map?)
626   IntRespMCIter sm_r_it = sm_resp_map.begin();
627   IntRespMCIter sm_r_end = sm_resp_map.end();
628   while (sm_r_it != sm_r_end) {
629     int sm_id = sm_r_it->first;
630     IntIntMIter id_it = recastIdMap.find(sm_id);
631     if (id_it != recastIdMap.end()) {
632       // no need to cache, just leave in the subModel's IntResponseMap
633       recastIdMap.erase(sm_id);
634       ++sm_r_it;
635     }
636     else {
637       ++sm_r_it; // prior to invalidation from erase()
638       subModel.cache_unmatched_response(sm_id);
639     }
640   }
641   return sm_resp_map;
642 }
643 
644 
645 void DataTransformModel::
cache_submodel_responses(const IntResponseMap & sm_resp_map,bool deep_copy)646 cache_submodel_responses(const IntResponseMap& sm_resp_map, bool deep_copy)
647 {
648   // Not using BOOST_FOREACH due to potential for iterator invalidation in
649   // erase in cache_unmatched_response (shouldn't be a concern with map?)
650   IntRespMCIter sm_r_it = sm_resp_map.begin();
651   IntRespMCIter sm_r_end = sm_resp_map.end();
652   while (sm_r_it != sm_r_end) {
653     int sm_id = sm_r_it->first;
654     IntIntMIter id_it = recastIdMap.find(sm_id);
655     if (id_it != recastIdMap.end()) {
656       int recast_id = id_it->second;
657 
658       // this is our eval, cache it as <recast_id, <sm_id, Response> >
659       if (cachedResp.find(recast_id) == cachedResp.end()) {
660         // insert a new recast_id instance
661         cachedResp[recast_id] = IntResponseMap();
662         cachedResp[recast_id][sm_id] = deep_copy ? sm_r_it->second.copy() :
663           sm_r_it->second;
664       }
665       else
666         cachedResp[recast_id][sm_id] = deep_copy ? sm_r_it->second.copy() :
667           sm_r_it->second;
668       recastIdMap.erase(sm_id);
669       ++sm_r_it;
670     }
671     else {
672       ++sm_r_it;
673       subModel.cache_unmatched_response(sm_id);
674     }
675   }
676 }
677 
678 
collect_residuals(bool collect_all)679 void DataTransformModel::collect_residuals(bool collect_all)
680 {
681   recastResponseMap.clear();
682 
683   //BOOST_FOREACH(IntIntResponseMapMap::value_type& cr_pair, cachedResp)
684   IntIntResponseMapMap::iterator cr_pair = cachedResp.begin();
685   while(cr_pair != cachedResp.end()) {
686     int recast_id = cr_pair->first;  // (.second is a subModel IntResponseMap)
687     size_t num_exp = expData.num_experiments();
688 
689     // the blocking synch case requires all data present
690     if (collect_all && cr_pair->second.size() != num_exp) {
691       Cerr << "\nError (DataTransformModel): Sub-model returned "
692            << cr_pair->second.size() << "evaluations,\n  but have " << num_exp
693            << " experiment configurations.\n";
694       abort_handler(-1);
695     }
696 
697     // populate recastResponseMap with any recast evals that have all
698     // their configs complete (only complete/clear those with finished
699     // experiment configs)
700     if (cr_pair->second.size() == num_exp) {
701 
702       IntASMIter s_it = recastSetMap.find(recast_id);
703       IntVarsMIter v_it = recastVarsMap.find(recast_id);
704 
705       recastResponseMap[recast_id] = currentResponse.copy();
706       recastResponseMap[recast_id].active_set(s_it->second);
707 
708       transform_response_map(cr_pair->second, v_it->second,
709                              recastResponseMap[recast_id]);
710 
711       // cleanup (could do clear() at end)
712       recastVarsMap.erase(v_it);
713       recastSetMap.erase(s_it);
714       // BMA TODO:
715       //RecastModel::transform_secondary_response();
716       // BMA TODO: consider iterator here instead of value?
717       cr_pair++;
718       cachedResp.erase(recast_id);
719       print_residual_response(recastResponseMap[recast_id]);
720     } else {
721       cr_pair++;
722     }
723   }
724 }
725 
726 
727 /** This transformation assumes the residuals are in submodel eval_id
728     order. */
729 void DataTransformModel::
transform_response_map(const IntResponseMap & submodel_resp,const Variables & recast_vars,Response & residual_resp)730 transform_response_map(const IntResponseMap& submodel_resp,
731                        const Variables& recast_vars,
732                        Response& residual_resp)
733 {
734   size_t num_exp = expData.num_experiments();
735   if (submodel_resp.size() != num_exp) {
736     // unsupported case: could (shouldn't) happen in complex MF workflows
737     Cerr << "\nError (DataTransformModel): sub model evals wrong size.\n";
738     abort_handler(-1);
739   }
740   IntRespMCIter sm_eval_it = submodel_resp.begin();
741   for (size_t i=0; i<num_exp; ++i, ++sm_eval_it)
742     expData.form_residuals(sm_eval_it->second, i, residual_resp);
743 
744   // scale by covariance, including hyper-parameter multipliers
745   // BMA TODO: doesn't need submodel vars...
746   scale_response(subModel.current_variables(), recast_vars, residual_resp);
747 }
748 
749 void DataTransformModel::
scale_response(const Variables & submodel_vars,const Variables & recast_vars,Response & recast_response)750 scale_response(const Variables& submodel_vars,
751 	       const Variables& recast_vars,
752 	       Response& recast_response)
753 {
754   // scale by (error covariance)^{-1/2}
755   if (expData.variance_active())
756     expData.scale_residuals(recast_response);
757 
758   // TODO: may need to scale by hyperparameters in Covariance as well
759   if (obsErrorMultiplierMode > CALIBRATE_NONE) {
760     // r <- r ./ mult, where mult might be per-block
761     // scale by mult, whether 1, per-experiment, per-response, or both
762     // for now, the multiplier calibration mode is a method-related
763     // parameter; could instead have a by-index interface here...
764 
765     // For now, assume only continuous variables, with hyper at end
766 
767     // extract hyperparams; now both raw and sub vars have size total params
768     // hyper are the last numHyperParams
769     size_t num_calib_params = submodel_vars.cv();
770     RealVector hyper_params;
771     hyper_params.sizeUninitialized(numHyperparams);
772     copy_data_partial(recast_vars.continuous_variables(), num_calib_params,
773 		      numHyperparams, hyper_params);
774 
775     // Apply hyperparameter multipliers to all fn, grad, Hess,
776     // including derivative contributions from hyperparmeters
777     // BMA TODO: Model after the above scale_residuals, operating block-wise
778     // and accounting for ASV
779     expData.
780       scale_residuals(hyper_params, obsErrorMultiplierMode, num_calib_params,
781 		      recast_response);
782   }
783 }
784 
785 
786 /** Pull up the continuous variable values and labels into the
787     RecastModel, inserting the hyper-parameter values/labels  */
init_continuous_vars()788 void DataTransformModel::init_continuous_vars()
789 {
790   const SharedVariablesData& svd = subModel.current_variables().shared_data();
791   const SizetArray& sm_vc_totals = svd.components_totals();
792   const RealVector& sm_acv = subModel.all_continuous_variables();
793   StringMultiArrayConstView sm_acvl = subModel.all_continuous_variable_labels();
794   const RealVector & sm_aclb = subModel.all_continuous_lower_bounds();
795   const RealVector & sm_acub = subModel.all_continuous_upper_bounds();
796 
797   int continuous_vc_inds[4] = {TOTAL_CDV, TOTAL_CAUV, TOTAL_CEUV, TOTAL_CSV};
798   int hyperparam_vc_ind = get_hyperparam_vc_index(subModel);
799 
800   size_t sm_offset = 0;
801   size_t dtm_offset = 0;
802 
803   for(const int& vci : continuous_vc_inds) {
804 
805     size_t num_cvars = sm_vc_totals[vci];
806     for (size_t i=0; i<num_cvars; ++i) {
807       all_continuous_variable(sm_acv[sm_offset], dtm_offset);
808       all_continuous_variable_label(sm_acvl[sm_offset], dtm_offset);
809       all_continuous_lower_bound(sm_aclb[sm_offset], dtm_offset);
810       all_continuous_upper_bound(sm_acub[sm_offset], dtm_offset);
811       ++sm_offset;
812       ++dtm_offset;
813     }
814     if (vci == hyperparam_vc_ind) {
815       // insert the hyper-parameter values/labels
816       const StringArray& hyper_labels =
817 	expData.hyperparam_labels(obsErrorMultiplierMode);
818       for (size_t i=0; i<numHyperparams; ++i) {
819 	all_continuous_variable(1.0, dtm_offset);
820 	all_continuous_variable_label(hyper_labels[i], dtm_offset);
821 	all_continuous_lower_bound(0.0, dtm_offset);
822 	all_continuous_upper_bound(std::numeric_limits<double>::infinity(),
823 				   dtm_offset);
824 	++dtm_offset;
825       }
826     }
827 
828   }
829 
830 }
831 
832 
833 /** Passing the inbound array size so we can use one function for
834     Teuchos and std containers (size vs. length) */
835 template<typename T>
836 void DataTransformModel::
expand_primary_array(size_t submodel_size,const T & submodel_array,size_t recast_size,T & recast_array) const837 expand_primary_array(size_t submodel_size, const T& submodel_array,
838 		     size_t recast_size, T& recast_array) const
839 {
840   // Assume that coefficients have been expanded for fields (weights, scales, sense)
841   // So each is either len1 or num_elements
842   if (submodel_size == 1)
843     // this copy may not be needed, depends on ctor behavior
844     recast_array = submodel_array;
845   else if (submodel_size > 1) {
846 
847     // TODO: convenience function to do this
848     // For num_elements case, just fill the recast_array with copies
849     size_t num_exp = expData.num_experiments();
850 
851     assert(submodel_size * num_exp == recast_size);
852 
853     recast_array.resize(recast_size);
854     size_t calib_term_ind = 0;
855     for (size_t exp_ind = 0; exp_ind < num_exp; ++exp_ind)
856       for (size_t sma_ind = 0; sma_ind < submodel_size; ++sma_ind)
857 	recast_array[calib_term_ind++] = submodel_array[sma_ind];
858   }
859   // else leave recast_array empty
860 }
861 
862 
print_residual_response(const Response & resid_resp)863 void DataTransformModel::print_residual_response(const Response& resid_resp)
864 {
865   if (outputLevel >= VERBOSE_OUTPUT) {
866     Cout << "\n-----------------------------------------------------------";
867     Cout << "\nPost-processing Function Evaluation: Data Transformation";
868     Cout << "\n-----------------------------------------------------------"
869 	 << std::endl;
870   }
871 
872   if (outputLevel >= VERBOSE_OUTPUT &&
873       subordinate_model().num_primary_fns() > 0) {
874     Cout << "Calibration data transformation; residuals:\n";
875     write_data(Cout, resid_resp.function_values(),
876 	       resid_resp.function_labels());
877     Cout << std::endl;
878   }
879   if (outputLevel >= DEBUG_OUTPUT &&
880       subordinate_model().num_primary_fns() > 0) {
881     Cout << "Calibration data transformation; full response:\n"
882 	 << resid_resp << std::endl;
883   }
884 }
885 
886 
887 void DataTransformModel::
print_best_responses(std::ostream & s,const Variables & best_submodel_vars,const Response & best_submodel_resp,size_t num_best,size_t best_ind)888 print_best_responses(std::ostream& s,
889                      const Variables& best_submodel_vars,
890                      const Response& best_submodel_resp,
891                      size_t num_best, size_t best_ind)
892 {
893   // BMA TODO: Why copying the response, why not just update dataTransformModel?
894   Response residual_resp(current_response().copy());
895   // only transform residuals, not derivatives
896   ActiveSet fn_only_as = residual_resp.active_set();
897   fn_only_as.request_values(1);
898   residual_resp.active_set(fn_only_as);
899 
900   s << "Original (as-posed) response:\n";
901 
902   // print the original userModel Responses
903   if (expData.config_vars().size() == 0) {
904     const RealVector& best_fns = best_submodel_resp.function_values();
905     Minimizer::print_model_resp(subModel.num_primary_fns(), best_fns, num_best,
906                                 best_ind, s);
907     // form residuals from model responses and apply covariance
908     short dt_verbosity = output_level();
909     output_level(SILENT_OUTPUT);
910     data_transform_response(best_submodel_vars, best_submodel_resp,
911                             residual_resp);
912     output_level(dt_verbosity);
913   }
914   else {
915     recover_submodel_responses(s, best_submodel_vars,
916                                num_best, best_ind, residual_resp);
917   }
918 
919   const RealVector& resid_fns = residual_resp.function_values();
920   // The data transformation may be weighting by variance; remind the user:
921   if (expData.variance_active())
922     s << "Variance-weighted original (as-posed) residuals:\n";
923   else
924     s << "Original (as-posed) residuals:\n";
925   Minimizer::print_residuals(num_primary_fns(), resid_fns, RealVector(),
926 			     num_best, best_ind, s);
927 
928   // must use the expanded weight set from the data difference model
929   const RealVector& lsq_weights = Model::primary_response_fn_weights();
930   Minimizer::print_residuals(num_primary_fns(), resid_fns, lsq_weights,
931                              num_best, best_ind, s);
932 }
933 
934 
935 // The core of this can be modular on the Model (and static)
936 void DataTransformModel::
recover_submodel_responses(std::ostream & s,const Variables & best_submodel_vars,size_t num_best,size_t best_ind,Response & residual_resp)937 recover_submodel_responses(std::ostream& s,
938                            const Variables& best_submodel_vars,
939                            size_t num_best, size_t best_ind,
940                            Response& residual_resp)
941 {
942   if (subModel.num_primary_fns() > 1 || expData.config_vars().size() > 1)
943     s << "<<<<< Best model responses ";
944   else
945     s << "<<<<< Best model response ";
946   if (num_best > 1) s << "(set " << best_ind+1 << ") "; s << "\n";
947 
948   // first try cache lookup
949 
950   // Have to make sure the Variables object is in the right mode for
951   // inactive operations. Make a deep copy of the SharedVariablesData
952   // to avoid corrupting any inbound Variables information... (const
953   // doesn't protect the SVD)
954   Variables lookup_vars = best_submodel_vars.copy(true);
955   lookup_vars.inactive_view(MIXED_STATE);
956 
957   String interface_id = subModel.interface_id();
958   Response lookup_resp = subModel.current_response().copy();
959   ActiveSet lookup_as = lookup_resp.active_set();
960   lookup_as.request_values(1);
961   lookup_resp.active_set(lookup_as);
962   ParamResponsePair lookup_pr(lookup_vars, interface_id, lookup_resp);
963 
964   // use model_resp to populate residuals as we go
965   Response model_resp;
966   size_t num_exp = expData.num_experiments();
967   for (size_t i=0; i<num_exp; ++i) {
968 
969     //      Model::inactive_variables(expData.config_vars()[i], subModel);
970     Model::inactive_variables(expData.config_vars()[i], subModel, lookup_vars);
971 
972     // TODO: use user-provided experiment numbers if given
973     s << "<<<<< Best configuration variables (experiment " << i+1 << ") =\n";
974     lookup_vars.write(s, INACTIVE_VARS);  // vars object writes labels
975 
976     bool lookup_failure = false;
977     // BMA: why is this necessary?  Should have a reference to same object as PRP
978     lookup_pr.variables(lookup_vars);
979     PRPCacheHIter cache_it = lookup_by_val(data_pairs, lookup_pr);
980     if (cache_it == data_pairs.get<hashed>().end()) {
981 
982       // If model is a data fit surrogate, re-evaluate it if needed.
983       // Didn't use != "hierarchical" in case other surrogate types are added.
984       if ( subModel.model_type() == "surrogate" &&
985            (strbegins(subModel.surrogate_type(), "global_") ||
986             strbegins(subModel.surrogate_type(), "local_") ||
987             strbegins(subModel.surrogate_type(), "multipoint_")) ) {
988         // TODO: Want to make this a quiet evaluation, but not easy to
989         // propagate to the interface?!?
990         //subModel.ouput_level(SILENT_OUTPUT); // and need restore
991         subModel.current_variables() = lookup_vars;
992         subModel.evaluate(lookup_resp.active_set());
993         model_resp = subModel.current_response();
994 
995         // TODO: There are other cases where re-evaluate would be
996         // safe, like a recast of a simulation model that has caching
997         // enabled, but don't treat that for now.
998       }
999       else {
1000         // BMA TODO: Consider NaN so downstream output isn't misleading
1001         lookup_failure = true;
1002         s << "Could not retrieve best model responses (experiment " << i+1
1003           << ")";
1004       }
1005     }
1006     else {
1007       model_resp = cache_it->response();
1008     }
1009 
1010     if (!lookup_failure) {
1011       expData.form_residuals(model_resp, i, residual_resp);
1012 
1013       // By including labels, this deviates from other summary function output
1014       if (subModel.num_primary_fns() > 1)
1015         s << "<<<<< Best model responses (experiment " << i+1 << ") =\n";
1016       else
1017         s << "<<<<< Best model response (experiment " << i+1 << ") =\n";
1018       write_data_partial(s, (size_t)0, subModel.num_primary_fns(),
1019                          model_resp.function_values(),
1020                          model_resp.function_labels());
1021     }
1022   }
1023 
1024   // TODO: this won't scale properly if hyper-parameters are
1025   // calibrated; would need the error multipliers, which are in the
1026   // recast variables space
1027   scale_response(subModel.current_variables(), currentVariables, residual_resp);
1028 }
1029 
1030 void DataTransformModel::
archive_best_residuals(const ResultsManager & results_db,const StrStrSizet & iterator_id,const int num_terms,const RealVector & best_terms,const Real wssr,const int num_points,const int point_index)1031 archive_best_residuals(const ResultsManager &results_db,
1032                               const StrStrSizet &iterator_id,
1033                               const int num_terms,
1034                               const RealVector &best_terms,
1035                               const Real wssr, const int num_points,
1036                               const int point_index) {
1037   if(!results_db.active()) return;
1038 
1039   StringArray residuals_location;
1040   StringArray norm_location;
1041   if(num_points > 1) {
1042     String set_string = String("set:") + std::to_string(point_index+1);
1043     residuals_location.push_back(set_string);
1044     norm_location.push_back(set_string);
1045   }
1046   residuals_location.push_back("best_residuals");
1047   norm_location.push_back("best_norm");
1048   Teuchos::SerialDenseVector<int, Real> residuals(Teuchos::View,
1049                               const_cast<Real*>(&best_terms[0]),
1050                               num_terms);
1051   results_db.insert(iterator_id, residuals_location, residuals);
1052   results_db.insert(iterator_id, norm_location, wssr);
1053 }
1054 
archive_best_original(const ResultsManager & results_db,const StrStrSizet & iterator_id,const RealVector & function_values,const int & exp_index,const int & num_best,const int & best_index)1055 void DataTransformModel::archive_best_original(const ResultsManager &results_db,
1056                                                const StrStrSizet &iterator_id,
1057                                                const RealVector &function_values,
1058                                                const int &exp_index,
1059                                                const int &num_best,
1060                                                const int &best_index) {
1061   if(!results_db.active())  return;
1062 
1063   DimScaleMap scales;
1064   scales.emplace(0, StringScale("responses", subModel.response_labels(),
1065                                 ScaleScope::SHARED)
1066                 );
1067 
1068   StringArray location;
1069   if(num_best > 1)
1070     location.push_back(String("set:") + std::to_string(best_index+1));
1071   location.push_back("best_model_responses");
1072   if(expData.num_config_vars()) {
1073     location.push_back(String("experiment:") + std::to_string(exp_index+1));
1074     location.push_back("responses");
1075   }
1076 
1077   results_db.insert(iterator_id, location, function_values, scales);
1078 }
1079 
1080 
1081 void DataTransformModel::
archive_submodel_responses(const ResultsManager & results_db,const StrStrSizet & iterator_id,const Variables & best_submodel_vars,size_t num_best,size_t best_ind,Response & residual_resp)1082 archive_submodel_responses(const ResultsManager &results_db,
1083                            const StrStrSizet &iterator_id,
1084                            const Variables& best_submodel_vars,
1085                            size_t num_best, size_t best_ind,
1086                            Response& residual_resp)
1087 {
1088   // first try cache lookup
1089 
1090   // Have to make sure the Variables object is in the right mode for
1091   // inactive operations. Make a deep copy of the SharedVariablesData
1092   // to avoid corrupting any inbound Variables information... (const
1093   // doesn't protect the SVD)
1094 
1095 
1096   Variables lookup_vars = best_submodel_vars.copy(true);
1097   lookup_vars.inactive_view(MIXED_STATE);
1098 
1099   String interface_id = subModel.interface_id();
1100   Response lookup_resp = subModel.current_response().copy();
1101   ActiveSet lookup_as = lookup_resp.active_set();
1102   lookup_as.request_values(1);
1103   lookup_resp.active_set(lookup_as);
1104   ParamResponsePair lookup_pr(lookup_vars, interface_id, lookup_resp);
1105 
1106   // use model_resp to populate residuals as we go
1107   Response model_resp;
1108   size_t num_exp = expData.num_experiments();
1109   for (size_t i=0; i<num_exp; ++i) {
1110 
1111     //      Model::inactive_variables(expData.config_vars()[i], subModel);
1112     Model::inactive_variables(expData.config_vars()[i], subModel, lookup_vars);
1113 
1114     bool lookup_failure = false;
1115     // BMA: why is this necessary?  Should have a reference to same object as PRP
1116     lookup_pr.variables(lookup_vars);
1117     PRPCacheHIter cache_it = lookup_by_val(data_pairs, lookup_pr);
1118     if (cache_it == data_pairs.get<hashed>().end()) {
1119 
1120       // If model is a data fit surrogate, re-evaluate it if needed.
1121       // Didn't use != "hierarchical" in case other surrogate types are added.
1122       if ( subModel.model_type() == "surrogate" &&
1123            (strbegins(subModel.surrogate_type(), "global_") ||
1124             strbegins(subModel.surrogate_type(), "local_") ||
1125             strbegins(subModel.surrogate_type(), "multipoint_")) ) {
1126         // TODO: Want to make this a quiet evaluation, but not easy to
1127         // propagate to the interface?!?
1128         //subModel.ouput_level(SILENT_OUTPUT); // and need restore
1129         subModel.current_variables() = lookup_vars;
1130         subModel.evaluate(lookup_resp.active_set());
1131         model_resp = subModel.current_response();
1132 
1133         // TODO: There are other cases where re-evaluate would be
1134         // safe, like a recast of a simulation model that has caching
1135         // enabled, but don't treat that for now.
1136       }
1137     }
1138     else {
1139       model_resp = cache_it->response();
1140     }
1141 
1142     if (!lookup_failure) {
1143       expData.form_residuals(model_resp, i, residual_resp);
1144       archive_best_original(results_db, iterator_id,
1145                              model_resp.function_values(),
1146                              i, num_best, best_ind);
1147       if(expData.num_config_vars() != 0)
1148         archive_best_config_variables(results_db, iterator_id,
1149                                      lookup_vars, i,
1150                                      num_best, best_ind);
1151 
1152     }
1153   }
1154 
1155   // TODO: this won't scale properly if hyper-parameters are
1156   // calibrated; would need the error multipliers, which are in the
1157   // recast variables space
1158   scale_response(subModel.current_variables(), currentVariables, residual_resp);
1159 }
1160 
1161 /// archive best responses
1162 void DataTransformModel::
archive_best_responses(const ResultsManager & results_db,const StrStrSizet iterator_id,const Variables & best_submodel_vars,const Response & best_submodel_resp,size_t num_best,size_t best_ind)1163 archive_best_responses(const ResultsManager &results_db,
1164                        const StrStrSizet iterator_id,
1165                        const Variables& best_submodel_vars,
1166                        const Response& best_submodel_resp,
1167                        size_t num_best, size_t best_ind) {
1168 
1169   // BMA TODO: Why copying the response, why not just update dataTransformModel?
1170   Response residual_resp(current_response().copy());
1171   // only transform residuals, not derivatives
1172   ActiveSet fn_only_as = residual_resp.active_set();
1173   fn_only_as.request_values(1);
1174   residual_resp.active_set(fn_only_as);
1175 
1176   // print the original userModel Responses
1177   if (expData.config_vars().size() == 0) {
1178     const RealVector& best_fns = best_submodel_resp.function_values();
1179     archive_best_original(results_db, iterator_id, best_fns, 0, num_best, best_ind);
1180     // form residuals from model responses and apply covariance
1181     short dt_verbosity = output_level();
1182     output_level(SILENT_OUTPUT);
1183     data_transform_response(best_submodel_vars, best_submodel_resp,
1184                             residual_resp);
1185     output_level(dt_verbosity);
1186   }
1187   else {
1188     archive_submodel_responses(results_db, iterator_id, best_submodel_vars,
1189                                num_best, best_ind, residual_resp);
1190   }
1191 
1192   const RealVector& resid_fns = residual_resp.function_values();
1193   // print_residuals will archive the residuals. for now, skip archiving the un-weighted
1194   // ones by passing in an uninitialized ResultsManager.
1195   const RealVector& lsq_weights = Model::primary_response_fn_weights();
1196   Real wssr = std::sqrt(Minimizer::sum_squared_residuals(num_primary_fns(),
1197                                                          resid_fns, lsq_weights)
1198                        );
1199   // Currently only the weighted residuals are archived. This differs from what's printed
1200   // to the console (see print_best_responses() above).
1201   archive_best_residuals(results_db, iterator_id, num_primary_fns(),
1202       resid_fns, wssr, num_best, best_ind);
1203 
1204 }
1205 
1206 /// Archive best configuration variables
1207 void DataTransformModel::
archive_best_config_variables(const ResultsManager & results_db,const StrStrSizet & iterator_id,const Variables & vars,const int & exp_index,const int & num_best,const int & best_index)1208 archive_best_config_variables(const ResultsManager &results_db,
1209                               const StrStrSizet &iterator_id,
1210                               const Variables &vars,
1211                               const int &exp_index,
1212                               const int &num_best,
1213                               const int &best_index) {
1214 
1215   if(!results_db.active())  return;
1216   // When using configuration variables, all inactive variables are
1217   // expected to be config vars.
1218 
1219   const auto & cv_labels = vars.inactive_continuous_variable_labels();
1220   const auto & div_labels = vars.inactive_discrete_int_variable_labels();
1221   const auto & dsv_labels = vars.inactive_discrete_string_variable_labels();
1222   const auto & drv_labels = vars.inactive_discrete_real_variable_labels();
1223 
1224   StringArray location;
1225   size_t d_index = 2;
1226   if(num_best > 1) {
1227     location.push_back(String("set:") + std::to_string(best_index+1));
1228     d_index = 3;
1229 
1230   }
1231   location.push_back("best_model_responses");
1232   location.push_back(String("experiment:") + std::to_string(exp_index+1));
1233   location.push_back(""); // placeholder for variable domain
1234   if(cv_labels.size()) {
1235     location[d_index] = "continuous_config_variables";
1236     DimScaleMap scales;
1237     scales.emplace(0, StringScale("variables", cv_labels));
1238     results_db.insert(iterator_id, location,
1239         vars.inactive_continuous_variables(), scales);
1240   }
1241   if(div_labels.size()) {
1242     location[d_index] = "discrete_integer_config_variables";
1243     DimScaleMap scales;
1244     scales.emplace(0, StringScale("variables", div_labels));
1245     results_db.insert(iterator_id, location,
1246         vars.inactive_discrete_int_variables(), scales);
1247   }
1248   if(dsv_labels.size()) {
1249     location[d_index] = "discrete_string_config_variables";
1250     DimScaleMap scales;
1251     scales.emplace(0, StringScale("variables", dsv_labels));
1252     results_db.insert(iterator_id, location,
1253         vars.inactive_discrete_string_variables(), scales);
1254   }
1255   if(drv_labels.size()) {
1256     location[d_index] = "discrete_real_config_variables";
1257     DimScaleMap scales;
1258     scales.emplace(0, StringScale("variables", drv_labels));
1259     results_db.insert(iterator_id, location,
1260         vars.inactive_discrete_real_variables(), scales);
1261   }
1262 }
1263 
num_config_vars() const1264 int DataTransformModel::num_config_vars() const {
1265   return expData.num_config_vars();
1266 }
1267 
1268 }  // namespace Dakota
1269