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:       LeastSq
10 //- Description: Implementation code for the LeastSq class
11 //- Owner:       Mike Eldred
12 //- Checked by:
13 
14 #include "dakota_system_defs.hpp"
15 #include "dakota_data_io.hpp"
16 #include "DakotaModel.hpp"
17 #include "DataTransformModel.hpp"
18 #include "ScalingModel.hpp"
19 #include "WeightingModel.hpp"
20 #include "DakotaLeastSq.hpp"
21 #include "ParamResponsePair.hpp"
22 #include "PRPMultiIndex.hpp"
23 #include "ProblemDescDB.hpp"
24 #include "RecastModel.hpp"
25 #include "Teuchos_LAPACK.hpp"
26 #include "pecos_stat_util.hpp"
27 
28 static const char rcsId[]="@(#) $Id: DakotaLeastSq.cpp 7031 2010-10-22 16:23:52Z mseldre $";
29 
30 
31 using namespace std;
32 
33 namespace Dakota {
34   extern PRPCache data_pairs; // global container
35 
36 // initialization of static needed by RecastModel
37 LeastSq* LeastSq::leastSqInstance(NULL);
38 
39 /** This constructor extracts the inherited data for the least squares
40     branch and performs sanity checking on gradient and constraint
41     settings. */
LeastSq(ProblemDescDB & problem_db,Model & model,std::shared_ptr<TraitsBase> traits)42 LeastSq::LeastSq(ProblemDescDB& problem_db, Model& model, std::shared_ptr<TraitsBase> traits):
43   Minimizer(problem_db, model, traits),
44   // initial value from Minimizer as accounts for fields and transformations
45   numLeastSqTerms(numUserPrimaryFns),
46   weightFlag(!iteratedModel.primary_response_fn_weights().empty()),
47 				// TODO: wrong because of recasting layers
48   retrievedIterPriFns(false)
49 {
50   optimizationFlag  = false;
51 
52   bool err_flag = false;
53   // Check for proper function definition
54   if (model.primary_fn_type() != CALIB_TERMS) {
55     Cerr << "\nError: model must have calibration terms to apply least squares "
56 	 << "methods." << std::endl;
57     err_flag = true;
58   }
59   // Check for correct bit associated within methodName
60   if ( !(methodName & LEASTSQ_BIT) ) {
61     Cerr << "\nError: least squares bit not activated for method instantiation "
62 	 << "within LeastSq branch." << std::endl;
63     err_flag = true;
64   }
65 
66   if (err_flag)
67     abort_handler(-1);
68 
69   // Initialize a best variables instance; bestVariablesArray should
70   // be in calling context; so initialized before any recasts
71   bestVariablesArray.push_back(iteratedModel.current_variables().copy());
72 
73   // Wrap the iteratedModel in 0 -- 3 RecastModels, potentially resulting
74   // in weight(scale(data(model)))
75   if (calibrationDataFlag) {
76     data_transform_model();
77     // update local data sizes
78     numLeastSqTerms = numTotalCalibTerms;
79   }
80   if (scaleFlag)
81     scale_model();
82   if (weightFlag)
83     weight_model();
84 }
85 
86 
LeastSq(unsigned short method_name,Model & model,std::shared_ptr<TraitsBase> traits)87 LeastSq::LeastSq(unsigned short method_name, Model& model, std::shared_ptr<TraitsBase> traits):
88   Minimizer(method_name, model, traits),
89   numLeastSqTerms(numFunctions - numNonlinearConstraints),
90   weightFlag(false) //(!model.primary_response_fn_weights().empty()), // TO DO
91 {
92   bool err_flag = false;
93   // Check for proper function definition
94   if (numLeastSqTerms <= 0) {
95     Cerr << "\nError: number of least squares terms must be greater than zero "
96          << "for least squares methods." << std::endl;
97     err_flag = true;
98   }
99 
100   if (!model.primary_response_fn_weights().empty()) { // TO DO: support this
101     Cerr << "Error: on-the-fly LeastSq instantiations do not currently support "
102 	 << "residual weightings." << std::endl;
103     err_flag = true;
104   }
105 
106   if (err_flag)
107     abort_handler(-1);
108 
109   optimizationFlag  = false;
110 
111   // Initialize a best variables instance
112   bestVariablesArray.push_back(model.current_variables().copy());
113 }
114 
115 
116 /** Setup Recast for weighting model.  The weighting transformation
117     doesn't resize, and makes no vars, active set or secondary
118     mapping.  All indices are one-to-one mapped (no change in
119     counts). */
weight_model()120 void LeastSq::weight_model()
121 {
122   if (outputLevel >= DEBUG_OUTPUT)
123     Cout << "Initializing weighting transformation" << std::endl;
124 
125   // we assume sqrt(w_i) will be applied to each residual, therefore:
126   const RealVector& lsq_weights = iteratedModel.primary_response_fn_weights();
127   for (int i=0; i<lsq_weights.length(); ++i)
128     if (lsq_weights[i] < 0) {
129       Cerr << "\nError: Calibration term weights must be nonnegative. Specified "
130 	   << "weights are:\n" << lsq_weights << '\n';
131       abort_handler(-1);
132     }
133 
134   // TODO: pass sqrt to WeightingModel
135   iteratedModel.assign_rep(std::make_shared<WeightingModel>(iteratedModel));
136   ++myModelLayers;
137 }
138 
139 
140 /** Redefines default iterator results printing to include nonlinear
141     least squares results (residual terms and constraints). */
print_results(std::ostream & s,short results_state)142 void LeastSq::print_results(std::ostream& s, short results_state)
143 {
144   // Seq: print from native variables through to residuals as worked on:
145   //  * Best native parameters: LeastSq::print_results
146   //
147   //  * IF DataTransform: DataTransformModel::print_best_responses
148   //     - Best configs and native model responses (residuals or
149   //       functions, prior to data transform),
150   //     - [covariance-weighted] residuals
151   //     - residual norms
152   //
153   //    ELSE: Accounted for by final iterator space residuals
154   //
155   //  * Skip: scaled residuals
156   //
157   //  * Final iterator space residuals and residual norms (accounting
158   //    for scaling/weighting): DakotaLeastSq::print_results
159 
160   // TODO: Need to add residual norm to baselines
161 
162   // archive the single best point
163   size_t num_best = 1, best_ind = 0;
164   int eval_id;
165   //if(!calibrationDataFlag)
166   //  archive_allocate_residuals(num_best);
167 
168   // Print best calibration parameters.  Include any inactive
169   // variables unless they are used as experiment configuration
170   // variables since there's no "best" configuration.
171   const Variables& best_vars = bestVariablesArray.front();
172   if (expData.config_vars().size() == 0)
173     s << "<<<<< Best parameters          =\n" << best_vars;
174   else {
175     s << "<<<<< Best parameters (experiment config variables omitted) =\n";
176     best_vars.write(s, ACTIVE_VARS);
177   }
178 
179   // after post-run, responses should be back in user model space
180   // (no// scaling, or weighting); however TODO: they don't account
181   // for the multiple config cases.  They do contain the user-space
182   // constraints in all cases.
183   const RealVector& best_fns = bestResponseArray.front().function_values();
184 
185   if (calibrationDataFlag) {
186     // This will print the original model responses (possibly
187     // per-config) and the full set of data-transformed residuals,
188     // possibly scaled by sigma^-1/2.  This should be correct in
189     // interpolation cases as well.
190     std::shared_ptr<DataTransformModel> dt_model_rep =
191       std::static_pointer_cast<DataTransformModel>
192       (dataTransformModel.model_rep());
193     dt_model_rep->print_best_responses(s, best_vars,
194                                        bestResponseArray.front(), num_best, best_ind);
195   }
196   else {
197     // otherwise the residuals worked on are same size/type as the
198     // inbound Model, that is the original model had least squares
199     // terms and numLeastSqTerms == numTotalCalibTerms
200 
201     // only need clarify if transformations
202     if (scaleFlag || weightFlag)
203       s << "Original (as-posed) response:\n";
204     // always print the native residuals
205     print_residuals(numUserPrimaryFns, best_fns, RealVector(),
206 		    num_best, best_ind, s);
207   }
208 
209   // TODO: this may be per-experiment configuration, but there is no
210   // management of it in the problem formulation.  Need to explicitly
211   // disallow.
212   if (numNonlinearConstraints) {
213     s << "<<<<< Best constraint values   =\n";
214     write_data_partial(s, numUserPrimaryFns, numNonlinearConstraints, best_fns);
215   }
216 
217   // Print fn. eval. number where best occurred.  This cannot be catalogued
218   // directly because the optimizers track the best iterate internally and
219   // return the best results after iteration completion.  Therfore, perform a
220   // search in the data_pairs cache to extract the evalId for the best fn. eval.
221 
222   // TODO: for multi-config, there won't be a single best would have
223   // to check whether there are distinct configs
224 
225   // must search in the inbound Model's space (and even that may not
226   // suffice if there are additional recastings underlying this
227   // solver's Model) to find the function evaluation ID number
228   Model orig_model = original_model();
229   const String& interface_id = orig_model.interface_id();
230   // use asv = 1's
231   ActiveSet search_set(orig_model.response_size(), numContinuousVars);
232 
233   activeSet.request_values(1);
234   PRPCacheHIter cache_it = lookup_by_val(data_pairs,
235     iteratedModel.interface_id(), best_vars, activeSet);
236   if (cache_it == data_pairs.get<hashed>().end()) {
237     s << "<<<<< Best data not found in evaluation cache\n\n";
238     eval_id = 0;
239   }
240   else {
241     eval_id = cache_it->eval_id();
242     if (eval_id > 0)
243       s << "<<<<< Best data captured at function evaluation " << eval_id
244 	<< "\n\n";
245     else // should not occur
246       s << "<<<<< Best data not found in evaluations from current execution,"
247 	<< "\n      but retrieved from restart archive with evaluation id "
248 	<< -eval_id << "\n\n";
249   }
250 
251   // Print confidence intervals for each estimated parameter.
252   // These CIs are based on a linear approximation of the underlying
253   // nonlinear model, and are individual CIs, not joint CIs.
254   // Currently there is no weighting matrix, but that can be added.
255 
256   if (!confBoundsLower.empty() && !confBoundsUpper.empty()) {
257     // BMA: Not sure this warning is needed
258     if (expData.num_experiments() > 1)
259       s << "Warning: Confidence intervals may be inaccurate when "
260         << "num_experiments > 1\n";
261 
262     s << "Confidence Intervals on Calibrated Parameters:\n";
263 
264     StringMultiArrayConstView cv_labels
265       = iteratedModel.continuous_variable_labels();
266     for (size_t i = 0; i < numContinuousVars; i++)
267       s << std::setw(14) << cv_labels[i] << ": [ "
268 	<< setw(write_precision+6) << confBoundsLower[i] << ", "
269 	<< setw(write_precision+6) << confBoundsUpper[i] << " ]\n";
270   }
271 }
272 
273 
274 /** This function should be invoked (or reimplemented) by any derived
275     implementations of initialize_run() (which would otherwise hide
276     it). */
initialize_run()277 void LeastSq::initialize_run()
278 {
279   Minimizer::initialize_run();
280 
281   // pull any late updates into the RecastModel; may need to update
282   // from the underlying user model in case of hybrid methods, so
283   // should recurse through any local transformations
284   if (myModelLayers > 0)
285     iteratedModel.update_from_subordinate_model(myModelLayers-1);
286 
287   // Track any previous object instance in case of recursion.  Note that
288   // leastSqInstance and minimizerInstance must be tracked separately since
289   // the previous least squares method and previous minimizer could be
290   // different instances (e.g., for UQ of calibrated solutions with NPSOL
291   // for MPP search and NL2SOL for NLS, the previous minimizerInstance is
292   // NPSOL and the previous leastSqInstance is NULL).
293   prevLSqInstance = leastSqInstance;
294   leastSqInstance = this;
295 
296   retrievedIterPriFns = false;
297   bestIterPriFns.resize(0);
298 }
299 
300 
301 /** Implements portions of post_run specific to LeastSq for scaling
302     back to native variables and functions.  This function should be
303     invoked (or reimplemented) by any derived implementations of
304     post_run() (which would otherwise hide it). */
post_run(std::ostream & s)305 void LeastSq::post_run(std::ostream& s)
306 {
307   // BMA TODO: the lookups can't possibly retrieve config vars properly...
308   // DataTransformModel needs to reimplement db_lookup...
309 
310   // Moreover, can't store a single best_resp if config vars
311 
312   // BMA TODO: print_results review/cleanup now that we're storing
313   // native and transformed residuals...
314 
315   // Due to all the complicated use cases for residual recovery, we
316   // opt to lookup or re-evaluate the necessary models here, performing fn
317   // and grad evals separately, in case they are cached in different
318   // evals, and expecting duplicates or surrogate evals in most cases.
319 
320   if (bestVariablesArray.empty() || bestResponseArray.empty()) {
321     Cerr << "\nError: Empty calibration solution variables or response.\n";
322     abort_handler(METHOD_ERROR);
323   }
324   if (bestVariablesArray.size() > 1) {
325     Cout << "\nWarning: " << bestVariablesArray.size() << " calibration "
326 	 << "best parameter sets returned; expected only one." << std::endl;
327   }
328   if (bestResponseArray.size() > 1) {
329     Cout << "\nWarning: " << bestResponseArray.size() << " calibration "
330 	 << "best residual sets returned; expected only one." << std::endl;
331   }
332 
333   bool transform_flag = weightFlag || scaleFlag || calibrationDataFlag;
334 
335   // On entry:
336   //  * best_vars contains the iterator space (transformed) variables
337   //  * bestIterPriFns contains the residuals if available
338   //  * best_resp contains the iterator space (transformed) constraints
339 
340   // BMA REVIEW: best_vars / best_resp must be used judiciously in
341   // config var cases...
342 
343   Variables& best_vars = bestVariablesArray.front();
344   Response& best_resp = bestResponseArray.front();
345   RealVector best_fns = best_resp.function_values_view();
346 
347   // After recovery:
348   //  * iter_resp contains all native fn vals, constraints for return and
349   //    reporting
350   //  * iter_resp contains iterator residuals and gradient
351   //    for CI calculation and reporting (doesn't need constraints)
352 
353   // iterator space variables and response (deep copy only if needed)
354   Variables iter_vars(scaleFlag ? best_vars.copy() : best_vars);
355   Response iter_resp(transform_flag ? iteratedModel.current_response().copy() :
356 		     best_resp);
357   RealVector iter_fns = iter_resp.function_values_view();
358 
359   // Transform variables back to inbound model, before any potential lookup
360   if (scaleFlag) {
361     std::shared_ptr<ScalingModel> scale_model_rep =
362       std::static_pointer_cast<ScalingModel>(scalingModel.model_rep());
363     best_vars.continuous_variables
364       (scale_model_rep->cv_scaled2native(iter_vars.continuous_variables()));
365   }
366 
367   // also member retrievedIterPriFns (true for NL2SOL, NLSSOL, false for SNLL)
368   bool have_native_pri_fns = false;
369   bool have_iter_pri_grads = false;
370 
371   // If there are no problem transformations, populate (native) best
372   // from iterator-space residuals
373   if (!transform_flag && retrievedIterPriFns) {
374     copy_data_partial(bestIterPriFns, 0, (int)numLeastSqTerms, best_fns, 0);
375     have_native_pri_fns = true;
376   }
377 
378   // -----
379   // Retrieve in native/user space for best_resp and reporting
380   // -----
381 
382   // Always necessary for SNLLLeastSq recovery; other methods if transforms
383 
384   // TODO: In the multiple config case, this is going to retrieve
385   // whichever config was last evaluated, together with the optimal
386   // calibration parameters.  Could skip here since it's done again in
387   // DataTransformModel at print_results time.
388 
389   if (!have_native_pri_fns) {
390 
391     // Only need to retrieve functions; as constraints are always
392     // cached by the solver and unscaled if needed below
393     // BMA TODO: Don't really need this whole response object
394     // Could just cache the evalId here.. via cacheiter.
395     Model orig_model = original_model();
396     Response found_resp(orig_model.current_response().copy());
397     ActiveSet search_set(found_resp.active_set());
398     search_set.request_values(0);
399     for (size_t i=0; i<numUserPrimaryFns; ++i)
400       search_set.request_value(1, i);
401     // The receiving response must have the right ASV when using this
402     // lookup signature
403     found_resp.active_set(search_set);
404     have_native_pri_fns |= orig_model.db_lookup(best_vars, search_set,
405 						found_resp);
406 
407     if (have_native_pri_fns)
408       copy_data_partial(found_resp.function_values(), 0, (int)numUserPrimaryFns,
409 			best_fns, 0);
410     else
411       // This can occur in model calibration under uncertainty using nested
412       // models, or surrogate models so make this non-fatal.
413       Cout << "Warning: couldn't recover final least squares terms from "
414 	   << "evaluation database."
415 	   << std::endl;
416   }
417 
418   // -----
419   // Retrieve in transformed space for CIs and reporting
420   // -----
421   if (!retrievedIterPriFns) {
422 
423     // This lookup should only be necessary for SNLLLeastSq and will
424     // fail if there is a data transformation, so will be caught by
425     // re-evaluate below.
426 
427     // (Rather than transforming the found native function values, keep
428     // it simpler and lookup the iterator space responses too.)
429 
430     // Only need to retrieve functions; as constraints are always
431     // cached by the solver and unscaled if needed below
432     // BMA TODO: Don't really need this whole response object
433     // Could just cache the evalId here.. via cacheiter.
434     Response found_resp(iteratedModel.current_response().copy());
435     ActiveSet search_set(found_resp.active_set());
436     search_set.request_values(0);
437     for (size_t i=0; i<numLeastSqTerms; ++i)
438       search_set.request_value(1, i);
439     // The receiving response must have the right ASV when using this
440     // lookup signature
441     found_resp.active_set(search_set);
442     retrievedIterPriFns |= iteratedModel.db_lookup(iter_vars, search_set,
443 						   found_resp);
444 
445     if (retrievedIterPriFns)
446       copy_data_partial(found_resp.function_values(), 0, (int)numLeastSqTerms,
447 			iter_fns, 0);
448     else
449       Cout << "Warning: couldn't recover final (transformed) least squares "
450 	   << "terms from\n          evaluation database."
451 	   << std::endl;
452   }
453 
454   // Confidence intervals calculations are unconditional and no solver
455   // stores the gradients.  Try to lookup first.
456   // We want the gradient of the transformed residuals
457   // w.r.t. the original variables, so use the iteratedModel
458   Response found_resp(iteratedModel.current_response().copy());
459   ActiveSet search_set(found_resp.active_set());
460   search_set.request_values(0);
461   for (size_t i=0; i<numLeastSqTerms; ++i)
462     search_set.request_value(2, i);
463   found_resp.active_set(search_set);
464   have_iter_pri_grads |= iteratedModel.db_lookup(iter_vars, search_set,
465 						 found_resp);
466 
467   if (have_iter_pri_grads) {
468     RealMatrix found_gradients(Teuchos::View,
469 			       found_resp.function_gradients(),
470 			       (int)numContinuousVars, (int)numLeastSqTerms);
471     RealMatrix iter_gradients(Teuchos::View, iter_resp.function_gradients(),
472 			      (int)numContinuousVars, (int)numLeastSqTerms);
473     iter_gradients.assign(found_gradients);
474   }
475   else if ( outputLevel > NORMAL_OUTPUT)
476     Cout << "Info: Couldn't recover residual gradient for confidence interval "
477 	 << "calculation; will attempt re-evaluation." << std::endl;
478 
479   // If needed, evaluate the function values separately from the
480   // gradients so more likely to hit a duplicate if they're stored in
481   // different evals.  These evals will also aid in recovery of
482   // surrogate-based LSQ
483   if (!retrievedIterPriFns || !have_native_pri_fns) {
484 
485     // BMA TODO: Be finer grained and eval original vs. iterated... if
486     // only need one or other:
487     // if (!iter_fns)
488     //    eval iterated model; save iter_fns and conditionally save native_fns
489     // else if (!native_fns)
490     //    eval original model; save native_fns
491     iteratedModel.continuous_variables(iter_vars.continuous_variables());
492     activeSet.request_values(0);
493     for (size_t i=0; i<numLeastSqTerms; ++i)
494       activeSet.request_value(1, i);
495     iteratedModel.evaluate(activeSet);
496 
497     if (!retrievedIterPriFns) {
498       copy_data_partial(iteratedModel.current_response().function_values(),
499 			0, (int)numLeastSqTerms, iter_fns, 0);
500       retrievedIterPriFns = true;
501     }
502 
503     if (!have_native_pri_fns) {
504       copy_data_partial(original_model().current_response().function_values(),
505 			0, (int)numUserPrimaryFns, best_fns, 0);
506       have_native_pri_fns = true;
507     }
508   }
509 
510   // Can't evaluate gradient with vendor-computed gradients (so no CIs)
511   if (!have_iter_pri_grads && !vendorNumericalGradFlag) {
512     // For now, we populate iter_resp, then partially undo the scaling in
513     // get_confidence_intervals.  Could instead get the eval from
514     // original_model and transform it up.
515     iteratedModel.continuous_variables(iter_vars.continuous_variables());
516     activeSet.request_values(0);
517     for (size_t i=0; i<numLeastSqTerms; ++i)
518       activeSet.request_value(2, i);
519     iteratedModel.evaluate(activeSet);
520 
521     RealMatrix eval_gradients(Teuchos::View,
522 			      iteratedModel.current_response().function_gradients(),
523 			      (int)numContinuousVars, (int)numLeastSqTerms);
524     RealMatrix iter_gradients(Teuchos::View, iter_resp.function_gradients(),
525 			      (int)numContinuousVars, (int)numLeastSqTerms);
526     iter_gradients.assign(eval_gradients);
527 
528     have_iter_pri_grads = true;
529 
530   }
531 
532   // All derived solvers return constraints in best_resp; unscale
533   // in-place if needed
534   // BMA TODO: constrained LSQ with scaling test
535   if (scaleFlag && numNonlinearConstraints > 0) {
536     std::shared_ptr<ScalingModel> scale_model_rep =
537       std::static_pointer_cast<ScalingModel>(scalingModel.model_rep());
538     RealVector best_fns = best_resp.function_values_view();
539     // only requesting scaling of constraints, so no need for variable Jacobian
540     activeSet.request_values(1);
541     scale_model_rep->
542       secondary_resp_scaled2native(best_resp.function_values(),
543 				   activeSet.request_vector(),
544 				   best_fns);
545   }
546 
547   // confidence intervals require
548   //  - fully transformed residuals
549   //  - native vars for calculating intervals
550   //  - Jacobian: transformed resid / native vars
551   //    (or fully transformed for un-transforming)
552   get_confidence_intervals(best_vars, iter_resp);
553 
554   Minimizer::post_run(s);
555 }
556 
557 
558 /** Calculate individual confidence intervals for each parameter,
559     based on a linear approximation of the nonlinear model. native_cv
560     are needed for transformations and final reporting.  iter_resp
561     must contain the final differenced, scaled, weighted residuals and gradients. */
get_confidence_intervals(const Variables & native_vars,const Response & iter_resp)562 void LeastSq::get_confidence_intervals(const Variables& native_vars,
563 				       const Response& iter_resp)
564 {
565   // TODO: Fix CIs for interpolation and multi-experiment cases.  For
566   // simple multi-experiment cases, can just use the model derivatives
567   // without replication.  Concern is with singular aggregate Jacobian
568   // matrix J.
569 
570   // Confidence intervals should be based on weighted/scaled iterator
571   // residuals (since that was the nonlinear regression problem
572   // formulation), but original user parameters, so must use
573   // d(scaled_residual) / d(native_vars).
574   if (vendorNumericalGradFlag) {
575     Cout << "\nWarning: Confidence Interval calculations are not available"
576          << "\n         for vendor numerical gradients.\n\n";
577     return;
578   }
579   if (numLeastSqTerms < numContinuousVars) {
580     Cout << "\nWarning: Confidence Interval calculations are not available"
581          << "\n         when number of residuals is less than number of"
582 	 << "\n         variables.\n\n";
583     return;
584   }
585 
586   // recover the iterator space residuals, hopefully via duplicate detection
587   const RealVector& resid_star = iter_resp.function_values();
588 
589   // first calculate the estimate of sigma-squared.
590   Real dof = std::max(double(numLeastSqTerms-numContinuousVars), 1.);
591   Real sse_total = 0.;
592   for (int i=0; i<numLeastSqTerms; i++)
593     sse_total += (resid_star[i]*resid_star[i]);
594   Real sigma_sq_hat = sse_total/dof;
595 
596   // We are using a formulation where the standard error of the
597   // parameter vector is calculated as the square root of the diagonal
598   // elements of sigma_sq_hat*inverse(J'J), where J is the matrix
599   // of derivatives of the model with respect to the parameters,
600   // and J' is the transpose of J.  Insteaad of calculating J'J and
601   // explicitly taking the inverse, we are using a QR decomposition,
602   // where J=QR, and inv(J'J)= inv((QR)'QR)=inv(R'Q'QR)=inv(R'R)=
603   // inv(R)*inv(R').
604   // J must be in column order for the Fortran call
605   Teuchos::LAPACK<int, Real> la;
606   int info;
607   int M = numLeastSqTerms;
608   int N = numContinuousVars;
609   int LDA = numLeastSqTerms;
610   double *tau, *work;
611   double* Jmatrix = new double[numLeastSqTerms*numContinuousVars];
612 
613   // With scaling, the iter_resp will potentially contain
614   // d(scaled_resp) / d(scaled_params).
615   //
616   // When parameters are scaled, have to apply the variable
617   // transformation Jacobian to get to
618   // d(scaled_resp) / d(native_params) =
619   //   d(scaled_resp) / d(scaled_params) * d(scaled_params) / d(native_params)
620 
621   // envelope to hold the either unscaled or iterator response
622   Response ultimate_resp = scaleFlag ? iter_resp.copy() : iter_resp;
623   if (scaleFlag) {
624     std::shared_ptr<ScalingModel> scale_model_rep =
625       std::static_pointer_cast<ScalingModel>(scalingModel.model_rep());
626     bool unscale_resp = false;
627     scale_model_rep->response_modify_s2n(native_vars, iter_resp,
628 					 ultimate_resp, 0, numLeastSqTerms,
629 					 unscale_resp);
630   }
631   const RealMatrix& fn_grads = ultimate_resp.function_gradients_view();
632 
633   // BMA: TODO we don't need to transpose this matrix...
634   for (int i=0; i<numLeastSqTerms; i++)
635     for (int j=0; j<numContinuousVars; j++)
636       Jmatrix[(j*numLeastSqTerms)+i]=fn_grads(j,i);
637 
638   // This is the QR decomposition, the results are returned in J
639   work = new double[N + std::min(M,N)];
640   tau = work + N;
641 
642   la.GEQRF(M,N,Jmatrix,LDA,tau,work,N,&info);
643   bool error_flag = info;
644   delete[] work;
645 
646   // if you add these three lines right after DGEQRF, then the upper triangular
647   // part of Jmatrix will then contain Rinverse
648 
649   char uplo = 'U'; // upper triangular
650   char unitdiag = 'N'; // non-unit trangular
651   la.TRTRI(uplo, unitdiag, N, Jmatrix, LDA, &info);
652   error_flag &= info;
653 
654   if (error_flag) {
655     Cout << "\nWarning: LAPACK error computing confidence intervals.\n\n";
656     return;
657   }
658 
659   RealVector standard_error(numContinuousVars);
660   RealVector diag(numContinuousVars, true);
661   for (int i=0; i<numContinuousVars; i++) {
662     for (int j=i; j<numContinuousVars; j++)
663       diag(i) += Jmatrix[j*numLeastSqTerms+i]*Jmatrix[j*numLeastSqTerms+i];
664     standard_error[i] = std::sqrt(diag(i)*sigma_sq_hat);
665   }
666   delete[] Jmatrix;
667 
668   confBoundsLower.sizeUninitialized(numContinuousVars);
669   confBoundsUpper.sizeUninitialized(numContinuousVars);
670 
671   Pecos::students_t_dist t_dist(dof);
672   Real tdist =  bmth::quantile(t_dist,0.975);
673 
674   const RealVector& native_cv = native_vars.continuous_variables();
675   for (int j=0; j<numContinuousVars; j++) {
676     confBoundsLower[j] = native_cv[j] - tdist * standard_error[j];
677     confBoundsUpper[j] = native_cv[j] + tdist * standard_error[j];
678   }
679 }
680 
archive_best_results()681 void LeastSq::archive_best_results() {
682   Minimizer::archive_best_results();
683   if(!resultsDB.active() || expData.num_experiments() > 1) return;
684 
685   StringMultiArrayConstView cv_labels
686     = iteratedModel.continuous_variable_labels();
687   DimScaleMap scales;
688   scales.emplace(0, StringScale("variables", cv_labels));
689   scales.emplace(1, StringScale("bounds", {"lower", "upper"}));
690   resultsDB.allocate_matrix(run_identifier(), {String("confidence_intervals")},
691       ResultsOutputType::REAL, confBoundsLower.length(),2, scales);
692   resultsDB.insert_into(run_identifier(), {String("confidence_intervals")},
693       confBoundsLower, 0, false);
694   resultsDB.insert_into(run_identifier(), {String("confidence_intervals")},
695       confBoundsUpper, 1, false);
696 
697 }
698 
699 } // namespace Dakota
700