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:	 NonDQuadrature
10 //- Description: Implementation code for NonDQuadrature class
11 //- Owner:       Mike Eldred
12 //- Revised by:
13 //- Version:
14 
15 #include "dakota_data_types.hpp"
16 #include "dakota_system_defs.hpp"
17 #include "NonDQuadrature.hpp"
18 #include "DakotaModel.hpp"
19 #include "DiscrepancyCorrection.hpp"
20 #include "ProblemDescDB.hpp"
21 #include "PolynomialApproximation.hpp"
22 #include "LHSDriver.hpp"
23 #include "dakota_stat_util.hpp"
24 
25 static const char rcsId[]="@(#) $Id: NonDQuadrature.cpp,v 1.57 2004/06/21 19:57:32 mseldre Exp $";
26 
27 
28 namespace Dakota {
29 
30 /** This constructor is called for a standard letter-envelope iterator
31     instantiation.  In this case, set_db_list_nodes has been called
32     and probDescDB can be queried for settings from the method
33     specification.  It is not currently used, as there is not yet a
34     separate nond_quadrature method specification. */
NonDQuadrature(ProblemDescDB & problem_db,Model & model)35 NonDQuadrature::NonDQuadrature(ProblemDescDB& problem_db, Model& model):
36   NonDIntegration(problem_db, model),
37   quadOrderSpec(probDescDB.get_ushort("method.nond.quadrature_order")),
38   numSamples(0), quadMode(FULL_TENSOR)
39 {
40   // initialize the numerical integration driver
41   numIntDriver = Pecos::IntegrationDriver(Pecos::QUADRATURE);
42   tpqDriver = std::static_pointer_cast<Pecos::TensorProductDriver>
43     (numIntDriver.driver_rep());
44 
45   //check_variables(x_dist.random_variables());
46   // TO DO: create a ProbabilityTransformModel, if needed
47   const Pecos::MultivariateDistribution& u_dist
48     = model.multivariate_distribution();
49 
50   short refine_type
51     = probDescDB.get_short("method.nond.expansion_refinement_type");
52   short refine_control
53     = probDescDB.get_short("method.nond.expansion_refinement_control");
54   short refine_metric = (refine_control) ? Pecos::COVARIANCE_METRIC :
55     Pecos::NO_METRIC;
56   short refine_stats  = (refine_control) ? Pecos::ACTIVE_EXPANSION_STATS :
57     Pecos::NO_EXPANSION_STATS;
58   short nest_override = probDescDB.get_short("method.nond.nesting_override");
59   nestedRules = ( nest_override == Pecos::NESTED ||
60 		  ( refine_type && nest_override != Pecos::NON_NESTED ) );
61   Pecos::ExpansionConfigOptions ec_options(Pecos::QUADRATURE,
62     probDescDB.get_short("method.nond.expansion_basis_type"),
63     iteratedModel.correction_type(),
64     probDescDB.get_short("method.nond.multilevel_discrepancy_emulation"),
65     outputLevel, probDescDB.get_bool("method.variance_based_decomp"),
66     probDescDB.get_ushort("method.nond.vbd_interaction_order"),
67     refine_control, refine_metric, refine_stats,
68     probDescDB.get_int("method.nond.max_refinement_iterations"),
69     probDescDB.get_int("method.nond.max_solver_iterations"), convergenceTol,
70     probDescDB.get_ushort("method.soft_convergence_limit"));
71 
72   bool piecewise_basis = (probDescDB.get_bool("method.nond.piecewise_basis") ||
73 			  refine_type == Pecos::H_REFINEMENT);
74   bool use_derivs = probDescDB.get_bool("method.derivative_usage");
75   bool equidist_rules = true; // NEWTON_COTES pts for piecewise interpolants
76   Pecos::BasisConfigOptions bc_options(nestedRules, piecewise_basis,
77 				       equidist_rules, use_derivs);
78 
79   tpqDriver->initialize_grid(u_dist, ec_options, bc_options);
80   tpqDriver->initialize_grid_parameters(u_dist);
81 
82   reset(); // init_dim_quad_order() uses integrationRules from initialize_grid()
83 
84   maxEvalConcurrency *= tpqDriver->grid_size();
85 }
86 
87 
88 /** This alternate constructor is used for on-the-fly generation and
89     evaluation of numerical quadrature points. */
90 NonDQuadrature::
NonDQuadrature(Model & model,unsigned short quad_order,const RealVector & dim_pref,short driver_mode)91 NonDQuadrature(Model& model, unsigned short quad_order,
92 	       const RealVector& dim_pref, short driver_mode):
93   NonDIntegration(QUADRATURE_INTEGRATION, model, dim_pref), nestedRules(false),
94   quadOrderSpec(quad_order), numSamples(0), quadMode(FULL_TENSOR)
95 {
96   // initialize the numerical integration driver
97   numIntDriver = Pecos::IntegrationDriver(Pecos::QUADRATURE);
98   tpqDriver = std::static_pointer_cast<Pecos::TensorProductDriver>
99     (numIntDriver.driver_rep());
100 
101   tpqDriver->mode(driver_mode);
102 
103   // local natafTransform not yet updated: x_ran_vars would have to be passed in
104   // from NonDExpansion if check_variables() needed to be called here.  Instead,
105   // it is deferred until run time in NonDIntegration::core_run().
106   //check_variables(x_ran_vars);
107 }
108 
109 
110 /** This alternate constructor is used for on-the-fly generation and
111     evaluation of filtered tensor quadrature points. */
112 NonDQuadrature::
NonDQuadrature(Model & model,unsigned short quad_order,const RealVector & dim_pref,short driver_mode,int num_filt_samples)113 NonDQuadrature(Model& model, unsigned short quad_order,
114 	       const RealVector& dim_pref, short driver_mode,
115 	       int num_filt_samples):
116   NonDIntegration(QUADRATURE_INTEGRATION, model, dim_pref),
117   nestedRules(false), // minimize zeros introduced into Vandermonde matrix
118   quadOrderSpec(quad_order), quadMode(FILTERED_TENSOR),
119   numSamples(num_filt_samples)
120 {
121   // initialize the numerical integration driver
122   numIntDriver = Pecos::IntegrationDriver(Pecos::QUADRATURE);
123   tpqDriver = std::static_pointer_cast<Pecos::TensorProductDriver>
124     (numIntDriver.driver_rep());
125 
126   tpqDriver->mode(driver_mode);
127 
128   // local natafTransform not yet updated: x_ran_vars would have to be passed in
129   // from NonDExpansion if check_variables() needed to be called here.  Instead,
130   // it is deferred until run time in NonDIntegration::core_run().
131   //check_variables(x_ran_vars);
132 }
133 
134 
135 /** This alternate constructor is used for on-the-fly generation and
136     evaluation of random sampling from a tensor quadrature multi-index. */
137 NonDQuadrature::
NonDQuadrature(Model & model,unsigned short quad_order,const RealVector & dim_pref,short driver_mode,int num_sub_samples,int seed)138 NonDQuadrature(Model& model, unsigned short quad_order,
139 	       const RealVector& dim_pref, short driver_mode,
140 	       int num_sub_samples, int seed):
141   NonDIntegration(QUADRATURE_INTEGRATION, model, dim_pref),
142   nestedRules(false), // minimize zeros introduced into Vandermonde matrix
143   quadOrderSpec(quad_order), quadMode(RANDOM_TENSOR),
144   numSamples(num_sub_samples), randomSeed(seed)
145 {
146   // initialize the numerical integration driver
147   numIntDriver = Pecos::IntegrationDriver(Pecos::QUADRATURE);
148   tpqDriver = std::static_pointer_cast<Pecos::TensorProductDriver>
149     (numIntDriver.driver_rep());
150 
151   tpqDriver->mode(driver_mode);
152 
153   // local natafTransform not yet updated: x_ran_vars would have to be passed in
154   // from NonDExpansion if check_variables() needed to be called here.  Instead,
155   // it is deferred until run time in NonDIntegration::core_run().
156   //check_variables(x_ran_vars);
157 }
158 
159 
~NonDQuadrature()160 NonDQuadrature::~NonDQuadrature()
161 { }
162 
163 
164 /** Used in combination with alternate NonDQuadrature constructor. */
165 void NonDQuadrature::
initialize_grid(const std::vector<Pecos::BasisPolynomial> & poly_basis)166 initialize_grid(const std::vector<Pecos::BasisPolynomial>& poly_basis)
167 {
168   tpqDriver->initialize_grid(poly_basis);
169   tpqDriver->
170     initialize_grid_parameters(iteratedModel.multivariate_distribution());
171 
172   switch (quadMode) {
173   case FULL_TENSOR:
174     // infer nestedRules
175     for (size_t i=0; i<numContinuousVars; ++i) {
176       short rule = poly_basis[i].collocation_rule();
177       // Distinguish between rules that *support* vs. *require* nesting, since
178       // TPQ should allow unrestricted order specifications where supported.
179       // > GENZ_KEISTER and GAUSS_PATTERSON are only defined as nested rules
180       // > NEWTON_COTES, CLENSHAW_CURTIS, FEJER2 support nesting but also allow
181       //   arbitrary order specification
182       if (rule == Pecos::GENZ_KEISTER || rule == Pecos::GAUSS_PATTERSON)// ||
183 	//rule == Pecos::NEWTON_COTES || rule == Pecos::CLENSHAW_CURTIS ||
184 	//rule == Pecos::FEJER2)
185 	{ nestedRules = true; break; }
186     }
187     reset();
188     maxEvalConcurrency *= tpqDriver->grid_size();
189     break;
190   case FILTERED_TENSOR:
191     // nested overrides not currently part of tensor regression spec
192     //for () if () { nestedRules = true; break; }
193     update(); // compute min quad order reqd for numSamples
194     maxEvalConcurrency *= numSamples;
195     break;
196   case RANDOM_TENSOR:
197     // nested overrides not currently part of tensor regression spec
198     //for () if () { nestedRules = true; break; }
199     reset();  // propagate updated settings to tpqDriver
200     update(); // enforce min quad order constraints
201     maxEvalConcurrency *= numSamples;
202     break;
203   }
204 }
205 
206 
207 void NonDQuadrature::
initialize_dimension_quadrature_order(unsigned short quad_order_spec,const RealVector & dim_pref_spec)208 initialize_dimension_quadrature_order(unsigned short quad_order_spec,
209 				      const RealVector& dim_pref_spec)
210 {
211   // Update ref_quad_order from quad_order_spec and dim_pref_spec
212   UShortArray ref_quad_order;
213   dimension_preference_to_anisotropic_order(quad_order_spec,   dim_pref_spec,
214 					    numContinuousVars, ref_quad_order);
215 
216   // Update Pecos::TensorProductDriver::quadOrder from ref_quad_order
217   tpqDriver->reference_quadrature_order(ref_quad_order, nestedRules);
218 }
219 
220 
221 void NonDQuadrature::
compute_minimum_quadrature_order(size_t min_samples,const RealVector & dim_pref)222 compute_minimum_quadrature_order(size_t min_samples, const RealVector& dim_pref)
223 {
224   UShortArray ref_quad_order(numContinuousVars, 1);
225   // compute minimal order tensor grid with at least min_samples points
226   if (dim_pref.empty()) // isotropic tensor grid
227     while (tpqDriver->grid_size() < min_samples)
228       increment_grid(ref_quad_order);
229   else                   // anisotropic tensor grid
230     while (tpqDriver->grid_size() < min_samples)
231       increment_grid_preference(dim_pref, ref_quad_order);
232 }
233 
234 
get_parameter_sets(Model & model)235 void NonDQuadrature::get_parameter_sets(Model& model)
236 {
237   // capture any distribution parameter insertions
238   if (subIteratorFlag)
239     tpqDriver->initialize_grid_parameters(model.multivariate_distribution());
240 
241   // Precompute quadrature rules (e.g., by defining maximal order for
242   // NumGenOrthogPolynomial::solve_eigenproblem()):
243   tpqDriver->precompute_rules(); // efficiency optimization
244 
245   size_t i, j, num_quad_points = tpqDriver->grid_size();
246   const Pecos::UShortArray& quad_order = tpqDriver->quadrature_order();
247   Cout << "\nNumber of Gauss points per variable: { ";
248   for (i=0; i<numContinuousVars; ++i)
249     Cout << quad_order[i] << ' ';
250   Cout << "}\n";
251 
252   switch (quadMode) {
253   // --------------------------------
254   // Tensor quadrature (default mode)
255   // --------------------------------
256   case FULL_TENSOR:
257     Cout << "Total number of integration points: " << num_quad_points << '\n';
258     // Compute the tensor-product grid and store in allSamples
259     tpqDriver->compute_grid(allSamples);
260     if (outputLevel > NORMAL_OUTPUT)
261       print_points_weights("dakota_quadrature_tabular.dat");
262     break;
263   // ------------------------------------------------------------------------
264   // Probabilistic collocation from a tensor grid filtered by product weights
265   // ------------------------------------------------------------------------
266   case FILTERED_TENSOR:
267     Cout << "Filtered to " << numSamples
268 	 << " samples with max product weight.\n";
269     // Compute the tensor-product grid and store in allSamples
270     tpqDriver->compute_grid(allSamples);
271     // retain a subset of the minimal order tensor grid
272     filter_parameter_sets();
273     break;
274   // ----------------------------------------------------------------------
275   // Probabilistic collocation from random sampling of a tensor multi-index
276   // ----------------------------------------------------------------------
277   case RANDOM_TENSOR: {
278     Cout << numSamples << " samples drawn randomly from tensor grid.\n";
279     allSamples.shapeUninitialized(numContinuousVars, numSamples);
280 
281     const Pecos::UShortArray& lev_index = tpqDriver->level_index();
282     tpqDriver->assign_1d_collocation_points_weights(quad_order, lev_index);
283     const Pecos::Real3DArray& colloc_pts_1d
284       = tpqDriver->collocation_points_1d();
285 
286     // prevent case of all lhs_const variables, which is an lhs_prep error
287     bool lhs_error_trap = true;
288     for (i=0; i<numContinuousVars; ++i)
289       if (quad_order[i] > 1)
290 	{ lhs_error_trap = false; break; }
291     if (lhs_error_trap) { // only 1 point from which to draw samples
292       for (i=0; i<numContinuousVars; ++i) {
293 	Real samp_i = colloc_pts_1d[0][i][0]; // all levels,indices = 0
294 	for (j=0; j<numSamples; ++j)
295 	  allSamples(i,j) = samp_i;
296       }
297     }
298     else { // sample randomly from the tensor multi-index
299       IntVector index_l_bnds(numContinuousVars), // init to 0
300                 index_u_bnds(numContinuousVars, false);
301       for (j=0; j<numContinuousVars; ++j)
302 	index_u_bnds[j] = quad_order[j] - 1;
303       IntMatrix sorted_samples;
304       // generate unique samples since redundancy degrades the conditioning
305       Pecos::LHSDriver lhs("lhs", IGNORE_RANKS, false);
306       if (!randomSeed) randomSeed = generate_system_seed();
307       lhs.seed(randomSeed);
308       lhs.generate_uniform_index_samples(index_l_bnds, index_u_bnds, numSamples,
309 					 sorted_samples, true); // backfill
310 
311       // convert multi-index samples into allSamples
312       for (i=0; i<numSamples; ++i){
313 	Real* all_samp_i = allSamples[i];
314 	int*  sorted_samples_i = sorted_samples[i];
315 	for (j=0; j<numContinuousVars; ++j)
316 	  all_samp_i[j] = colloc_pts_1d[lev_index[j]][j][sorted_samples_i[j]];
317       }
318     }
319     break;
320   }
321   }
322 }
323 
324 
filter_parameter_sets()325 void NonDQuadrature::filter_parameter_sets()
326 {
327   size_t i, num_tensor_pts = allSamples.numCols();
328   const Pecos::RealVector& wts = tpqDriver->type1_weight_sets();
329 #ifdef DEBUG
330   Cout << "allSamples pre-filter:" << allSamples
331        << "weights pre-filter:\n"  << wts;
332 #endif // DEBUG
333   // sort TPQ points in order of descending weight
334   std::multimap<Real, RealVector> ordered_pts;
335   for (i=0; i<num_tensor_pts; ++i) {
336     RealVector col_i(Teuchos::Copy, allSamples[i], numContinuousVars);
337     ordered_pts.insert(std::pair<Real, RealVector>(-std::abs(wts[i]), col_i));
338   }
339   // truncate allSamples to the first numSamples with largest weight
340   allSamples.reshape(numContinuousVars, numSamples);
341   std::multimap<Real, RealVector>::iterator it;
342   for (i=0, it=ordered_pts.begin(); i<numSamples; ++i, ++it)
343     Teuchos::setCol(it->second, (int)i, allSamples);
344 #ifdef DEBUG
345   Cout << "allSamples post-filter:" << allSamples;
346 #endif // DEBUG
347 }
348 
349 
350 /** used by DataFitSurrModel::build_global() to publish the minimum
351     number of points needed from the quadrature routine in order to
352     build a particular global approximation. */
353 void NonDQuadrature::
sampling_reset(int min_samples,bool all_data_flag,bool stats_flag)354 sampling_reset(int min_samples, bool all_data_flag, bool stats_flag)
355 {
356   // tpqDriver tracks the active model key
357   UShortArray rqo = tpqDriver->reference_quadrature_order();
358   while (tpqDriver->grid_size() < min_samples) {
359     if (dimPrefSpec.empty()) increment_grid(rqo);
360     else                     increment_grid_preference(dimPrefSpec, rqo);
361   }
362 
363   if (min_samples > numSamples)
364     numSamples = min_samples;
365 
366   /* Old:
367   if (tpqDriver->grid_size() < min_samples) {
368     UShortArray dqo_l_bnd; // isotropic or anisotropic based on dimPrefSpec
369     compute_minimum_quadrature_order(min_samples, dimPrefSpec, dqo_l_bnd);
370     // enforce lower bound
371     UShortArray new_dqo(numContinuousVars);
372     for (size_t i=0; i<numContinuousVars; ++i)
373       new_dqo[i] = std::max(dqo_l_bnd[i], dimQuadOrderRef[i]);
374     // update tpqDriver
375     tpqDriver->reference_quadrature_order(new_dqo, nestedRules);
376   }
377   */
378 
379   // not currently used by this class:
380   //allDataFlag = all_data_flag;
381   //statsFlag   = stats_flag;
382 }
383 
384 
increment_grid(UShortArray & ref_quad_order)385 void NonDQuadrature::increment_grid(UShortArray& ref_quad_order)
386 {
387   // Used for uniform refinement: all quad orders are incremented by 1
388   if (nestedRules) {
389     // define reference point
390     size_t orig_size = tpqDriver->grid_size();
391     // initial increment and nestedness enforcement
392     increment_reference_quadrature_order(ref_quad_order);
393     // ensure change in presence of restricted growth
394     while (tpqDriver->grid_size() == orig_size)
395       increment_reference_quadrature_order(ref_quad_order);
396   }
397   else
398     increment_reference_quadrature_order(ref_quad_order);
399 
400   if (outputLevel >= DEBUG_OUTPUT)
401     Cout << "Incremented quadrature order:\n" << tpqDriver->quadrature_order();
402 }
403 
404 
405 void NonDQuadrature::
increment_grid_preference(const RealVector & dim_pref,UShortArray & ref_quad_order)406 increment_grid_preference(const RealVector& dim_pref,
407 			  UShortArray& ref_quad_order)
408 {
409   // Used for dimension-adaptive refinement: order lower bounds are enforced
410   // using ref_quad_order such that anisotropy may not reduce dimension
411   // resolution once grids have been resolved (as with SparseGridDriver::
412   // axisLowerBounds in NonDSparseGrid).
413   if (nestedRules) {
414     // define reference point
415     size_t orig_size = tpqDriver->grid_size();
416     // initial increment, anisotropy update, and nestedness enforcement
417     increment_reference_quadrature_order(dim_pref, ref_quad_order);
418     // ensure change in presence of restricted growth
419     while (tpqDriver->grid_size() == orig_size)
420       increment_reference_quadrature_order(dim_pref, ref_quad_order);
421   }
422   else
423     increment_reference_quadrature_order(dim_pref, ref_quad_order);
424 
425   if (outputLevel >= DEBUG_OUTPUT)
426     Cout << "Incremented quadrature order:\n" << tpqDriver->quadrature_order();
427 }
428 
429 
430 void NonDQuadrature::
increment_reference_quadrature_order(UShortArray & ref_quad_order)431 increment_reference_quadrature_order(UShortArray& ref_quad_order)
432 {
433   // increment uniformly by 1 (no growth rule is currently enforced,
434   // but could be desirable for weak nesting of symmetric rules)
435   for (size_t i=0; i<numContinuousVars; ++i)
436     ref_quad_order[i] += 1;
437 
438   tpqDriver->reference_quadrature_order(ref_quad_order, nestedRules);
439 }
440 
441 
442 void NonDQuadrature::
increment_reference_quadrature_order(const RealVector & dim_pref,UShortArray & ref_quad_order)443 increment_reference_quadrature_order(const RealVector& dim_pref,
444 				     UShortArray& ref_quad_order)
445 {
446   // determine the dimension with max preference
447   Real max_dim_pref = dim_pref[0]; size_t max_dim_pref_index = 0;
448   for (size_t i=1; i<numContinuousVars; ++i)
449     if (dim_pref[i] > max_dim_pref)
450       { max_dim_pref = dim_pref[i]; max_dim_pref_index = i; }
451   // increment only the dimension with max preference by 1
452   ref_quad_order[max_dim_pref_index] += 1;
453   // now balance the other dims relative to this new increment, preserving
454   // previous resolution
455   update_anisotropic_order(dim_pref, ref_quad_order);
456 
457   tpqDriver->reference_quadrature_order(ref_quad_order, nestedRules);
458 }
459 
460 
461 void NonDQuadrature::
update_anisotropic_order(const RealVector & dim_pref,UShortArray & ref_quad_order)462 update_anisotropic_order(const RealVector& dim_pref,
463 			 UShortArray& ref_quad_order)
464 {
465   // Logic is loosely patterned after anisotropic SSG in which the dominant
466   // dimension is constrained by the current ssgLevel and all nondominant
467   // dimensions are scaled back.
468   unsigned short max_quad_ref = ref_quad_order[0];
469   Real max_dim_pref = dim_pref[0]; size_t max_dim_pref_index = 0;
470   for (size_t i=1; i<numContinuousVars; ++i) {
471     if (ref_quad_order[i] > max_quad_ref)
472       max_quad_ref = ref_quad_order[i];
473     if (dim_pref[i] > max_dim_pref)
474       { max_dim_pref = dim_pref[i]; max_dim_pref_index = i; }
475   }
476   // There are several different options for the following operation.  Initial
477   // choice is to preserve the current (recently incremented) ref_quad_order
478   // entry in the dimension with max_dim_pref and to compute the others relative
479   // to max_quad_ref (whose dimension may differ from max_dim_pref).  Could also
480   // decide to set max_quad_ref in the max_dim_pref dimension, but this could be
481   // a much larger increment.  In all cases, the current ref_quad_order is used
482   // as a lower bound to prevent reducing previous resolution.
483   for (size_t i=0; i<numContinuousVars; ++i)
484     if (i != max_dim_pref_index)
485       ref_quad_order[i] = std::max(ref_quad_order[i],
486 	(unsigned short)(max_quad_ref*dim_pref[i]/max_dim_pref)); // truncate
487 
488   // When used as a stand-alone update:
489   //tpqDriver->reference_quadrature_order(ref_quad_order, nestedRules);
490 }
491 
492 } // namespace Dakota
493