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 #include "ExperimentData.hpp"
10 #include "DataMethod.hpp"
11 #include "ProblemDescDB.hpp"
12 
13 namespace Dakota {
14 
ExperimentData()15 ExperimentData::ExperimentData():
16   calibrationDataFlag(false), numExperiments(0), numConfigVars(0),
17   covarianceDeterminant(1.0), logCovarianceDeterminant(0.0),
18   scalarDataFormat(TABULAR_EXPER_ANNOT), scalarSigmaPerRow(0),
19   readSimFieldCoords(false), interpolateFlag(false), outputLevel(NORMAL_OUTPUT)
20 {  /* empty ctor */  }
21 
22 
23 ExperimentData::
ExperimentData(const ProblemDescDB & pddb,const SharedResponseData & srd,short output_level)24 ExperimentData(const ProblemDescDB& pddb,
25                const SharedResponseData& srd, short output_level):
26   calibrationDataFlag(pddb.get_bool("responses.calibration_data")),
27   numExperiments(pddb.get_sizet("responses.num_experiments")),
28   numConfigVars(pddb.get_sizet("responses.num_config_vars")),
29   covarianceDeterminant(1.0), logCovarianceDeterminant(0.0),
30   scalarDataFilename(pddb.get_string("responses.scalar_data_filename")),
31   scalarDataFormat(pddb.get_ushort("responses.scalar_data_format")),
32   scalarSigmaPerRow(0),
33   readSimFieldCoords(pddb.get_bool("responses.read_field_coordinates")),
34   interpolateFlag(pddb.get_bool("responses.interpolate")),
35   outputLevel(output_level)
36 {
37   initialize(pddb.get_sa("responses.variance_type"), srd);
38 }
39 
40 
41 ExperimentData::
ExperimentData(size_t num_experiments,size_t num_config_vars,const boost::filesystem::path & data_prefix,const SharedResponseData & srd,const StringArray & variance_types,short output_level,std::string scalar_data_filename)42 ExperimentData(size_t num_experiments, size_t num_config_vars,
43                const boost::filesystem::path& data_prefix,
44                const SharedResponseData& srd,
45                const StringArray& variance_types,
46                short output_level,
47                std::string scalar_data_filename):
48   calibrationDataFlag(true), numExperiments(num_experiments),
49   numConfigVars(num_config_vars),
50   covarianceDeterminant(1.0), logCovarianceDeterminant(0.0),
51   dataPathPrefix(data_prefix), scalarDataFilename(scalar_data_filename),
52   scalarDataFormat(TABULAR_EXPER_ANNOT), scalarSigmaPerRow(0),
53   readSimFieldCoords(false), interpolateFlag(false), outputLevel(output_level)
54 {
55   initialize(variance_types, srd);
56 }
57 
58 ExperimentData::
ExperimentData(size_t num_experiments,const SharedResponseData & srd,const RealMatrix & config_vars,const IntResponseMap & all_responses,short output_level)59 ExperimentData(size_t num_experiments, const SharedResponseData& srd,
60                const RealMatrix& config_vars,
61                const IntResponseMap& all_responses, short output_level):
62   calibrationDataFlag(false), numExperiments(num_experiments),
63   numConfigVars(config_vars.numRows()),
64   covarianceDeterminant(1.0), logCovarianceDeterminant(0.0),
65   scalarDataFormat(TABULAR_EXPER_ANNOT), scalarSigmaPerRow(0),
66   readSimFieldCoords(false), interpolateFlag(false), outputLevel(output_level)
67 {
68   simulationSRD = srd.copy();
69   allConfigVars.resize(numExperiments);
70   for (size_t i=0; i<numExperiments; ++i) {
71     allConfigVars[i] =
72       Teuchos::getCol(Teuchos::Copy, const_cast<RealMatrix&>(config_vars),
73                       (int) i);
74     if (outputLevel >= DEBUG_OUTPUT)
75       Cout << " allConfigVars i " << allConfigVars[i] << '\n';
76   }
77   if (outputLevel >= DEBUG_OUTPUT)
78     Cout << "Number of config vars " << numConfigVars << '\n';
79 
80   // BMA TODO: This doesn't make an object of type ExperimentResponse!
81   IntRespMCIter resp_it = all_responses.begin();
82   IntRespMCIter resp_end = all_responses.end();
83   for ( ; resp_it != resp_end; ++resp_it)
84      allExperiments.push_back(resp_it->second);
85 }
86 
87 
initialize(const StringArray & variance_types,const SharedResponseData & srd)88 void ExperimentData::initialize(const StringArray& variance_types,
89 				const SharedResponseData& srd)
90 {
91   // only initialize data if needed; TODO: consider always initializing
92   if (calibrationDataFlag || !scalarDataFilename.empty()) {
93 
94     if (outputLevel > NORMAL_OUTPUT) {
95       Cout << "Constructing ExperimentData with " << numExperiments
96 	   << " experiment(s).";
97       if (!scalarDataFilename.empty())
98 	Cout << "\n  Scalar data file name: '" << scalarDataFilename << "'";
99       Cout << std::endl;
100     }
101 
102     if (interpolateFlag) {
103       // can't interpolate if there are no simulation coordinates
104       if (!readSimFieldCoords) {
105 	Cerr << "\nError: calibration data 'interpolate' option not available "
106 	     << "if simulation coordinates are not read in also. "
107              << "Please specify simulation coordinates with read_field_coordinates.\n";
108 	abort_handler(-1);
109       }
110 
111       // can't use normInf as the vector is a 1 x num_fields matrix
112       bool multiple_coords = false;
113       const IntVector coords_per_field = srd.num_coords_per_field();
114       for (size_t f_ind = 0; f_ind < coords_per_field.length(); ++f_ind)
115 	if (coords_per_field[f_ind] > 1) {
116 	  multiple_coords = true;
117 	  break;
118 	}
119       if (multiple_coords) {
120 	Cerr << "\nError: calibration data 'interpolate' option not available "
121 	     << "for fields with\n       more than 1 independent coordinate.\n";
122 	abort_handler(-1);
123       }
124     }
125 
126     // for now, copy in case any recasting between construct and read;
127     // don't want to share a rep, or do we?
128     simulationSRD = srd.copy();
129 
130     parse_sigma_types(variance_types);
131   }
132   else
133     {
134       experimentLengths.sizeUninitialized(1);
135       experimentLengths[0] = srd.num_functions();
136       expOffsets.size(1); // init to 0
137     }
138 }
139 
140 /** Validate user-provided sigma specifcation. User can specify 0, 1,
141     or num_response_groups sigmas.  If specified, sigma types must be
142     the same for all scalar responses. */
parse_sigma_types(const StringArray & sigma_types)143 void ExperimentData::parse_sigma_types(const StringArray& sigma_types)
144 {
145   // leave array empty if not needed (could have many responses and no sigmas)
146   if (sigma_types.size() == 0)
147     return;
148 
149   // valid options for sigma_type, and mapping to enum
150   std::map<String, unsigned short> sigma_map;
151   sigma_map["none"] = NO_SIGMA;
152   sigma_map["scalar"] = SCALAR_SIGMA;
153   sigma_map["diagonal"] = DIAGONAL_SIGMA;
154   sigma_map["matrix"] = MATRIX_SIGMA;
155 
156   // expand sigma if 0 or 1 given, without validation
157   size_t num_resp_groups = simulationSRD.num_response_groups();
158   size_t num_scalar = simulationSRD.num_scalar_primary();
159   varianceTypes.resize(num_resp_groups, NO_SIGMA);
160   if (sigma_types.size() == 1) {
161     // assign all sigmas to the specified one
162     if (sigma_map.find(sigma_types[0]) != sigma_map.end())
163       varianceTypes.assign(num_resp_groups, sigma_map[sigma_types[0]]);
164     else {
165       Cerr << "\nError: invalid sigma_type '" << sigma_types[0]
166 	   << "' specified." << std::endl;
167       abort_handler(PARSE_ERROR);
168     }
169   }
170   else if (sigma_types.size() == num_resp_groups) {
171     // initialize one sigma type per
172     for (size_t resp_ind = 0; resp_ind < num_resp_groups; ++resp_ind) {
173       if (sigma_map.find(sigma_types[resp_ind]) != sigma_map.end())
174 	varianceTypes[resp_ind] = sigma_map[sigma_types[resp_ind]];
175       else {
176 	Cerr << "\nError: invalid sigma_type '" << sigma_types[resp_ind]
177 	     << "' specified." << std::endl;
178 	abort_handler(PARSE_ERROR);
179       }
180     }
181   }
182   else  {
183     Cerr << "\nError: sigma_types must have length 1 or number of "
184 	 << "calibration_terms." << std::endl;
185     abort_handler(PARSE_ERROR);
186   }
187 
188   // when using simple scalar data format, must validate that all
189   // scalar are the same and valid (when using separate files, can
190   // differ)
191 
192   // scalar sigma must be 0 or scalar
193   bool scalar_data_file = !scalarDataFilename.empty();
194   for (size_t scalar_ind = 0; scalar_ind < num_scalar; ++scalar_ind) {
195     if (varianceTypes[scalar_ind] != NO_SIGMA &&
196 	varianceTypes[scalar_ind] != SCALAR_SIGMA) {
197       Cerr << "\nError: sigma_type must be 'none' or 'scalar' for scalar "
198 	   << "responses." << std::endl;
199       abort_handler(PARSE_ERROR);
200     }
201     if (scalar_data_file) {
202       if (varianceTypes[scalar_ind] != varianceTypes[0]) {
203 	Cerr << "\nError: sigma_type must be the same for all scalar responses "
204 	     << "when using scalar data file."
205 	     << std::endl;
206 	abort_handler(PARSE_ERROR);
207       }
208     }
209   }
210   // number of sigma to read from simple data file(0 or num_scalar)
211   if (scalar_data_file && varianceTypes.size() > 0 &&
212       varianceTypes[0] == SCALAR_SIGMA)
213     scalarSigmaPerRow = num_scalar;
214 
215 }
216 
217 void ExperimentData::
add_data(const RealVector & one_configvars,const Response & one_response)218 add_data(const RealVector& one_configvars, const Response& one_response)
219 {
220   // BMA TODO: This doesn't make an object of type ExperimentResponse!
221   numExperiments += 1;
222   if (outputLevel >= DEBUG_OUTPUT)
223     Cout << "numExperiments in add_data " << numExperiments << '\n';
224 
225   allConfigVars.push_back(one_configvars);
226   allExperiments.push_back(one_response);
227 }
228 
229 
load_data(const std::string & context_message)230 void ExperimentData::load_data(const std::string& context_message)
231 {
232   // TODO: complete scalar and field cases
233 
234   bool scalar_data_file = !scalarDataFilename.empty();
235   if (!calibrationDataFlag && !scalar_data_file) {
236     Cerr << "\nError: load_data attempted for empty experiment spec."
237 	 << std::endl;
238     abort_handler(-1);
239   }
240 
241   // Get a copy of the simulation SRD to use in contructing the
242   // experiment response; won't be able to share the core data since
243   // different experiments may have different sizes...
244   SharedResponseData exp_srd = simulationSRD.copy();
245 
246   // change the type of response
247   // TODO: new ctor for experiment response
248   exp_srd.response_type(EXPERIMENT_RESPONSE);
249   Response exp_resp(exp_srd);
250   if (outputLevel >= DEBUG_OUTPUT) {
251     Cout << "Constructing experiment response; initial response is:"
252 	 << std::endl;
253     exp_resp.write(Cout);
254   }
255 
256   if (numConfigVars > 0)
257     allConfigVars.resize(numExperiments);
258 
259   size_t num_scalars = simulationSRD.num_scalar_primary();
260 
261   // Count how many fields have each sigma type (for sizing). For
262   // field data, if "scalar" or "none" is specified, need to convert
263   // to a diagonal the length of the actual data
264 
265   // only fields can have matrix or diagonal
266   size_t num_field_sigma_matrices =
267     std::count(varianceTypes.begin(), varianceTypes.end(), MATRIX_SIGMA);
268   size_t num_field_sigma_diagonals =
269     std::count(varianceTypes.begin(), varianceTypes.end(), DIAGONAL_SIGMA);
270   // this counts how many fields have scalar or no sigma, as given by user
271   // varianceTypes is either empty or number response groups
272   size_t num_field_sigma_scalars = 0;
273   size_t num_field_sigma_none = 0;
274   if( !varianceTypes.empty() ) {
275     num_field_sigma_scalars =
276       std::count(varianceTypes.begin()+num_scalars, varianceTypes.end(),
277 		 SCALAR_SIGMA);
278     num_field_sigma_none =
279       std::count(varianceTypes.begin()+num_scalars, varianceTypes.end(),
280 		 NO_SIGMA);
281   }
282 
283   // TODO: temporary duplication until necessary covariance APIs are updated
284 
285   // setup for reading historical format, one experiment per line,
286   // each consisting of [ [config_vars] fn values [ fn variance ] ]
287   std::ifstream scalar_data_stream;
288   if (scalar_data_file) {
289     if (outputLevel >= NORMAL_OUTPUT) {
290       Cout << "\nReading scalar experimental data from file "
291 	   << scalarDataFilename << ":";
292       Cout << "\n  " << numExperiments << " experiment(s) for";
293       Cout << "\n  " << num_scalars << " scalar responses" << std::endl;
294     }
295     TabularIO::open_file(scalar_data_stream, scalarDataFilename,
296 			 context_message);
297     TabularIO::read_header_tabular(scalar_data_stream, scalarDataFormat);
298   }
299 
300   if (!scalar_data_file && numConfigVars > 0) {
301     // read all experiment config vars from new field data format files at once
302     // TODO: have the user give a name for this file, since should be
303     // the same for all responses.  Read from foo.<exp_num>.config.
304     //    String config_vars_basename("experiment");
305     boost::filesystem::path config_vars_basepath = dataPathPrefix / "experiment";
306     try {
307       read_config_vars_multifile(config_vars_basepath.string(), numExperiments,
308           numConfigVars, allConfigVars);
309     }
310     catch(std::runtime_error & e)
311     {
312       if( numConfigVars > 0 )
313         throw
314           std::runtime_error("Expected to read " +
315                              convert_to_string(numConfigVars) +
316                              " experiment config variables, but required file(s) \"" +
317                              config_vars_basepath.string() +
318                              ".*.config\" not found.");
319     }
320   }
321 
322   bool found_error = false;
323   for (size_t exp_index = 0; exp_index < numExperiments; ++exp_index) {
324 
325     // conditionally read leading exp_id column
326     // TODO: error handling
327     if (scalar_data_file)
328       TabularIO::read_leading_columns(scalar_data_stream, scalarDataFormat);
329 
330     // -----
331     // Read and set the configuration variables
332     // -----
333 
334     // Need to decide what to do if both scalar_data_file and "experiment.#" files exist - RWH
335     if ( (numConfigVars > 0) && scalar_data_file ) {
336       allConfigVars[exp_index].sizeUninitialized(numConfigVars);
337       // TODO: try/catch
338       scalar_data_stream >> std::ws;
339       if ( scalar_data_stream.eof() ) {
340         Cerr << "\nError: End of file '" << scalarDataFilename
341           << "' reached before reading "
342           << numExperiments << " sets of values."
343           << std::endl;
344         abort_handler(-1);
345       }
346       scalar_data_stream >> allConfigVars[exp_index];
347     }
348     // TODO: else validate scalar vs. field configs?
349 
350     // read one file per field response, resize, and populate the
351     // experiment (values, sigma, coords)
352     load_experiment(exp_index, scalar_data_stream, num_field_sigma_matrices,
353 		    num_field_sigma_diagonals, num_field_sigma_scalars,
354 		    num_field_sigma_none, exp_resp);
355 
356     if (simulationSRD.field_lengths() != exp_resp.field_lengths() &&
357 	!interpolateFlag) {
358       Cerr << "\nError: field lengths of experiment " << exp_index+1
359 	   << " data:\n       " << exp_resp.field_lengths()
360 	   << "\n       differ from simulation field lengths:"
361 	   << simulationSRD.field_lengths() << "and 'interpolate' not enabled."
362 	   << std::endl;
363 	found_error = true;
364     }
365 
366     if (outputLevel >= DEBUG_OUTPUT)
367       Cout << "Values for experiment " << exp_index + 1 << ": \n"
368 	   << exp_resp.function_values() << std::endl;
369 
370     allExperiments.push_back(exp_resp.copy());
371   }
372   if (found_error)
373     abort_handler(-1);
374 
375 
376   // verify that the two experiments have different data
377   if (outputLevel >= DEBUG_OUTPUT) {
378     Cout << "Experiment data summary:\n\n";
379     for (size_t i=0; i<numExperiments; ++i) {
380       if (numConfigVars > 0)
381 	Cout << "  Experiment " << i+1 << " configuration variables:"<< "\n"
382 	     << allConfigVars[i];
383       Cout << "  Experiment " << i+1 << " data values:"<< "\n"
384 	   << allExperiments[i].function_values() << '\n';
385     }
386   }
387 
388   // store the lengths (number of functions) of each experiment
389   per_exp_length(experimentLengths);
390   size_t i, num_exp = allExperiments.size();
391   expOffsets.sizeUninitialized(num_exp);
392   expOffsets(0) = 0;
393   for (i=1; i<num_exp; i++)
394     expOffsets(i) = experimentLengths(i-1) + expOffsets(i-1);
395 
396   // Precompute and cache experiment determinants
397   covarianceDeterminant = 1.0;
398   logCovarianceDeterminant = 0.0;
399   for (size_t exp_ind=0; exp_ind < numExperiments; ++exp_ind) {
400     covarianceDeterminant *= allExperiments[exp_ind].covariance_determinant();
401     logCovarianceDeterminant +=
402       allExperiments[exp_ind].log_covariance_determinant();
403   }
404 
405   // Check and warn if extra data exists in scalar_data_stream
406   if (scalar_data_file) {
407     scalar_data_stream >> std::ws;
408     if ( !scalar_data_stream.eof() )
409       Cout << "\nWarning: Data file '" << scalarDataFilename
410         << "' contains extra data."
411         << std::endl;
412   }
413 }
414 
415 
416 /** Load an experiment from a mixture of legacy format data and field
417     data format files */
418 void ExperimentData::
load_experiment(size_t exp_index,std::ifstream & scalar_data_stream,size_t num_field_sigma_matrices,size_t num_field_sigma_diagonals,size_t num_field_sigma_scalars,size_t num_field_sigma_none,Response & exp_resp)419 load_experiment(size_t exp_index, std::ifstream& scalar_data_stream,
420 		size_t num_field_sigma_matrices,
421 		size_t num_field_sigma_diagonals,
422 		size_t num_field_sigma_scalars,
423 		size_t num_field_sigma_none, Response& exp_resp)
424 {
425   bool scalar_data_file = !scalarDataFilename.empty();
426   size_t num_scalars = simulationSRD.num_scalar_primary();
427   size_t num_fields = simulationSRD.num_field_response_groups();
428   size_t num_resp = num_scalars + num_fields;
429 
430 
431   // -----
432   // Data to populate from files for a single experiment
433   // -----
434 
435   // Since the field lengths aren't known until all reads are
436   // complete, use temporary storage to read them all, then resize the
437   // Response object.
438   //
439   // TODO: May want to have field lengths managed in Response instead
440   // of SharedResponse and generate labels on the fly when needed.
441 
442   // scalar or field values; the RealVectors for scalars will have
443   // length 1; the length of RealVectors for fields will be determined
444   // by the file read
445   RealVectorArray exp_values(num_scalars + num_fields);
446 
447   // field lengths may differ for each experiment
448   IntVector field_lengths(num_fields, 0);
449 
450   // coordinates for fields only
451   RealMatrix exp_coords;
452 
453   // Non-field response sigmas; field response sigma scalars later get expanded into diagonals
454   RealVector sigma_scalars;
455   IntVector scalar_map_indices;
456 
457   // -----
458   // Read the data
459   // -----
460 
461   // populate scalar data function and sigma values; translate data
462   // from historical to new format (later collapse and eliminate
463   // copies) TODO: advance a row of exp data in outer context and pass
464   // in here to simplify these two cases
465   sigma_scalars.resize(num_scalars);
466   scalar_map_indices.resize(num_scalars);
467   if (scalar_data_file) {
468     // Non-field response sigmas; field response sigma scalars later get expanded into diagonals
469     for (size_t fn_index = 0; fn_index < num_scalars; ++fn_index) {
470       exp_values[fn_index].resize(1);
471       scalar_data_stream >> std::ws;
472       if ( scalar_data_stream.eof() ) {
473         Cerr << "\nError: End of file '" << scalarDataFilename
474           << "' reached before reading "
475           << numExperiments << " sets of values."
476           << std::endl;
477         abort_handler(-1);
478       }
479       scalar_data_stream >> exp_values[fn_index];
480     }
481     if (scalarSigmaPerRow > 0)
482       read_scalar_sigma(scalar_data_stream, sigma_scalars, scalar_map_indices);
483     else {
484       sigma_scalars = 1.0;  // historically these defaulted to 1.0
485       for (size_t i = 0; i<num_scalars; ++i) {
486         scalar_map_indices[i] = i;
487       }
488       // BMA: in a mixed case we might want these populated with 1.0
489       // even if data missing
490     }
491   }
492   else
493   {
494     RealMatrix working_cov_values;
495     const StringArray& scalar_labels = exp_resp.function_labels();
496     for( size_t i=0; i<num_scalars; ++i ) {
497       //std::cout << "Scalar function " << i << ": veriance_type = " << varianceTypes[i] << ", label = \"" << scalar_labels[i] << "\"" << std::endl;
498       // Read data from file named: scalar_labels.expt_num.dat
499       boost::filesystem::path field_base = dataPathPrefix / scalar_labels[i];
500       read_field_values(field_base.string(), exp_index+1, exp_values[i]);
501 
502       // Optionally allow covariance data
503       if (!varianceTypes.empty()) {
504 	if( varianceTypes[i] ) {
505 	  read_covariance(field_base.string(), exp_index+1, working_cov_values);
506 	  sigma_scalars[i] = working_cov_values(0,0);
507 	  scalar_map_indices[i] = i;
508 	}
509 	else {
510 	  sigma_scalars[i] = 1.0;
511 	  scalar_map_indices[i] = i;
512 	}
513       }
514     }
515 
516   }
517 
518   // Data for sigma - Note: all fields have matrix, diagonal or none covariance (sigma_ data)
519   //                        and so we dimension accordingly.
520 
521   // For sanity checking
522   size_t count_no_sigmas       = 0;
523   size_t count_sigma_scalars   = 0;
524   size_t count_sigma_diagonals = 0;
525   size_t count_sigma_matrices  = 0;
526 
527   std::vector<RealMatrix> sigma_matrices(num_field_sigma_matrices);
528   // for fields we make diagonal covariance for these three types
529   std::vector<RealVector> sigma_diagonals(num_field_sigma_diagonals +
530 					  num_field_sigma_scalars +
531 					  num_field_sigma_none);
532   // indices for the entries in the above data structures
533   IntVector matrix_map_indices(num_field_sigma_matrices),
534             diagonal_map_indices(num_field_sigma_diagonals +
535 				 num_field_sigma_scalars +
536 				 num_field_sigma_none);
537 
538 
539   // populate field data, sigma, and coordinates from separate files
540   const StringArray& field_labels = exp_resp.field_group_labels();
541   for (size_t field_index = 0; field_index < num_fields; ++field_index) {
542     size_t fn_index = num_scalars + field_index;
543     const String& field_name = field_labels[field_index];
544 
545     // read a column vector of field values for this field from
546     // fn_name.exp_num.dat
547     boost::filesystem::path field_base = dataPathPrefix / field_name;
548     read_field_values(field_base.string(), exp_index+1, exp_values[fn_index]);
549     field_lengths[field_index] = exp_values[fn_index].length();
550 
551     // For fields with corresponding coords file, read coordinates
552     // from field_name.exp_num.coords and validate number of rows is
553     // field_lengths[field_index]
554     std::string filename = field_name + "." + Dakota::convert_to_string(exp_index+1) + ".coords";
555     boost::filesystem::path coord_path_and_file = dataPathPrefix / filename;
556     if ( boost::filesystem::is_regular_file(coord_path_and_file) )
557     {
558       boost::filesystem::path coord_base = dataPathPrefix / field_name;
559       read_coord_values(coord_base.string(), exp_index+1, exp_coords);
560       // Sanity check length
561       if( field_lengths[field_index] != exp_coords.numRows() )
562         throw std::runtime_error("Inconsistent lengths of field data and coordinates.");
563       exp_resp.field_coords(exp_coords, field_index);
564     }
565 
566     if (!varianceTypes.empty()) {
567        // read sigma 1, N (field_lengths[field_index]), or N^2 values
568       RealMatrix working_cov_values;
569       switch(varianceTypes[fn_index])
570 	{
571 	case NO_SIGMA:
572 	  // expand to a diagonal of 1.0 of appropriate length = field_length and add
573 	  // field_index to diagonals map
574 	  sigma_diagonals[count_sigma_diagonals].sizeUninitialized(field_lengths[field_index]);
575 	  for( int i=0; i<field_lengths[field_index]; ++i )
576 	    sigma_diagonals[count_sigma_diagonals](i) = 1.0;
577 	  diagonal_map_indices[count_sigma_diagonals++] = fn_index; // or should it be field_index? - RWH
578 	  count_no_sigmas++;
579 	  break;
580 
581 	case SCALAR_SIGMA:
582 	  // read single value, expand to a diagonal of appropriate length = field_length and add
583 	  // field_index to diagonals map
584 	  Cout << "Reading scalar cov from " << field_base.string() << std::endl;
585 	  read_covariance(field_base.string(), exp_index+1, working_cov_values);
586 	  sigma_diagonals[count_sigma_diagonals].sizeUninitialized(field_lengths[field_index]);
587 	  for( int i=0; i<field_lengths[field_index]; ++i )
588 	    sigma_diagonals[count_sigma_diagonals](i) = working_cov_values(0,0);
589 	  diagonal_map_indices[count_sigma_diagonals++] = fn_index; // or should it be field_index? - RWH
590 	  count_sigma_scalars++;
591 	  break;
592 
593 	case DIAGONAL_SIGMA:
594 	  // read N values, add to sigma_diagonals and add num_scalars +
595 	  // field_index to diagonals map
596 	  Cout << "Reading diagonal cov from " << field_base.string() << std::endl;
597 	  read_covariance(field_base.string(), exp_index+1, Dakota::CovarianceMatrix::VECTOR,
598 			  field_lengths[field_index], working_cov_values);
599 	  sigma_diagonals[count_sigma_diagonals].sizeUninitialized(field_lengths[field_index]);
600 	  for( int i=0; i<field_lengths[field_index]; ++i )
601 	    sigma_diagonals[count_sigma_diagonals](i) = working_cov_values[0][i];
602 	  diagonal_map_indices[count_sigma_diagonals++] = fn_index; // or should it be field_index? - RWH
603 	  //sigma_diagonals[count_sigma_diagonals-1].print(Cout);
604 	  break;
605 
606 	case MATRIX_SIGMA:
607 	  // read N^2 values, add to sigma_matrices and add num_scalars +
608 	  // field_index to matrices map
609 	  read_covariance(field_base.string(), exp_index+1, Dakota::CovarianceMatrix::MATRIX,
610 			  field_lengths[field_index], working_cov_values);
611           // Check for symmetry
612           if( !is_matrix_symmetric(working_cov_values) )
613             throw std::runtime_error("Covariance matrix from \""+field_base.string()+"\" is not symmetric.");
614 	  sigma_matrices[count_sigma_matrices] = working_cov_values;
615 	  matrix_map_indices[count_sigma_matrices++] = fn_index; // or should it be field_index? - RWH
616 	  //sigma_matrices[count_sigma_matrices-1].print(Cout);
617 	  break;
618 	}
619     }
620   }
621   // Sanity check consistency
622   if( count_no_sigmas != num_field_sigma_none )
623     throw std::runtime_error("Mismatch between specified and actual fields with no sigma provided.");
624   if( count_sigma_scalars != num_field_sigma_scalars )
625     throw std::runtime_error("Mismatch between specified and actual sigma scalars.");
626   if( count_sigma_diagonals != (num_field_sigma_diagonals +
627 				num_field_sigma_scalars + num_field_sigma_none) )
628     throw std::runtime_error("Mismatch between specified and actual sigma diagonals.");
629   if( count_sigma_matrices != num_field_sigma_matrices )
630     throw std::runtime_error("Mismatch between specified and actual sigma matrices.");
631 
632 
633   // -----
634   // Reshape and map in the data
635   // -----
636 
637   // Reshape the experiment, now that we know total size
638   exp_resp.field_lengths(field_lengths);
639 
640   for (size_t fn_index = 0; fn_index < num_scalars; ++fn_index)
641     exp_resp.function_value(exp_values[fn_index][0], fn_index);
642 
643   for (size_t field_ind = 0; field_ind < num_fields; ++field_ind)
644     exp_resp.field_values(exp_values[num_scalars + field_ind], field_ind);
645 
646   //Cout << "Sigma scalars " << sigma_scalars << "\n";
647   //Cout << "Scalar map indices" << scalar_map_indices << "\n";
648 
649   exp_resp.set_full_covariance(sigma_matrices, sigma_diagonals, sigma_scalars,
650         		       matrix_map_indices, diagonal_map_indices,
651         		       scalar_map_indices);
652 
653 }
654 
655 
656 
read_scalar_sigma(std::ifstream & scalar_data_stream,RealVector & sigma_scalars,IntVector & scalar_map_indices)657 void ExperimentData::read_scalar_sigma(std::ifstream& scalar_data_stream,
658 				       RealVector& sigma_scalars,
659 				       IntVector& scalar_map_indices)
660 {
661   // currently no longer allow 1 sigma to apply to all scalar responses
662   // always read 0, or N
663   RealVector sigma_row(scalarSigmaPerRow);
664   scalar_data_stream >> sigma_row;
665   for (size_t i = 0; i<scalarSigmaPerRow; ++i) {
666     sigma_scalars[i] = sigma_row[i];
667     scalar_map_indices[i] = i;
668   }
669 }
670 
671 size_t ExperimentData::
num_scalar_primary() const672 num_scalar_primary() const
673 {
674   if( simulationSRD.is_null() )
675     throw std::runtime_error("ExperimentData is incorrectly (or not) initialized.");
676 
677   return simulationSRD.num_scalar_primary();
678 }
679 
680 size_t ExperimentData::
num_fields() const681 num_fields() const
682 {
683   if( simulationSRD.is_null() )
684     throw std::runtime_error("ExperimentData is incorrectly (or not) initialized.");
685 
686   return  simulationSRD.num_field_response_groups();
687 }
688 
689 
num_config_vars() const690 size_t ExperimentData::num_config_vars() const
691 {
692   return numConfigVars;
693 }
694 
695 
config_vars() const696 const std::vector<RealVector>& ExperimentData::config_vars() const
697 {
698   return allConfigVars;
699 }
700 
701 
per_exp_length(IntVector & per_length) const702 void ExperimentData::per_exp_length(IntVector& per_length) const
703 {
704   per_length.resize(allExperiments.size());
705   //Cout << "num experiments " << num_experiments();
706 
707   for (size_t i=0; i<num_experiments(); i++)
708     per_length(i)= allExperiments[i].function_values().length();
709   //Cout << "per length " << per_length;
710 }
711 
712 
field_lengths(size_t experiment) const713 const IntVector& ExperimentData::field_lengths(size_t experiment) const
714 {
715   return allExperiments[experiment].field_lengths();
716 }
717 
all_data(size_t experiment)718 const RealVector& ExperimentData::all_data(size_t experiment)
719 {
720   if (experiment >= allExperiments.size()) {
721     Cerr << "\nError: invalid experiment index " << experiment << std::endl;
722     abort_handler(-1);
723   }
724   return allExperiments[experiment].function_values();
725 }
726 
response(size_t experiment)727 const Response& ExperimentData::response(size_t experiment)
728 {
729   if (experiment >= allExperiments.size()) {
730     Cerr << "\nError: invalid experiment index " << experiment << std::endl;
731     abort_handler(-1);
732   }
733   return allExperiments[experiment];
734 }
735 
num_total_exppoints() const736 size_t ExperimentData::num_total_exppoints() const
737 {
738   size_t res_size = 0;
739   for (size_t i=0; i<num_experiments(); i++)
740     res_size += allExperiments[i].function_values().length();
741   return res_size;
742 }
743 
744 Real ExperimentData::
scalar_data(size_t response,size_t experiment)745 scalar_data(size_t response, size_t experiment)
746 {
747   //if (allExperiments[response].experimentType != SCALAR_DATA) {
748   //  Cerr << "Error (ExperimentData): invalid query of scalar data." << std::endl;
749   //  abort_handler(-1);
750   //}
751   return(allExperiments[experiment].function_value(response));
752 }
753 
754 RealVector ExperimentData::
field_data_view(size_t response,size_t experiment) const755 field_data_view(size_t response, size_t experiment) const
756 {
757   return(allExperiments[experiment].field_values_view(response));
758 }
759 
760 RealMatrix ExperimentData::
field_coords_view(size_t response,size_t experiment) const761 field_coords_view(size_t response, size_t experiment) const
762 {
763   return(allExperiments[experiment].field_coords_view(response));
764 }
765 
variance_type_active(short variance_type) const766 bool ExperimentData::variance_type_active(short variance_type) const
767 {
768   UShortArray::const_iterator vt_it =
769     std::find(varianceTypes.begin(), varianceTypes.end(), variance_type);
770   return vt_it != varianceTypes.end();
771 }
772 
variance_active() const773 bool ExperimentData::variance_active() const
774 {
775   return (variance_type_active(SCALAR_SIGMA) ||
776 	  variance_type_active(DIAGONAL_SIGMA) ||
777 	  variance_type_active(MATRIX_SIGMA));
778 }
779 
interpolate_flag() const780 bool ExperimentData::interpolate_flag() const
781 {
782   return interpolateFlag;
783 }
784 
785 RealVector ExperimentData::
residuals_view(const RealVector & residuals,size_t experiment) const786 residuals_view(const RealVector& residuals, size_t experiment) const
787 {
788   int exp_offset = expOffsets[experiment];
789   RealVector exp_resid(Teuchos::View, residuals.values()+exp_offset,
790 		       experimentLengths[experiment]);
791   return exp_resid;
792 }
793 
794 /// Return a view (to allowing updaing in place) of the gradients associated
795 /// with a given experiment, from a matrix contaning gradients from
796 /// all experiments
797 RealMatrix ExperimentData::
gradients_view(const RealMatrix & gradients,size_t experiment) const798 gradients_view(const RealMatrix &gradients, size_t experiment) const
799 {
800   int exp_offset = expOffsets[experiment];
801   RealMatrix exp_grads(Teuchos::View, gradients, gradients.numRows(),
802 		       experimentLengths[experiment], 0, exp_offset );
803   return exp_grads;
804 }
805 
806 /// Return a view (to allowing updaing in place) of the hessians associated
807 /// with a given experiment, from an array contaning the hessians from
808 /// all experiments
809 RealSymMatrixArray ExperimentData::
hessians_view(const RealSymMatrixArray & hessians,size_t experiment) const810 hessians_view(const RealSymMatrixArray &hessians, size_t experiment) const {
811   int num_hess = experimentLengths[experiment],
812     exp_offset = expOffsets[experiment];
813   RealSymMatrixArray exp_hessians( num_hess );
814   if ( !hessians.empty() ){
815     for (size_t i=0; i<num_hess; ++i,++exp_offset)
816       if (hessians[exp_offset].numRows()) // else leave exp_hessians[i] empty
817 	exp_hessians[i] = RealSymMatrix(Teuchos::View, hessians[exp_offset],
818 					hessians[exp_offset].numRows());
819   }
820   return exp_hessians;
821 }
822 
823 Real ExperimentData::
apply_covariance(const RealVector & residuals,size_t experiment) const824 apply_covariance(const RealVector& residuals, size_t experiment) const
825 {
826   RealVector exp_resid = residuals_view( residuals, experiment );
827   if ( variance_active() )
828     return(allExperiments[experiment].apply_covariance(exp_resid));
829   else
830     return exp_resid.dot( exp_resid );
831 }
832 
833 // BMA TODO: These functions don't get called when covariance is
834 // inactive, but if they did, could undesireably resize the outbound
835 // object.
836 void ExperimentData::
apply_covariance_inv_sqrt(const RealVector & residuals,size_t experiment,RealVector & weighted_residuals) const837 apply_covariance_inv_sqrt(const RealVector& residuals, size_t experiment,
838 			  RealVector& weighted_residuals) const
839 {
840   RealVector exp_resid = residuals_view( residuals, experiment );
841   if ( variance_active() )
842     allExperiments[experiment].apply_covariance_inv_sqrt(exp_resid,
843 							 weighted_residuals);
844   else{
845     // Return a deep copy
846     weighted_residuals.sizeUninitialized( exp_resid.length() );
847     weighted_residuals.assign( exp_resid );
848   }
849 }
850 
851 // BMA TODO: These functions don't get called when covariance is
852 // inactive, but if they did, could undesireably resize the outbound
853 // object.
854 void ExperimentData::
apply_covariance_inv_sqrt(const RealMatrix & gradients,size_t experiment,RealMatrix & weighted_gradients) const855 apply_covariance_inv_sqrt(const RealMatrix& gradients, size_t experiment,
856 			  RealMatrix& weighted_gradients) const
857 {
858   RealMatrix exp_grads = gradients_view( gradients, experiment );
859   if ( variance_active() )
860     allExperiments[experiment].apply_covariance_inv_sqrt(exp_grads,
861 							 weighted_gradients);
862   else{
863     // Return a deep copy
864     weighted_gradients.shapeUninitialized( exp_grads.numRows(),
865 					   exp_grads.numCols() );
866     weighted_gradients.assign( exp_grads );
867   }
868 }
869 
870 // BMA TODO: These functions don't get called when covariance is
871 // inactive, but if they did, could undesireably resize the outbound
872 // object.
873 void ExperimentData::
apply_covariance_inv_sqrt(const RealSymMatrixArray & hessians,size_t experiment,RealSymMatrixArray & weighted_hessians) const874 apply_covariance_inv_sqrt(const RealSymMatrixArray& hessians, size_t experiment,
875 			  RealSymMatrixArray& weighted_hessians) const
876 {
877   RealSymMatrixArray exp_hessians = hessians_view( hessians, experiment );
878   if ( variance_active() )
879     allExperiments[experiment].apply_covariance_inv_sqrt(exp_hessians,
880 							 weighted_hessians);
881   else{
882     // Return a deep copy
883     size_t num_hess = exp_hessians.size();
884     weighted_hessians.resize( num_hess );
885     if ( !exp_hessians.empty() ){
886       for (size_t i=0; i<num_hess; ++i)
887 	if (exp_hessians[i].numRows()) // else leave exp_hessians[i] empty
888 	  weighted_hessians[i] = RealSymMatrix(Teuchos::Copy,
889 					       exp_hessians[i],
890 					       exp_hessians[i].numRows());
891     }
892   }
893 
894 }
895 
apply_simulation_error(const RealVector & simulation_error,size_t experiment)896 void ExperimentData::apply_simulation_error(const RealVector& simulation_error,
897                                             size_t experiment)
898 {
899   Response exp_response = allExperiments[experiment];
900   const RealVector& exp_vals = exp_response.function_values();
901   for (size_t i = 0; i < allExperiments[experiment].num_functions(); i++)
902     exp_response.function_value(exp_vals[i] + simulation_error[i], i);
903 }
904 
905 
906 void ExperimentData::
get_main_diagonal(RealVector & diagonal,size_t experiment) const907 get_main_diagonal(RealVector &diagonal, size_t experiment ) const
908 {
909   if ( !variance_active() )
910     throw( std::runtime_error("ExperimentData::get_main_diagonal - covariance matrix is empty") );
911     allExperiments[experiment].get_covariance_diagonal( diagonal );
912 }
913 
cov_std_deviation(RealVectorArray & std_deviations) const914 void ExperimentData::cov_std_deviation(RealVectorArray& std_deviations) const
915 {
916   std_deviations.resize(numExperiments);
917   for (size_t exp_ind=0; exp_ind<numExperiments; ++exp_ind) {
918     RealVector& sd_vec = std_deviations[exp_ind];
919     allExperiments[exp_ind].experiment_covariance().get_main_diagonal(sd_vec);
920     for (size_t i=0; i<sd_vec.length(); ++i)
921       sd_vec[i] = std::sqrt(sd_vec[i]);
922   }
923 }
924 
cov_as_correlation(RealSymMatrixArray & corr_matrices) const925 void ExperimentData::cov_as_correlation(RealSymMatrixArray& corr_matrices) const
926 {
927   corr_matrices.resize(numExperiments);
928   for (size_t exp_ind=0; exp_ind<numExperiments; ++exp_ind) {
929     const ExperimentCovariance& exp_cov =
930       allExperiments[exp_ind].experiment_covariance();
931     exp_cov.as_correlation(corr_matrices[exp_ind]);
932   }
933 }
934 
covariance(int exp_ind,RealSymMatrix & cov_mat) const935 void ExperimentData::covariance(int exp_ind, RealSymMatrix& cov_mat) const
936 {
937   const ExperimentCovariance& exp_cov =
938     allExperiments[exp_ind].experiment_covariance();
939   exp_cov.dense_covariance(cov_mat);
940 }
941 
942 
943 /** This assumes the souce gradient/Hessian are size less or equal to
944     the destination response, and that the leading part is to be populated. */
945 void ExperimentData::
form_residuals(const Response & sim_resp,Response & residual_resp) const946 form_residuals(const Response& sim_resp, Response& residual_resp) const
947 {
948   // BMA: perhaps a better name would be per_exp_asv?
949   // BMA TODO: Make this call robust to zero and single experiment cases
950   ShortArray total_asv = determine_active_request(residual_resp);
951 
952   IntVector experiment_lengths;
953   per_exp_length(experiment_lengths);
954   size_t residual_resp_offset = 0;
955   for (size_t exp_ind = 0; exp_ind < numExperiments; ++exp_ind){
956     size_t num_fns_exp = experiment_lengths[exp_ind]; // total length this exper
957     form_residuals( sim_resp, exp_ind, total_asv, residual_resp_offset,
958 		    residual_resp );
959     residual_resp_offset += num_fns_exp;
960   }
961 }
962 
963 
964 void ExperimentData::
form_residuals(const Response & sim_resp,const size_t curr_exp,Response & residual_resp) const965 form_residuals(const Response& sim_resp, const size_t curr_exp,
966 	       Response& residual_resp) const
967 {
968   // BMA: perhaps a better name would be per_exp_asv?
969   // BMA TODO: Make this call robust to zero and single experiment cases
970   ShortArray total_asv = determine_active_request(residual_resp);
971 
972   IntVector experiment_lengths;
973   per_exp_length(experiment_lengths);
974   size_t residual_resp_offset = 0;
975   for (size_t exp_ind = 0; exp_ind < curr_exp; ++exp_ind){
976     size_t num_fns_exp = experiment_lengths[exp_ind]; // total length this exper
977     residual_resp_offset += num_fns_exp;
978   }
979   form_residuals(sim_resp, curr_exp, total_asv, residual_resp_offset,
980 		 residual_resp);
981 }
982 
983 
984 /** This assumes the souce gradient/Hessian are size less or equal to
985     the destination response, and that the leading part is to be populated. */
986 void ExperimentData::
form_residuals(const Response & sim_resp,size_t exp_ind,const ShortArray & total_asv,size_t exp_offset,Response & residual_resp) const987 form_residuals(const Response& sim_resp, size_t exp_ind,
988 	       const ShortArray &total_asv, size_t exp_offset,
989 	       Response &residual_resp ) const
990 {
991   // size of the residuals for this one experiment
992   size_t exp_resid_size = allExperiments[exp_ind].function_values().length();
993   // the functions from the simulation
994   RealVector sim_fns = sim_resp.function_values();
995   RealMatrix sim_grads = sim_resp.function_gradients_view();
996   RealSymMatrixArray sim_hessians = sim_resp.function_hessians_view();
997 
998   const IntVector& sim_field_lens = sim_resp.field_lengths();
999 
1000   short asv = total_asv[exp_ind];
1001   RealVector all_residuals = residual_resp.function_values_view();
1002   RealVector exp_residuals(Teuchos::View, all_residuals.values()+exp_offset,
1003 			   exp_resid_size);
1004 
1005   if (!interpolateFlag) {
1006 
1007     for (size_t i=0; i<exp_resid_size; i++){
1008       exp_residuals[i] =
1009 	sim_fns[i] - allExperiments[exp_ind].function_value(i);
1010      }
1011     // populate only the part of the gradients/Hessians for this
1012     // experiment, for the active submodel derivative variables
1013     if ( asv & 2 ){
1014       size_t num_sm_cv = sim_grads.numRows();
1015       RealMatrix resid_grads
1016 	(gradients_view(residual_resp.function_gradients(), exp_ind));
1017       resid_grads = 0.0;
1018       for (size_t r_ind = 0; r_ind<exp_resid_size; ++r_ind)
1019 	for (size_t i=0; i<num_sm_cv; ++i)
1020 	  resid_grads(i, r_ind) = sim_grads(i, r_ind);
1021     }
1022 
1023     if ( asv & 4 ) {
1024       size_t num_sm_cv = sim_grads.numRows();
1025       RealSymMatrixArray resid_hess
1026 	(hessians_view(residual_resp.function_hessians(), exp_ind));
1027       for (size_t r_ind = 0; r_ind<exp_resid_size; ++r_ind) {
1028 	resid_hess[r_ind] = 0.0;
1029 	for (size_t i=0; i<num_sm_cv; ++i)
1030 	  for (size_t j=0; j<num_sm_cv; ++j)
1031 	    resid_hess[r_ind](i,j) = sim_hessians[r_ind](i,j);
1032       }
1033     }
1034 
1035   } else {
1036 
1037     for (size_t i=0; i<num_scalar_primary(); i++) {
1038       exp_residuals[i] = sim_fns[i] - allExperiments[exp_ind].function_value(i);
1039       // BMA: Looked like gradients and Hessians of the scalars weren't
1040       // populated, so added:
1041       if (asv & 2) {
1042 	size_t num_sm_cv = sim_grads.numRows();
1043 	RealVector resid_grad =
1044 	  residual_resp.function_gradient_view(exp_offset+i);
1045 	resid_grad = 0.0;
1046 	for (size_t j=0; j<num_sm_cv; ++j)
1047 	  resid_grad[j] = sim_grads(j, i);
1048       }
1049       if (asv & 4) {
1050 	size_t num_sm_cv = sim_hessians[i].numRows();
1051 	RealSymMatrix resid_hess =
1052 	  residual_resp.function_hessian_view(exp_offset+i);
1053 	resid_hess = 0.0;
1054 	for (size_t j=0; j<num_sm_cv; ++j)
1055 	  for (size_t k=0; k<num_sm_cv; ++k)
1056 	    resid_hess(j,k) = sim_hessians[i](j,k);
1057 
1058       }
1059     }
1060 
1061     // interpolate the simulation data onto the coordinates of the experiment
1062     // data, populating functions, gradients, Hessians
1063 
1064     // I think residuals are stored in continguous order,
1065     // [exp1(scalars,fields),...,exp_n(scalars,fields)
1066     // if so need to pass exp_offset to interpolate function above
1067     // and inside that function set offset =  exp_offset and
1068     // then increment in usual way
1069     interpolate_simulation_data(sim_resp, exp_ind, total_asv, exp_offset,
1070 				residual_resp);
1071 
1072     if (outputLevel >= DEBUG_OUTPUT)
1073       Cout << "interp values" << exp_residuals << '\n';
1074 
1075     if (asv & 1) {
1076       // compute the residuals, i.e. subtract the experiment data values
1077       // from the (interpolated) simulation values.
1078       size_t cntr = num_scalar_primary();
1079       for (size_t i=0; i<num_fields(); i++){
1080 	size_t num_field_fns = field_data_view(i,exp_ind).length();
1081 	for (size_t j=0; j<num_field_fns; j++, cntr++)
1082 	  exp_residuals[cntr] -= field_data_view(i,exp_ind)[j];
1083       }
1084       if (outputLevel >= DEBUG_OUTPUT)
1085 	Cout << "residuals in exp space" << exp_residuals << '\n';
1086     }
1087   }
1088 }
1089 
1090 void ExperimentData::
interpolate_simulation_data(const Response & sim_resp,size_t exp_ind,const ShortArray & total_asv,size_t exp_offset,Response & interp_resp) const1091 interpolate_simulation_data(const Response &sim_resp, size_t exp_ind,
1092 			    const ShortArray &total_asv, size_t exp_offset,
1093 			    Response &interp_resp ) const
1094 {
1095   size_t offset = exp_offset + num_scalar_primary();
1096   IntVector field_lens = field_lengths(exp_ind);
1097   for (size_t field_num=0; field_num<num_fields(); field_num++){
1098     RealMatrix exp_coords = field_coords_view(field_num,exp_ind);
1099     interpolate_simulation_field_data( sim_resp, exp_coords, field_num,
1100 				       total_asv[exp_ind],
1101 				       offset, interp_resp );
1102     offset += field_lens[field_num];
1103   }
1104 }
1105 
1106 
1107 // BMA TODO: Make this call robust to zero and single experiment cases
1108 ShortArray ExperimentData::
determine_active_request(const Response & resid_resp) const1109 determine_active_request(const Response& resid_resp) const
1110 {
1111   ShortArray total_asv( numExperiments );
1112 
1113   // can't apply matrix-valued errors due to possibly incomplete
1114   // dataset when active set vector is in use (missing residuals)
1115   bool interogate_field_data =
1116     variance_type_active(MATRIX_SIGMA) || interpolate_flag();
1117 
1118   IntVector experiment_lengths;
1119   per_exp_length(experiment_lengths);
1120 
1121 
1122   size_t calib_term_ind = 0; // index into the total set of calibration terms
1123   for (size_t exp_ind = 0; exp_ind < numExperiments; ++exp_ind){
1124     // total length this exper
1125     size_t num_fns_exp = experiment_lengths[exp_ind];
1126 
1127     // Within a field group, cannot have matrix (off-diagonal)
1128     // covariance and non-uniform ASV
1129     //
1130     // TODO: This is overly conservative; instead check whether
1131     // matrix covariance is active on per-field group basis
1132     const ShortArray& asv = resid_resp.active_set_request_vector();
1133     total_asv[exp_ind] = 0;
1134     if (interogate_field_data) {
1135 
1136       size_t num_scalar = num_scalar_primary();
1137       for (size_t sc_ind = 0; sc_ind < num_scalar; ++sc_ind)
1138 	total_asv[exp_ind] |= asv[calib_term_ind + sc_ind];
1139 
1140       const IntVector& exp_field_lens = field_lengths(exp_ind);
1141       size_t num_field_groups = num_fields();
1142       size_t field_start = calib_term_ind + num_scalar;
1143       for (size_t fg_ind = 0; fg_ind < num_field_groups; ++fg_ind) {
1144 
1145 	// determine presence and consistency of active set vector
1146 	// requests within this field
1147 	size_t asv_1 = 0, asv_2 = 0, asv_4 = 0;
1148 	size_t num_fns_field = exp_field_lens[fg_ind];
1149 	for (size_t fn_ind = 0; fn_ind < num_fns_field; ++fn_ind) {
1150 	  if (asv[field_start + fn_ind] & 1) ++asv_1;
1151 	  if (asv[field_start + fn_ind] & 2) ++asv_2;
1152 	  if (asv[field_start + fn_ind] & 4) ++asv_4;
1153 	}
1154 
1155 	// with matrix covariance, each of fn, grad, Hess must have all
1156 	// same asv (either none or all) (within a field response group)
1157 	if ( (asv_1 != 0 && asv_1 != num_fns_field) ||
1158 	     (asv_2 != 0 && asv_2 != num_fns_field) ||
1159 	     (asv_4 != 0 && asv_4 != num_fns_field)) {
1160 	  Cerr << "\nError: matrix form of data error covariance cannot be "
1161 	       << "used with non-uniform\n       active set vector; consider "
1162 	       << "disabling active set vector or specifying no\n      , "
1163 	       << "scalar, or diagonal covariance" << std::endl;
1164 	  abort_handler(-1);
1165 	}
1166 	if (asv_1 > 0) total_asv[exp_ind] |= 1;
1167 	if (asv_2 > 0) total_asv[exp_ind] |= 2;
1168 	if (asv_4 > 0) total_asv[exp_ind] |= 4;
1169       }
1170     }
1171     else {
1172       // compute aggregate ASV over scalars and field data
1173       for (size_t fn_ind = 0; fn_ind < num_fns_exp; ++fn_ind)
1174 	total_asv[exp_ind] |= asv[calib_term_ind + fn_ind];
1175     }
1176 
1177     calib_term_ind += num_fns_exp;
1178   }  // for each experiment
1179   return(total_asv);
1180 }
1181 
1182 void ExperimentData::
scale_residuals(const Response & residual_response,RealVector & weighted_resid) const1183 scale_residuals(const Response& residual_response,
1184 		RealVector& weighted_resid) const {
1185 
1186   ShortArray total_asv = determine_active_request(residual_response);
1187 
1188   for (size_t exp_ind = 0; exp_ind < numExperiments; ++exp_ind) {
1189 
1190     // apply noise covariance to the residuals for this experiment
1191     // and store in correct place in weighted_residual
1192     if (outputLevel >= DEBUG_OUTPUT && total_asv[exp_ind] > 0)
1193       Cout << "Calibration: weighting residuals for experiment "
1194 	   << exp_ind + 1 << " with inverse of specified\nerror covariance."
1195 	   << std::endl;
1196 
1197     // apply cov_inv_sqrt to the residual vector
1198     if (total_asv[exp_ind] & 1) {
1199       // BMA: not sure why View didn't work (was disconnected):
1200       //RealVector exp_weighted_resid(residuals_view(weighted_resid, exp_ind));
1201       RealVector exp_weighted_resid;
1202       // takes full list of source residuals, but per-experiment output vector
1203       apply_covariance_inv_sqrt(residual_response.function_values(),
1204                                 exp_ind, exp_weighted_resid);
1205       size_t calib_term_ind = expOffsets[exp_ind];
1206       copy_data_partial(exp_weighted_resid, weighted_resid, calib_term_ind);
1207     }
1208 
1209   }
1210 }
1211 
1212 
scale_residuals(Response & residual_response) const1213 void ExperimentData::scale_residuals(Response& residual_response) const
1214 {
1215   ShortArray total_asv = determine_active_request(residual_response);
1216 
1217   IntVector experiment_lengths;
1218   per_exp_length(experiment_lengths);
1219 
1220   size_t calib_term_ind = 0; // index into the total set of calibration terms
1221   for (size_t exp_ind = 0; exp_ind < numExperiments; ++exp_ind){
1222     // total residuals in this exper
1223     size_t num_fns_exp = experiment_lengths[exp_ind];
1224 
1225     // apply noise covariance to the residuals for this experiment
1226     // and store in correct place in residual_response
1227     if (outputLevel >= DEBUG_OUTPUT && total_asv[exp_ind] > 0)
1228       Cout << "Calibration: weighting residuals for experiment "
1229 	   << exp_ind + 1 << " with inverse of\n specified error covariance."
1230 	   << std::endl;
1231 
1232     // apply cov_inv_sqrt to the residual vector
1233     RealVector weighted_resid;
1234     if (total_asv[exp_ind] & 1)
1235       apply_covariance_inv_sqrt(residual_response.function_values(),
1236 				exp_ind, weighted_resid);
1237     else
1238       weighted_resid =
1239 	residuals_view(residual_response.function_values(), exp_ind);
1240 
1241     // apply cov_inv_sqrt to each row of gradient matrix
1242     RealMatrix weighted_grad;
1243     if (total_asv[exp_ind] & 2) {
1244       apply_covariance_inv_sqrt(residual_response.function_gradients(),
1245 				exp_ind, weighted_grad);
1246     }
1247     else
1248       weighted_grad =
1249 	gradients_view(residual_response.function_gradients(), exp_ind);
1250 
1251     // apply cov_inv_sqrt to non-contiguous Hessian matrices
1252     RealSymMatrixArray weighted_hess;
1253     if (total_asv[exp_ind] & 4)
1254       apply_covariance_inv_sqrt(residual_response.function_hessians(),
1255 				exp_ind, weighted_hess);
1256     else
1257       weighted_hess = hessians_view(residual_response.function_hessians(),
1258 				    exp_ind);
1259 
1260     copy_field_data(weighted_resid, weighted_grad, weighted_hess,
1261 		    calib_term_ind, num_fns_exp, residual_response);
1262 
1263     calib_term_ind += num_fns_exp;
1264   }
1265 }
1266 
1267 
1268 /** Add the data back to the residual to recover the model, for use in
1269     surrogated-based LSQ where DB lookup will fail (need approx eval
1270     DB).  best_fns contains primary and secondary responses */
1271 void ExperimentData::
recover_model(size_t num_pri_fns,RealVector & best_fns) const1272 recover_model(size_t num_pri_fns, RealVector& best_fns) const
1273 {
1274   if (interpolateFlag) {
1275     Cerr << "Error: cannot recover model from residuals when interpolating.\n";
1276     abort_handler(-1);
1277   }
1278   const Response& experiment0 = allExperiments[0];
1279   if (num_pri_fns != experiment0.num_functions()) {
1280     Cerr << "Error: incompatible sizes in recover_model()\n";
1281     abort_handler(-1);
1282   }
1283   for (size_t i=0; i<num_pri_fns; ++i)
1284     best_fns[i] += experiment0.function_value(i);
1285 }
1286 
1287 void ExperimentData::
build_gradient_of_sum_square_residuals(const Response & resp,const ShortArray & asrv,RealVector & ssr_gradient)1288 build_gradient_of_sum_square_residuals( const Response& resp,
1289 					const ShortArray& asrv,
1290 					RealVector &ssr_gradient )
1291 {
1292   // initialize ssr_gradient to zero, prior to summing over set of experiments
1293   size_t exp_ind, num_v = resp.active_set().derivative_vector().size();
1294   if (ssr_gradient.length() != num_v) ssr_gradient.size(num_v); // init to 0.
1295   else                                ssr_gradient = 0.;
1296   //size_t residual_resp_offset = 0;
1297   for (exp_ind = 0; exp_ind < numExperiments; ++exp_ind)
1298     // adds to ssr_gradient for each experiment
1299     build_gradient_of_sum_square_residuals_from_response( resp, asrv, exp_ind,
1300 							  ssr_gradient );
1301 }
1302 
1303 void ExperimentData::
build_gradient_of_sum_square_residuals_from_response(const Response & resp,const ShortArray & asrv,int exp_ind,RealVector & ssr_gradient)1304 build_gradient_of_sum_square_residuals_from_response( const Response& resp,
1305 						      const ShortArray& asrv,
1306 						      int exp_ind,
1307 						      RealVector &ssr_gradient)
1308 {
1309   RealVector scaled_residuals = residuals_view(resp.function_values(), exp_ind);
1310   RealMatrix scaled_gradients =
1311     gradients_view(resp.function_gradients(), exp_ind);
1312 
1313   /*scaled_residuals.print(std::cout);
1314   scaled_gradients.print(std::cout);*/
1315 
1316   build_gradient_of_sum_square_residuals_from_function_data( scaled_gradients,
1317 							     scaled_residuals,
1318 							     ssr_gradient,
1319 							     asrv);
1320 }
1321 
1322 void ExperimentData::
build_gradient_of_sum_square_residuals_from_function_data(const RealMatrix & func_gradients,const RealVector & residuals,RealVector & ssr_gradient,const ShortArray & asrv)1323 build_gradient_of_sum_square_residuals_from_function_data(
1324 		 const RealMatrix &func_gradients,
1325                  const RealVector &residuals,
1326 		 RealVector &ssr_gradient, const ShortArray& asrv )
1327 {
1328   // This function assumes that residuals are r = ( approx - data )
1329   // NOT r = ( data - approx )
1330 
1331   // func_gradients is the transpose of the Jacobian of the functions
1332   int v, r, num_v = func_gradients.numRows(),
1333     num_residuals = residuals.length();
1334   for ( r=0; r<num_residuals; ++r )
1335     if ( (asrv[r] & 3) == 3 ) {
1336       Real res = residuals[r]; const Real* func_grad = func_gradients[r];
1337       for ( v=0; v<num_v; ++v )
1338 	ssr_gradient[v] += res * func_grad[v];
1339       // we compute gradient of sum square residuals divided by 2 (i.e. r'r/2),
1340       // where r has been scaled by sqrt(inv Gamma_d)
1341     }
1342 }
1343 
1344 void ExperimentData::
build_hessian_of_sum_square_residuals(const Response & resp,const ShortArray & asrv,RealSymMatrix & ssr_hessian)1345 build_hessian_of_sum_square_residuals( const Response& resp,
1346 				       const ShortArray& asrv,
1347 				       RealSymMatrix &ssr_hessian )
1348 {
1349   // initialize ssr_hessian to zero, prior to summing over set of experiments
1350   size_t exp_ind, num_v = resp.active_set().derivative_vector().size();
1351   if (ssr_hessian.numRows() != num_v) ssr_hessian.shape(num_v); // init to 0.
1352   else                                ssr_hessian = 0.;
1353   //size_t residual_resp_offset = 0;
1354   for (exp_ind = 0; exp_ind < numExperiments; ++exp_ind)
1355     // adds to ssr_hessian for each experiment
1356     build_hessian_of_sum_square_residuals_from_response( resp, asrv, exp_ind,
1357 							 ssr_hessian );
1358 }
1359 
1360 void ExperimentData::
build_hessian_of_sum_square_residuals_from_response(const Response & resp,const ShortArray & asrv,int exp_ind,RealSymMatrix & ssr_hessian)1361 build_hessian_of_sum_square_residuals_from_response( const Response& resp,
1362 						     const ShortArray& asrv,
1363 						     int exp_ind,
1364 						     RealSymMatrix &ssr_hessian)
1365 {
1366   RealVector scaled_residuals = residuals_view(resp.function_values(), exp_ind );
1367   RealMatrix scaled_gradients =
1368     gradients_view(resp.function_gradients(), exp_ind );
1369   RealSymMatrixArray scaled_hessians =
1370     hessians_view(resp.function_hessians(), exp_ind );
1371 
1372   /*scaled_residuals.print(std::cout);
1373   scaled_gradients.print(std::cout);
1374   Cout << "\nSize:" << scaled_hessians.size();
1375   for (size_t j=0;j<scaled_hessians.size();j++)
1376   scaled_hessians[j].print(std::cout);*/
1377 
1378   build_hessian_of_sum_square_residuals_from_function_data( scaled_hessians,
1379 							    scaled_gradients,
1380 							    scaled_residuals,
1381 							    ssr_hessian,
1382 							    asrv);
1383 }
1384 
1385 void ExperimentData::
build_hessian_of_sum_square_residuals_from_function_data(const RealSymMatrixArray & func_hessians,const RealMatrix & func_gradients,const RealVector & residuals,RealSymMatrix & ssr_hessian,const ShortArray & asrv)1386 build_hessian_of_sum_square_residuals_from_function_data(
1387 		 const RealSymMatrixArray &func_hessians,
1388 		 const RealMatrix &func_gradients,
1389                  const RealVector &residuals,
1390 		 RealSymMatrix &ssr_hessian,
1391 		 const ShortArray& asrv ){
1392   // This function assumes that residuals are r = ( approx - data )
1393   // NOT r = ( data - approx )
1394 
1395   // func_gradients is the transpose of the Jacobian of the functions
1396   int num_v = ssr_hessian.numRows(), num_residuals = residuals.length();
1397   for ( int k=0; k<num_v; k++ ) {
1398     for ( int j=0; j<=k; j++ ) {
1399       Real &hess_jk = ssr_hessian(j,k);
1400       for ( int i=0; i<num_residuals; i++ ) {
1401 	short asrv_i = asrv[i];
1402 	if (asrv_i & 2) hess_jk += func_gradients(j,i)*func_gradients(k,i);
1403 	if ( (asrv_i & 5) == 5 ) hess_jk += residuals[i]*func_hessians[i](j,k);
1404       }
1405       // we adopt convention and compute hessian of sum square residuals
1406       // multiplied by 1/2 e.g. r'r/2. Thus we do not need following
1407       // multiplication
1408       //hess_jk *= 2.;
1409     }
1410   }
1411 }
1412 
1413 
1414 /** In-place scaling of residual response by hyper-parameter multipliers */
1415 void ExperimentData::
scale_residuals(const RealVector & multipliers,unsigned short multiplier_mode,size_t num_calib_params,Response & residual_response) const1416 scale_residuals(const RealVector& multipliers, unsigned short multiplier_mode,
1417                 size_t num_calib_params, Response& residual_response) const
1418 {
1419 
1420   // NOTE: In most general case the index into multipliers is exp_ind
1421   // * num_response_groups + resp_ind (never per function ind, e.g.,
1422   // in a field)
1423   size_t num_hyper = multipliers.length();
1424   size_t total_resid = num_total_exppoints();
1425   const ShortArray& asv = residual_response.active_set_request_vector();
1426 
1427   switch (multiplier_mode) {
1428 
1429   case CALIBRATE_NONE:
1430     // no-op
1431     break;
1432 
1433   case CALIBRATE_ONE: {
1434     assert(multipliers.length() == 1);
1435     Real fn_scale = 1.0/sqrt(multipliers[0]);
1436     Real grad_scale = -0.5/multipliers[0];
1437     Real hess_scale = 0.75*std::pow(multipliers[0], -2.0);
1438 
1439     for (size_t i=0; i<total_resid; ++i) {
1440 
1441       if (asv[i] & 1) {
1442 	Real& fn_value = residual_response.function_value_view(i);
1443 	fn_value *= fn_scale;
1444       }
1445 
1446       if (asv[i] & 2) {
1447 	const Real& fn_value = residual_response.function_value(i);
1448 	RealVector fn_grad = residual_response.function_gradient_view(i);
1449 	// scale all of the gradient (including zeroed entries)
1450 	fn_grad *= fn_scale;
1451 	// then augment with gradient entry for the hyper-parameter
1452 	fn_grad[num_calib_params] = grad_scale * fn_value;
1453       }
1454 
1455       if (asv[i] & 4) {
1456 	const Real& fn_value = residual_response.function_value(i);
1457 	const RealVector fn_grad = residual_response.function_gradient_view(i);
1458 	RealSymMatrix fn_hess = residual_response.function_hessian_view(i);
1459 	// scale all of the Hessian (including zeroed entries)
1460 	fn_hess *= fn_scale;
1461 	// then augment with Hessian entries for the hyper-parameter
1462 	for (size_t j=0; j<num_calib_params; ++j)  {
1463 	  fn_hess(j, num_calib_params) = grad_scale * fn_grad[j];
1464 	  fn_hess(num_calib_params, j) = grad_scale * fn_grad[j];
1465 	}
1466 	fn_hess(num_calib_params, num_calib_params) = hess_scale * fn_value;
1467       }
1468 
1469     }
1470     break;
1471   }
1472 
1473   case CALIBRATE_PER_EXPER: case CALIBRATE_PER_RESP: case CALIBRATE_BOTH: {
1474     IntVector resid2mult_indices;
1475     resid2mult_map(multiplier_mode, resid2mult_indices);
1476 
1477     for (size_t i=0; i<total_resid; ++i) {
1478       // index of multiplier for this residual
1479       int mult_ind = resid2mult_indices[i];
1480       Real fn_scale = 1.0/sqrt(multipliers[mult_ind]);
1481       Real grad_scale = -0.5/multipliers[mult_ind];
1482       Real hess_scale = 0.75*std::pow(multipliers[mult_ind], -2.0);
1483 
1484       if (asv[i] & 1) {
1485 	Real& fn_value = residual_response.function_value_view(i);
1486 	fn_value *= fn_scale;
1487       }
1488 
1489       if (asv[i] & 2) {
1490 	const Real& fn_value = residual_response.function_value(i);
1491 	RealVector fn_grad = residual_response.function_gradient_view(i);
1492 	// scale all of the gradient (including zeroed entries)
1493 	fn_grad *= fn_scale;
1494 	// then augment with gradient entries for the hyper-parameters
1495 	// only 1 multiplier can affect a given residual
1496 	fn_grad[num_calib_params + mult_ind] = grad_scale * fn_value;
1497       }
1498 
1499       if (asv[i] & 4) {
1500 	const Real& fn_value = residual_response.function_value(i);
1501 	const RealVector fn_grad = residual_response.function_gradient_view(i);
1502 	RealSymMatrix fn_hess = residual_response.function_hessian_view(i);
1503 	// scale all of the Hessian (including zeroed entries)
1504 	fn_hess *= fn_scale;
1505 	// augment with Hessian entries for the hyper-parameters
1506 	for (size_t j=0; j<num_calib_params; ++j)  {
1507 	  fn_hess(j, num_calib_params + mult_ind) = grad_scale * fn_grad[j];
1508 	  fn_hess(num_calib_params + mult_ind, j) = grad_scale * fn_grad[j];
1509 	}
1510 	fn_hess(num_calib_params + mult_ind, num_calib_params + mult_ind) =
1511 	  hess_scale * fn_value;
1512       }
1513     }
1514     break;
1515   }
1516 
1517   default:
1518     // unknown mode
1519     Cerr << "\nError: unknown multiplier mode in scale_residuals().\n";
1520     abort_handler(-1);
1521     break;
1522   }
1523 }
1524 
1525 // BMA Reviewed
1526 /** Determinant of the total covariance used in inference, which has
1527     blocks mult_i * I * Cov_i. */
1528 Real ExperimentData::
cov_determinant(const RealVector & multipliers,unsigned short multiplier_mode) const1529 cov_determinant(const RealVector& multipliers,
1530                 unsigned short multiplier_mode) const
1531 {
1532   // initialize with product of experiment covariance determinants
1533   Real det = covarianceDeterminant;
1534   size_t total_resid = num_total_exppoints();
1535 
1536   switch (multiplier_mode) {
1537 
1538   case CALIBRATE_NONE:
1539     // no-op: multiplier is 1.0
1540     break;
1541 
1542   case CALIBRATE_ONE:
1543     assert(multipliers.length() == 1);
1544     det *= std::pow(multipliers[0], (double)total_resid);
1545     break;
1546 
1547   case CALIBRATE_PER_EXPER: case CALIBRATE_PER_RESP: case CALIBRATE_BOTH: {
1548     RealVector expand_mults;
1549     generate_multipliers(multipliers, multiplier_mode, expand_mults);
1550     // for each experiment, add contribution from mult: det(mult_i*I*Cov_i)
1551     // Don't need to exponentiate to block size as we're multiplying det by
1552     // each multiplier individiually
1553     for (size_t resid_ind = 0; resid_ind < total_resid; ++resid_ind)
1554       det *= expand_mults[resid_ind];
1555     break;
1556   }
1557 
1558   default:
1559     // unknown mode
1560     Cerr << "\nError: unknown multiplier mode in cov_determinant().\n";
1561     abort_handler(-1);
1562     break;
1563 
1564   } // switch
1565 
1566   return det;
1567 }
1568 
1569 /** Determinant of half the log of total covariance used in inference,
1570     which has blocks mult_i * I * Cov_i. */
1571 Real ExperimentData::
half_log_cov_determinant(const RealVector & multipliers,unsigned short multiplier_mode) const1572 half_log_cov_determinant(const RealVector& multipliers,
1573 			 unsigned short multiplier_mode) const
1574 {
1575   // initialize with sum of experiment covariance log determinants
1576   Real log_det = logCovarianceDeterminant;
1577   size_t total_resid = num_total_exppoints();
1578 
1579   switch (multiplier_mode) {
1580 
1581   case CALIBRATE_NONE:
1582     // no-op: multiplier is 1.0
1583     break;
1584 
1585   case CALIBRATE_ONE:
1586     assert(multipliers.length() == 1);
1587     log_det += std::log(multipliers[0]) * (double)total_resid;
1588     break;
1589 
1590   case CALIBRATE_PER_EXPER: case CALIBRATE_PER_RESP: case CALIBRATE_BOTH: {
1591     RealVector expand_mults;
1592     generate_multipliers(multipliers, multiplier_mode, expand_mults);
1593     // for each experiment, add contribution from mult: det(mult_i*I*Cov_i)
1594     for (size_t resid_ind = 0; resid_ind < total_resid; ++resid_ind)
1595       log_det += std::log(expand_mults[resid_ind]);
1596     break;
1597   }
1598 
1599   default:
1600     // unknown mode
1601     Cerr << "\nError: unknown multiplier mode in log_cov_determinant().\n";
1602     abort_handler(-1);
1603     break;
1604 
1605   } // switch
1606 
1607   return log_det / 2.0;
1608 }
1609 
1610 
1611 // BMA Reviewed
1612 /** Compute the gradient of scalar f(m) 0.5*log(det(mult*Cov))
1613     w.r.t. mults.  Since this is the only use case, we include the 0.5
1614     factor and perform an update in-place. */
1615 void ExperimentData::
half_log_cov_det_gradient(const RealVector & multipliers,unsigned short multiplier_mode,size_t hyper_offset,RealVector & gradient) const1616 half_log_cov_det_gradient(const RealVector& multipliers,
1617 			  unsigned short multiplier_mode, size_t hyper_offset,
1618 			  RealVector& gradient) const
1619 {
1620   switch (multiplier_mode) {
1621 
1622   case CALIBRATE_NONE:
1623     // no hyper-parameters being calibrated
1624     break;
1625 
1626   case CALIBRATE_ONE: {
1627     // This multiplier affects all functions
1628     assert(multipliers.length() == 1);
1629     Real total_resid = (Real) num_total_exppoints();
1630     gradient[hyper_offset] +=
1631       total_resid / multipliers[0] / 2.0;
1632     break;
1633   }
1634 
1635   case CALIBRATE_PER_EXPER: case CALIBRATE_PER_RESP: case CALIBRATE_BOTH: {
1636     SizetArray resid_per_mult = residuals_per_multiplier(multiplier_mode);
1637     assert(multipliers.length() == resid_per_mult.size());
1638     for (size_t i=0; i<multipliers.length(); ++i)
1639       gradient[hyper_offset + i] +=
1640 	((Real) resid_per_mult[i]) / multipliers[i] / 2.0;
1641     break;
1642   }
1643 
1644   }
1645 
1646 }
1647 
1648 // BMA Reviewed
1649 /** Compute the gradient of scalar f(m) log(det(mult*Cov)) w.r.t. mults */
1650 void ExperimentData::
half_log_cov_det_hessian(const RealVector & multipliers,unsigned short multiplier_mode,size_t hyper_offset,RealSymMatrix & hessian) const1651 half_log_cov_det_hessian(const RealVector& multipliers,
1652 			 unsigned short multiplier_mode, size_t hyper_offset,
1653 			 RealSymMatrix& hessian) const
1654 {
1655   switch (multiplier_mode) {
1656 
1657   case CALIBRATE_NONE:
1658     // no hyper-parameters being calibrated
1659     break;
1660 
1661   case CALIBRATE_ONE: {
1662     // This multiplier affects all functions
1663     assert(multipliers.length() == 1);
1664     Real total_resid = (Real) num_total_exppoints();
1665     hessian(hyper_offset, hyper_offset) +=
1666       -total_resid / multipliers[0] / multipliers[0] / 2.0;
1667     break;
1668   }
1669 
1670   case CALIBRATE_PER_EXPER: case CALIBRATE_PER_RESP: case CALIBRATE_BOTH: {
1671     SizetArray resid_per_mult = residuals_per_multiplier(multiplier_mode);
1672     assert(multipliers.length() == resid_per_mult.size());
1673     for (size_t i =0; i<multipliers.length(); ++i)
1674       hessian(hyper_offset, hyper_offset) +=
1675 	-((Real) resid_per_mult[i]) / multipliers[i] / multipliers[i] / 2.0;
1676     break;
1677   }
1678 
1679   }
1680 }
1681 
1682 
1683 // BMA Reviewed
1684 /** Calculate how many residuals each multiplier affects */
1685 SizetArray ExperimentData::
residuals_per_multiplier(unsigned short multiplier_mode) const1686 residuals_per_multiplier(unsigned short multiplier_mode) const
1687 {
1688   SizetArray resid_per_mult;
1689   switch (multiplier_mode) {
1690 
1691   case CALIBRATE_PER_EXPER: {
1692     resid_per_mult.resize(numExperiments, 0);
1693     for (size_t exp_ind=0; exp_ind < numExperiments; ++exp_ind) {
1694       size_t fns_this_exp = allExperiments[exp_ind].num_functions();
1695       resid_per_mult[exp_ind] = fns_this_exp;
1696     }
1697     break;
1698   }
1699 
1700   case CALIBRATE_PER_RESP: {
1701     size_t num_scalar = simulationSRD.num_scalar_primary();
1702     size_t num_fields = simulationSRD.num_field_response_groups();
1703     resid_per_mult.resize(num_scalar + num_fields, 0);
1704     // iterate scalar responses, then fields
1705     for (size_t s_ind  = 0; s_ind < num_scalar; ++s_ind)
1706       resid_per_mult[s_ind] += numExperiments;
1707 
1708     // each experiment can have different field lengths
1709     for (size_t exp_ind=0; exp_ind < numExperiments; ++exp_ind) {
1710       const IntVector& field_lens = allExperiments[exp_ind].field_lengths();
1711       for (size_t f_ind  = 0; f_ind < num_fields; ++f_ind)
1712 	resid_per_mult[num_scalar + f_ind] += field_lens[f_ind];
1713     }
1714     break;
1715   }
1716 
1717   case CALIBRATE_BOTH: {
1718     size_t num_scalar = simulationSRD.num_scalar_primary();
1719     size_t num_fields = simulationSRD.num_field_response_groups();
1720     size_t multiplier_offset = 0;
1721     resid_per_mult.resize(numExperiments*simulationSRD.num_response_groups(), 0);
1722     for (size_t exp_ind=0; exp_ind < numExperiments; ++exp_ind) {
1723 
1724       // iterate scalar responses, then fields
1725       for (size_t s_ind  = 0; s_ind < num_scalar; ++s_ind)
1726         resid_per_mult[multiplier_offset++] = 1;
1727 
1728       // each experiment can have different field lengths
1729       const IntVector& field_lens = allExperiments[exp_ind].field_lengths();
1730       for (size_t f_ind  = 0; f_ind < num_fields; ++f_ind) {
1731 	resid_per_mult[multiplier_offset++] = field_lens[f_ind];
1732       }
1733     }
1734     break;
1735   }
1736 
1737   }
1738 
1739   return resid_per_mult;
1740 }
1741 
1742 
1743 // BMA Reviewed
generate_multipliers(const RealVector & multipliers,unsigned short multiplier_mode,RealVector & expanded_multipliers) const1744 void ExperimentData::generate_multipliers(const RealVector& multipliers,
1745                                           unsigned short multiplier_mode,
1746                                           RealVector& expanded_multipliers) const
1747 {
1748   // in most cases, we won't call this function for NONE or ONE cases
1749   expanded_multipliers.resize(num_total_exppoints());
1750 
1751   switch (multiplier_mode) {
1752 
1753   case CALIBRATE_NONE:
1754     expanded_multipliers = 1.0;
1755     break;
1756 
1757   case CALIBRATE_ONE:
1758     assert(multipliers.length() == 1);
1759     expanded_multipliers = multipliers[0];
1760     break;
1761 
1762   case CALIBRATE_PER_EXPER: {
1763     assert(multipliers.length() == numExperiments);
1764     size_t resid_offset = 0;
1765     for (size_t exp_ind=0; exp_ind < numExperiments; ++exp_ind) {
1766       size_t fns_this_exp = allExperiments[exp_ind].num_functions();
1767       for (size_t fn_ind = 0; fn_ind < fns_this_exp; ++fn_ind)
1768         expanded_multipliers[resid_offset++] = multipliers[exp_ind];
1769     }
1770     break;
1771   }
1772 
1773   case CALIBRATE_PER_RESP: {
1774     assert(multipliers.length() == simulationSRD.num_response_groups());
1775     size_t num_scalar = simulationSRD.num_scalar_primary();
1776     size_t num_fields = simulationSRD.num_field_response_groups();
1777     size_t resid_offset = 0;
1778     for (size_t exp_ind=0; exp_ind < numExperiments; ++exp_ind) {
1779 
1780       // each response has a different multiplier, but they don't
1781       // differ across experiments
1782       size_t multiplier_offset = 0;
1783 
1784       // iterate scalar responses, then fields
1785       for (size_t s_ind  = 0; s_ind < num_scalar; ++s_ind)
1786         expanded_multipliers[resid_offset++] = multipliers[multiplier_offset++];
1787 
1788       // each experiment can have different field lengths
1789       const IntVector& field_lens = allExperiments[exp_ind].field_lengths();
1790       for (size_t f_ind  = 0; f_ind < num_fields; ++f_ind) {
1791         for (size_t i=0; i<field_lens[f_ind]; ++i)
1792           expanded_multipliers[resid_offset++] = multipliers[multiplier_offset];
1793         // only increment per-top-level response (each field has different mult)
1794         ++multiplier_offset;
1795       }
1796     }
1797     break;
1798   }
1799 
1800   case CALIBRATE_BOTH: {
1801     assert(multipliers.length() ==
1802            numExperiments*simulationSRD.num_response_groups());
1803     size_t num_scalar = simulationSRD.num_scalar_primary();
1804     size_t num_fields = simulationSRD.num_field_response_groups();
1805     size_t resid_offset = 0, multiplier_offset = 0;
1806     for (size_t exp_ind=0; exp_ind < numExperiments; ++exp_ind) {
1807 
1808       // don't reset the multiplier_offset; each exp, each resp has its own
1809 
1810       // iterate scalar responses, then fields
1811       for (size_t s_ind  = 0; s_ind < num_scalar; ++s_ind)
1812         expanded_multipliers[resid_offset++] = multipliers[multiplier_offset++];
1813 
1814       // each experiment can have different field lengths
1815       const IntVector& field_lens = allExperiments[exp_ind].field_lengths();
1816       for (size_t f_ind  = 0; f_ind < num_fields; ++f_ind) {
1817         for (size_t i=0; i<field_lens[f_ind]; ++i)
1818           expanded_multipliers[resid_offset++] = multipliers[multiplier_offset];
1819         // only increment per-top-level response (each field has different mult)
1820         ++multiplier_offset;
1821       }
1822     }
1823     break;
1824   }
1825 
1826   default:
1827     // unknown mode
1828     Cerr << "\nError: unknown multiplier mode in generate_multipliers().\n";
1829     abort_handler(-1);
1830     break;
1831 
1832   } // switch
1833 
1834 }
1835 
1836 
1837 /// return the index of the multiplier that affects each residual
resid2mult_map(unsigned short multiplier_mode,IntVector & resid2mult_indices) const1838 void ExperimentData::resid2mult_map(unsigned short multiplier_mode,
1839 				    IntVector& resid2mult_indices) const
1840 {
1841   // in most cases, we won't call this function for NONE or ONE cases
1842   resid2mult_indices.resize(num_total_exppoints());
1843 
1844   switch (multiplier_mode) {
1845 
1846   case CALIBRATE_NONE:
1847     Cerr << "\nError: cannot generate map for zero multipliers.\n";
1848     abort_handler(-1);
1849     break;
1850 
1851   case CALIBRATE_ONE:
1852     resid2mult_indices = 0;
1853     break;
1854 
1855   case CALIBRATE_PER_EXPER: {
1856     size_t resid_offset = 0;
1857     for (size_t exp_ind=0; exp_ind < numExperiments; ++exp_ind) {
1858       size_t fns_this_exp = allExperiments[exp_ind].num_functions();
1859       for (size_t fn_ind = 0; fn_ind < fns_this_exp; ++fn_ind)
1860         resid2mult_indices[resid_offset++] = exp_ind;
1861     }
1862     break;
1863   }
1864 
1865   case CALIBRATE_PER_RESP: {
1866     size_t num_scalar = simulationSRD.num_scalar_primary();
1867     size_t num_fields = simulationSRD.num_field_response_groups();
1868     size_t resid_offset = 0;
1869     for (size_t exp_ind=0; exp_ind < numExperiments; ++exp_ind) {
1870 
1871       // each response has a different multiplier, but they don't
1872       // differ across experiments
1873       size_t multiplier_offset = 0;
1874 
1875       // iterate scalar responses, then fields
1876       for (size_t s_ind  = 0; s_ind < num_scalar; ++s_ind)
1877         resid2mult_indices[resid_offset++] = multiplier_offset++;
1878 
1879       // each experiment can have different field lengths
1880       const IntVector& field_lens = allExperiments[exp_ind].field_lengths();
1881       for (size_t f_ind  = 0; f_ind < num_fields; ++f_ind) {
1882         for (size_t i=0; i<field_lens[f_ind]; ++i)
1883           resid2mult_indices[resid_offset++] = multiplier_offset;
1884         // only increment per-top-level response (each field has different mult)
1885         ++multiplier_offset;
1886       }
1887     }
1888     break;
1889   }
1890 
1891   case CALIBRATE_BOTH: {
1892     size_t num_scalar = simulationSRD.num_scalar_primary();
1893     size_t num_fields = simulationSRD.num_field_response_groups();
1894     size_t resid_offset = 0, multiplier_offset = 0;
1895     for (size_t exp_ind=0; exp_ind < numExperiments; ++exp_ind) {
1896 
1897       // don't reset the multiplier_offset; each exp, each resp has its own
1898 
1899       // iterate scalar responses, then fields
1900       for (size_t s_ind  = 0; s_ind < num_scalar; ++s_ind)
1901         resid2mult_indices[resid_offset++] = multiplier_offset++;
1902 
1903       // each experiment can have different field lengths
1904       const IntVector& field_lens = allExperiments[exp_ind].field_lengths();
1905       for (size_t f_ind  = 0; f_ind < num_fields; ++f_ind) {
1906         for (size_t i=0; i<field_lens[f_ind]; ++i)
1907           resid2mult_indices[resid_offset++] = multiplier_offset;
1908         // only increment per-top-level response (each field has different mult)
1909         ++multiplier_offset;
1910       }
1911     }
1912     break;
1913   }
1914 
1915   default:
1916     // unknown mode
1917     Cerr << "\nError: unknown multiplier mode in generate_multipliers().\n";
1918     abort_handler(-1);
1919     break;
1920 
1921   } // switch
1922 
1923 }
1924 
1925 
1926 StringArray ExperimentData::
hyperparam_labels(unsigned short multiplier_mode) const1927 hyperparam_labels(unsigned short multiplier_mode) const
1928 {
1929   String cm_prefix("CovMult");
1930   StringArray hp_labels;
1931 
1932   switch(multiplier_mode) {
1933 
1934   case CALIBRATE_NONE:
1935     break;
1936 
1937   case CALIBRATE_ONE:
1938     hp_labels.push_back(cm_prefix);
1939     break;
1940 
1941   case CALIBRATE_PER_EXPER:
1942     for (size_t exp_ind=0; exp_ind < numExperiments; ++exp_ind)
1943       hp_labels.
1944 	push_back(cm_prefix + "Exp" + std::to_string(exp_ind+1));
1945     break;
1946 
1947     // BMA TODO: Could use response labels here...
1948   case CALIBRATE_PER_RESP: {
1949     size_t num_resp = simulationSRD.num_scalar_primary() +
1950       simulationSRD.num_field_response_groups();
1951     for (size_t resp_ind=0; resp_ind < num_resp; ++resp_ind)
1952       hp_labels.
1953 	push_back(cm_prefix + "Resp" + std::to_string(resp_ind+1));
1954     break;
1955   }
1956 
1957   case CALIBRATE_BOTH: {
1958     size_t num_resp = simulationSRD.num_scalar_primary() +
1959       simulationSRD.num_field_response_groups();
1960     for (size_t exp_ind=0; exp_ind < numExperiments; ++exp_ind)
1961       for (size_t resp_ind=0; resp_ind < num_resp; ++resp_ind)
1962 	hp_labels.
1963 	  push_back(cm_prefix + "Exp" + std::to_string(exp_ind+1) +
1964 		    "Resp" + std::to_string(resp_ind+1));
1965     break;
1966   }
1967 
1968   default:
1969     Cerr << "\nError: unkown multiplier mode in hyperparam_labels().\n";
1970     abort_handler(-1);
1971 
1972   }
1973 
1974   return hp_labels;
1975 
1976 }
1977 
1978 
1979 }  // namespace Dakota
1980