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