1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
|
/*
* Copyright (c) 2017, Miroslav Stoyanov
*
* This file is part of
* Toolkit for Adaptive Stochastic Modeling And Non-Intrusive ApproximatioN: TASMANIAN
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
* and the following disclaimer in the documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse
* or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* UT-BATTELLE, LLC AND THE UNITED STATES GOVERNMENT MAKE NO REPRESENTATIONS AND DISCLAIM ALL WARRANTIES, BOTH EXPRESSED AND IMPLIED.
* THERE ARE NO EXPRESS OR IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, OR THAT THE USE OF THE SOFTWARE WILL NOT INFRINGE ANY PATENT,
* COPYRIGHT, TRADEMARK, OR OTHER PROPRIETARY RIGHTS, OR THAT THE SOFTWARE WILL ACCOMPLISH THE INTENDED RESULTS OR THAT THE SOFTWARE OR ITS USE WILL NOT RESULT IN INJURY OR DAMAGE.
* THE USER ASSUMES RESPONSIBILITY FOR ALL LIABILITIES, PENALTIES, FINES, CLAIMS, CAUSES OF ACTION, AND COSTS AND EXPENSES, CAUSED BY, RESULTING FROM OR ARISING OUT OF,
* IN WHOLE OR IN PART THE USE, STORAGE OR DISPOSAL OF THE SOFTWARE.
*/
#include "TasmanianAddons.hpp"
#include "gridtestCLICommon.hpp"
#include <limits>
using std::cout;
using std::setw;
//! \brief Runs tests and throws if the surrogates do not match to the given tolerance.
inline void compareGrids(double tolerance, TasGrid::TasmanianSparseGrid const &a, TasGrid::TasmanianSparseGrid const &b, bool check_num_points){
if (a.getNumDimensions() != b.getNumDimensions())
throw std::runtime_error("grids have wrong dimensions");
if (a.getNumOutputs() != b.getNumOutputs())
throw std::runtime_error("grids have wrong outputs");
if (check_num_points){ // some tests have a random number of points
if (a.getNumPoints() != b.getNumPoints()){
cout << "number of points = " << a.getNumPoints() << " " << b.getNumPoints() << endl;
throw std::runtime_error("grids have wrong number of points");
}
}
std::minstd_rand park_miller(77);
std::uniform_real_distribution<double> unif(-1.0, 1.0);
std::vector<double> test_points(2 * 1000);
for(auto &t : test_points) t = unif(park_miller);
std::vector<double> resa, resb; // reference and actual result
a.evaluateBatch(test_points, resa);
b.evaluateBatch(test_points, resb);
double err = 0.0;
for(auto ia = resa.begin(), ib = resb.begin(); ia != resa.end(); ia++, ib++)
err = std::max(err, std::abs(*ia - *ib));
if (err > tolerance){
cout << "difference = " << err << " expected = " << tolerance << endl;
throw std::runtime_error("grids have outputs that differ by more than the tolerance");
}
}
//! \brief Simple sequential construction for reference purposes.
void simpleSequentialConstruction(std::function<void(std::vector<double> const &x, std::vector<double> &y, size_t)> model,
size_t max_num_samples, size_t num_parallel_jobs,
TasmanianSparseGrid &grid,
std::function<std::vector<double>(TasGrid::TasmanianSparseGrid&)> get_candidates){
if (!grid.isUsingConstruction())
grid.beginConstruction();
auto candidates = get_candidates(grid);
while(!candidates.empty() && (grid.getNumLoaded() < (int) max_num_samples)){
CompleteStorage storage(grid.getNumDimensions());
size_t num_samples = std::min(std::min(num_parallel_jobs, max_num_samples - grid.getNumLoaded()), candidates.size() / grid.getNumDimensions());
for(size_t i=0; i<num_samples; i++){
std::vector<double> x(&candidates[i*grid.getNumDimensions()], &candidates[i*grid.getNumDimensions()] + grid.getNumDimensions());
std::vector<double> y(grid.getNumOutputs());
model(x, y, i);
storage.add(x, y);
}
storage.load(grid);
auto pnts = grid.getLoadedPoints();
candidates = get_candidates(grid);
}
grid.finishConstruction();
}
//! \brief Reference construction for localp grids.
void simpleSequentialConstruction(std::function<void(std::vector<double> const &x, std::vector<double> &y, size_t)> model,
size_t max_num_samples, size_t num_parallel_jobs,
TasmanianSparseGrid &grid,
double tolerance, TypeRefinement criteria, int output = -1,
std::vector<int> const &level_limits = std::vector<int>(),
std::vector<double> const &scale_correction = std::vector<double>()){
simpleSequentialConstruction(model, max_num_samples, num_parallel_jobs, grid,
[&](TasGrid::TasmanianSparseGrid& g)->std::vector<double>{
return g.getCandidateConstructionPoints(tolerance, criteria, output, level_limits, scale_correction);
});
}
//! \brief Reference construction for anisotropic refinement.
void simpleSequentialConstruction(std::function<void(std::vector<double> const &x, std::vector<double> &y, size_t)> model,
size_t max_num_samples, size_t num_parallel_jobs,
TasmanianSparseGrid &grid,
TypeDepth type, int output, std::vector<int> const &level_limits = std::vector<int>()){
simpleSequentialConstruction(model, max_num_samples, num_parallel_jobs, grid,
[&](TasGrid::TasmanianSparseGrid& g)->std::vector<double>{
return g.getCandidateConstructionPoints(type, output, level_limits);
});
}
//! \brief Tests the templates for automated construction.
bool testConstructSurrogate(bool verbose){
std::atomic_int last;
last = -1;
constexpr unsigned int delay_on_lock = 2;
// test the simple loadNeededPoints()
auto model_trig = [&](double const x[], double y[], size_t)->void{ y[0] = std::sin(x[0]) * std::cos(x[1]); };
auto model_trig_vec = [&](std::vector<double> const &x, std::vector<double> &y, size_t id)->void{ model_trig(x.data(), y.data(), id); };
auto grid = TasGrid::makeSequenceGrid(2, 1, 9, TasGrid::type_level, TasGrid::rule_leja);
auto reference_grid = grid;
auto points = reference_grid.getPoints();
std::vector<double> values(reference_grid.getNumPoints());
for(size_t i=0; i<values.size(); i++)
model_trig(&points[2*i], &values[i], 0);
reference_grid.loadNeededPoints(values);
loadNeededPoints<false, false>(model_trig, grid, 128); // sequential version ignores the num_threads
compareGrids(1.E-10, grid, reference_grid, true);
grid.loadNeededPoints(std::vector<double>(reference_grid.getNumPoints(), -1.0)); // set wrong values
loadNeededPoints<true, true>(model_trig, grid, 4); // reload the needed points using 4 threads
compareGrids(1.E-10, grid, reference_grid, true);
grid = TasGrid::makeSequenceGrid(2, 1, 9, TasGrid::type_level, TasGrid::rule_leja);
loadNeededPoints(model_trig_vec, grid, 4);
compareGrids(1.E-10, grid, reference_grid, true);
if (verbose) cout << std::setw(40) << "simple load values" << std::setw(10) << "Pass" << endl;
// parallel construction is susceptible to order of execution, number of points and which points may change from one run to the next
auto model_exp_seq = [&](std::vector<double> const &x, std::vector<double> &y, size_t)->void{ y = {std::exp(x[0] + x[1])}; };
auto model_exp = [&](std::vector<double> const &x, std::vector<double> &y, size_t id)->void{
model_exp_seq(x, y, id);
if (last == (int) id) std::this_thread::sleep_for(std::chrono::milliseconds(delay_on_lock));
else last = (int) id;
};
auto model_exp2 = [&](std::vector<double> const &x, std::vector<double> &y, size_t id)->void{
if (x.size() == 2) y = {std::exp(x[0] + x[1])}; // one sample
else y = {std::exp(x[0] + x[1]), std::exp(x[2] + x[3])}; // two samples
if (last == (int) id) std::this_thread::sleep_for(std::chrono::milliseconds(delay_on_lock));
else last = (int) id;
}; // two samples
grid = TasGrid::makeLocalPolynomialGrid(2, 1, 3, 2);
reference_grid = grid;
// Basic test, run until grid points are exhausted, number of points can still vary
// due to child surplus being computed before or after the parent point has completed
TasGrid::constructSurrogate<TasGrid::mode_parallel, no_initial_guess>
(model_exp, std::numeric_limits<size_t>::max(), 2, 1, grid, 1.E-4, TasGrid::refine_classic); // parallel
simpleSequentialConstruction(model_exp, 300, 2, reference_grid, 1.E-4, TasGrid::refine_classic);
compareGrids(5.E-4, grid, reference_grid, false);
if (verbose) cout << std::setw(40) << "parallel localp unlimited budget" << std::setw(10) << "Pass" << endl;
grid = TasGrid::makeLocalPolynomialGrid(2, 1, 3, 2);
TasGrid::constructSurrogate<TasGrid::mode_parallel, no_initial_guess>
(model_exp2, std::numeric_limits<size_t>::max(), 2, 2, grid, 1.E-4, TasGrid::refine_classic); // parallel, batch 2
compareGrids(5.E-4, grid, reference_grid, false);
if (verbose) cout << std::setw(40) << "parallel localp batch" << std::setw(10) << "Pass" << endl;
// Similar test, the number of points must match, but the actual points can be different
// compare the grids by how similar they are to each other
grid = TasGrid::makeLocalPolynomialGrid(2, 1, 3, 1);
reference_grid = grid;
TasGrid::constructSurrogate<TasGrid::mode_parallel, no_initial_guess>
(model_exp, 500, 4, 1, grid, 1.E-4, TasGrid::refine_classic); // parallel, limit points
simpleSequentialConstruction(model_exp, 500, 4, reference_grid, 1.E-4, TasGrid::refine_classic);
compareGrids(5.E-3, grid, reference_grid, true);
if (verbose) cout << std::setw(40) << "parallel localp limited budget" << std::setw(10) << "Pass" << endl;
// Sequential test, checks the simpler algorithm, this should be deterministic
grid = TasGrid::makeLocalPolynomialGrid(2, 1, 3, 2);
reference_grid = grid;
TasGrid::constructSurrogate<TasGrid::mode_sequential, no_initial_guess>
(model_exp_seq, std::numeric_limits<size_t>::max(), 2, 1, grid, 1.E-4, TasGrid::refine_classic); // sequential
simpleSequentialConstruction(model_exp, 300, 2, reference_grid, 1.E-4, TasGrid::refine_classic);
compareGrids(1.E-9, grid, reference_grid, true);
if (verbose) cout << std::setw(40) << "sequential localp limited budget" << std::setw(10) << "Pass" << endl;
// The construction algorithm is the same, but check if the getCandidateConstructionPoints() lambda works right
auto model_aniso_seq = [&](std::vector<double> const &x, std::vector<double> &y, size_t)->void{
y = {std::exp(x[0] + 0.1 * x[1])};
};
auto model_aniso = [&](std::vector<double> const &x, std::vector<double> &y, size_t id)->void{
model_aniso_seq(x, y, id);
if (last == (int) id) std::this_thread::sleep_for(std::chrono::milliseconds(delay_on_lock));
else last = (int) id;
};
grid = TasGrid::makeGlobalGrid(2, 1, 3, TasGrid::type_level, TasGrid::rule_rleja);
reference_grid = grid;
TasGrid::constructSurrogate<TasGrid::mode_parallel, no_initial_guess>
(model_aniso, 200, 3, 1, grid, TasGrid::type_iptotal, 0); // parallel
simpleSequentialConstruction(model_aniso, 200, 2, reference_grid, TasGrid::type_iptotal, 0);
compareGrids(1.E-8, grid, reference_grid, true);
if (verbose) cout << std::setw(40) << "parallel anisotropic rleja" << std::setw(10) << "Pass" << endl;
// additional fluctuation of number of points can happen due to not enough points to complete a tensor
// nevertheless the grid accuracy should match reasonably well
grid = TasGrid::makeGlobalGrid(2, 1, 3, TasGrid::type_level, TasGrid::rule_clenshawcurtis);
reference_grid = grid;
TasGrid::constructSurrogate<TasGrid::mode_parallel, no_initial_guess>
(model_aniso, 400, 3, 1, grid, TasGrid::type_iptotal, 0); // parallel
simpleSequentialConstruction(model_aniso, 400, 2, reference_grid, TasGrid::type_iptotal, 0);
compareGrids(5.E-4, grid, reference_grid, false);
if (verbose) cout << std::setw(40) << "parallel anisotropic global" << std::setw(10) << "Pass" << endl;
// fix the weights, the computed grid must be very similar to the direct anisotropic make grid
// i.e., the "most important" points are defined the same way through the user provided anisotropic weights
// the atomic trick is used to ensure that no thread lags and holds important samples that affect the estimate for the anisotropy
last = -1;
std::vector<int> aweights = {1, 2};
reference_grid = TasGrid::makeSequenceGrid(2, 1, 9, TasGrid::type_level, TasGrid::rule_leja, aweights);
loadNeededPoints<mode_sequential>(model_aniso_seq, reference_grid, 0);
grid = TasGrid::makeSequenceGrid(2, 1, 1, TasGrid::type_level, TasGrid::rule_leja);
TasGrid::constructSurrogate<TasGrid::mode_parallel, no_initial_guess>
(model_aniso, reference_grid.getNumLoaded(), 4, 1, grid, TasGrid::type_iptotal, aweights); // parallel
compareGrids(5.E-7, grid, reference_grid, true);
if (verbose) cout << std::setw(40) << "parallel weighted sequence" << std::setw(10) << "Pass" << endl;
// test checkpoint-restart
std::atomic_int num_good;
num_good = 0;
auto model_crash = [&](std::vector<double> const &x, std::vector<double> &y, size_t)->void{
y = {std::exp(x[0] + 0.1 * x[1])};
num_good++;
if (num_good == 10) throw std::runtime_error("test fail"); // simulate a crash on iteration 10
};
grid = TasGrid::makeSequenceGrid(2, 1, 1, TasGrid::type_level, TasGrid::rule_leja);
std::remove("checkpoint"); // make sure the checkpoint filename is not present (e.g., left after an earlier crash)
try{
TasGrid::constructSurrogate<TasGrid::mode_sequential, no_initial_guess>
(model_crash, reference_grid.getNumLoaded(),
2, 1, grid, TasGrid::type_iptotal, aweights, {}, "checkpoint");
std::cout << "ERROR: testConstructSurrogate() could not simulate a crash." << std::endl;
return false;
}catch(std::runtime_error &){
//cout << "Error caught!" << endl;
grid = TasGrid::makeSequenceGrid(2, 1, 1, TasGrid::type_level, TasGrid::rule_rleja); // recovery should change the rule
TasGrid::constructSurrogate<TasGrid::mode_sequential, no_initial_guess>
(model_crash, reference_grid.getNumLoaded(),
2, 1, grid, TasGrid::type_iptotal, aweights, {}, "checkpoint");
if (grid.getRule() != rule_leja) throw std::runtime_error("Failed recovery from crash, wrong rule.");
compareGrids(1.E-9, grid, reference_grid, true);
if (verbose) cout << std::setw(40) << "parallel resilient sequence" << std::setw(10) << "Pass" << endl;
}
if (std::remove("checkpoint") != 0) throw std::runtime_error("Could not delete the 'checkpoint' file, the file must exists after the test.");
return true;
}
|