From 78a72e7b2727ff6b414ce141c26e0a7bc8544a25 Mon Sep 17 00:00:00 2001 From: Miroslav Stoyanov <30537612+mkstoyanov@users.noreply.github.com> Date: Thu, 8 Aug 2024 20:25:39 -0400 Subject: [PATCH] plotting works for all degrees (#720) * plotting now works for arbitrary degree --- examples/continuity_2d.cpp | 8 +- examples/continuity_2d.py | 5 +- examples/inputs_1d.py | 4 +- python/asgard.py | 1 - python/pyasgard_test.py | 15 +- src/asgard_reconstruct.cpp | 266 ++++++++++++++++++++++++++++------- src/asgard_reconstruct.hpp | 13 +- src/asgard_wavelet_basis.hpp | 29 ++-- 8 files changed, 263 insertions(+), 78 deletions(-) diff --git a/examples/continuity_2d.cpp b/examples/continuity_2d.cpp index f321c11c6..d2f131476 100644 --- a/examples/continuity_2d.cpp +++ b/examples/continuity_2d.cpp @@ -123,10 +123,10 @@ class example_continuity2d : public asgard::PDE // function definitions needed to build up the dimension, terms and sources // - // for all funtions, the "vector x" indicates a batch of quadrature points + // for all functions, the "vector x" indicates a batch of quadrature points // in the corresponding dimension (e.g., dim0 or dim1) // the output should be a vector with the same size holding f(x) - // funcitons also accept a "time" scalar but it is often ignored + // functions also accept a "time" scalar but it is often ignored // specify initial condition vector functions... static vector initial_condition_x(vector const &x, precision const = 0) @@ -143,7 +143,7 @@ class example_continuity2d : public asgard::PDE // ignored parameter corresponds to time vector fx(x.size()); for (int i = 0; i < x.size(); i++) - fx[i] = std::sin(precision{2.0} * M_PI * x[i]); + fx[i] = std::sin(precision{2.0} * M_PI * x[i]); return fx; } @@ -198,7 +198,7 @@ class example_continuity2d : public asgard::PDE } // a bit of a misnomer, this is a scaling factor applied to dt - // in conjuction with the CFL conidition provided from the command line + // in conjunction with the CFL condition provided from the command line // (note: this is probably not fully supported at the moment) static precision get_dt(asgard::dimension const &dim) { diff --git a/examples/continuity_2d.py b/examples/continuity_2d.py index bee7a0076..cf657aefb 100644 --- a/examples/continuity_2d.py +++ b/examples/continuity_2d.py @@ -24,14 +24,13 @@ exit(1) print("asgard: running the continuity example") - os.system("./example_continuity_2d -p continuity_2 -d 1 -l 6 -w 10 -n 10 -t 0.0001") + os.system("./example_continuity_2d -p continuity_2 -d 2 -l 5 -n 10 -t 0.0001 -of cont1_final.h5") # the example above will run for 10 time steps and the -w 10 options # will tell the code to output on the final 10-th step - output_filename = 'asgard_wavelet_10.h5' + output_filename = 'cont1_final.h5' if not os.path.isfile(output_filename): print("ERROR: example_continuity_2d did not generate an output file") - exit(1) # using the ASGarD python module to read from the file snapshot = asgard.pde_snapshot(output_filename) diff --git a/examples/inputs_1d.py b/examples/inputs_1d.py index 341c9900d..cc035a7da 100644 --- a/examples/inputs_1d.py +++ b/examples/inputs_1d.py @@ -29,8 +29,8 @@ outfile2 = 'waves2.h5' # providing the input file and also forcing the degree to 1 - os.system("./example_inputs_1d -if inputs_1d_1.txt -d 1 -of %s" % outfile1) - os.system("./example_inputs_1d -if inputs_1d_2.txt -d 1 -of %s" % outfile2) + os.system("./example_inputs_1d -if inputs_1d_1.txt -d 2 -of %s" % outfile1) + os.system("./example_inputs_1d -if inputs_1d_2.txt -d 2 -of %s" % outfile2) if not os.path.isfile(outfile1) or not os.path.isfile(outfile2): print("ERROR: example_inputs_1d did not generate an output file") diff --git a/python/asgard.py b/python/asgard.py index ccaa5607b..c035fa62e 100644 --- a/python/asgard.py +++ b/python/asgard.py @@ -49,7 +49,6 @@ def __init__(self, filename, verbose = False): self.num_dimensions = fdata['ndims'][()] self.degree = fdata['degree'][()] - assert self.degree <= 1, "only works with constant and linear basis, others will be coming soon" self.state = fdata['soln'][()] self.cells = fdata['elements'][()] diff --git a/python/pyasgard_test.py b/python/pyasgard_test.py index b1456c67d..7f9ba58ec 100644 --- a/python/pyasgard_test.py +++ b/python/pyasgard_test.py @@ -151,20 +151,21 @@ def test2d_basis_reconstruct(self): # simple IO test def test_simple1d(self): print("\ntesting 1d plot") - tols = (1.E-3, 1.E-5) - for degree in range(2): - os.system("./asgard -p continuity_1 -d %d -l 6 -w 10 -dt 0.01 1>/dev/null" % degree) + tols = (1.E-4, 1.E-6, 1.E-8, 1.E-9) + levs = (6, 6, 4, 4) + for degree in range(4): + os.system("./asgard -p continuity_1 -d %d -l 6 -dt 0.005 -of _test_plot.h5 1>/dev/null" % degree) - self.assertTrue(os.path.isfile("asgard_wavelet_10.h5"), "failed to run continuity_1") + self.assertTrue(os.path.isfile("_test_plot.h5"), "failed to run continuity_1") - snapshot = asgard.pde_snapshot("asgard_wavelet_10.h5") + snapshot = asgard.pde_snapshot("_test_plot.h5") gold_dimension_min = np.array([-1.0, ]) gold_dimension_max = np.array([1.0, ]) self.almost_equal(snapshot.dimension_min, gold_dimension_min, - "mismatch in dimension_min") + "mismatch in dimension_min") self.almost_equal(snapshot.dimension_max, gold_dimension_max, - "mismatch in dimension_max") + "mismatch in dimension_max") self.assertEqual(snapshot.num_dimensions, 1, "mismatch in the number of dimensions") self.assertEqual(snapshot.num_cells, 64, "mismatch in the number of cells") diff --git a/src/asgard_reconstruct.cpp b/src/asgard_reconstruct.cpp index fdc6b1238..15bc1ef22 100644 --- a/src/asgard_reconstruct.cpp +++ b/src/asgard_reconstruct.cpp @@ -2,6 +2,8 @@ #include "asgard_wavelet_basis.hpp" +#include "basis.hpp" + namespace asgard { @@ -50,6 +52,35 @@ reconstruct_solution::reconstruct_solution( // analyze the graph and prepare cache data build_tree(); + + // prepare the wavelet basis, if using general degree + if (degree >= 3) + { + // using coefficient matrices for the left/right wavelets + // and scale factors for the legendre basis + wavelets.resize(2 * pterms_ * pterms_ + pterms_); + wleft = wavelets.data() + pterms_; + wright = wleft + pterms_ * pterms_; + + // first pterms_ entries are the scale factors of the + // orthogonal Legendre polynomials on the interval (0, 1) + wavelets[0] = 1.0; + for (int i = 1; i < pterms_; i++) + wavelets[i] = std::sqrt(2.0 * i + 1.0); + + // using ASGarD to build the matrices, but may be differen precision + using prec = default_precision; + std::array, 6> matx = generate_multi_wavelets(degree); + fk::matrix const &c = matx[4]; // only need one matrix + + // reversing the order, since we will be running from degree 0 to pterms_-1 + auto w = wleft; + for (auto i : indexof(pterms_)) + w = std::copy_n(c.data(0, pterms_ - i - 1), pterms_, w); + + for (auto i : indexof(pterms_)) + w = std::copy_n(c.data(pterms_, pterms_ - i - 1), pterms_, w); + } } void reconstruct_solution::set_domain_bounds(double const amin[], double const amax[]) @@ -94,8 +125,11 @@ void reconstruct_solution::reconstruct(double const x[], int num_x, double y[]) case 1: reconstruct<1>(x, num_x, y); break; + case 2: + reconstruct<2>(x, num_x, y); + break; default: - throw std::runtime_error("incorrect degree"); + reconstruct<-1>(x, num_x, y); } } @@ -230,72 +264,214 @@ void reconstruct_solution::build_tree() std::copy_if(tree[0], tree[0] + tree.total_size(), indx.begin(), [](int t)->bool{ return (t > -1); }); } +template std::optional -reconstruct_solution::basis_value0(int const p[], double const x[], double const c[]) const +reconstruct_solution::basis_value(int const p[], double const x[], + double const c[], vector2d &scratch) const { + // degree 0, 1, and 2 are hard-coded as analytic formulat + // degree -1 means that we are using general degree and should use pterms_ + static_assert(degree >= -1 and degree <= 2, + "unsupported degree, use 0, 1, 2 for analytic expressions, " + "and degree -1 for the general case"); + // first translat the x values to normalized values for the given cell std::array xn; - std::array w; + double w = 1.0; int const &num_dimensions = cells_.num_dimensions(); - for (int d = 0; d < num_dimensions; d++) + for (auto d : indexof(num_dimensions)) { int p2l2; - fm::intlog2_pow2pows2(p[d], p2l2, w[d]); + double wd; + fm::intlog2_pow2pows2(p[d], p2l2, wd); + w *= wd; xn[d] = (p[d] > 1) ? (p2l2 * (x[d] + 1.0) - p[d]) : x[d]; - // if the normalized point is out of bounds, return no-value - if (xn[d] < 0.0 or xn[d] > 1.0) - return std::optional(); + // analytic formular work on (0, 1), general degree uses a shift to (-1, 1) + if constexpr (degree == -1) + { + xn[d] = 2.0 * xn[d] - 1.0; + if (xn[d] < -1.0 or xn[d] > 1.0) + return std::optional(); + } + else + { + // if the normalized point is out of bounds, return no-value + if (xn[d] < 0.0 or xn[d] > 1.0) + return std::optional(); + } } // loop over all the basis inside the cell - double val = 1.0; - for (auto d : indexof(num_dimensions)) - val *= (p[d] == 0 or xn[d] > 0.5) ? w[d] : -w[d]; + if constexpr (degree == -1) // general case + { + // find the values of all basis functions at different x[d] + for (auto d : indexof(num_dimensions)) + { + double *vals = scratch[d]; + if (p[d] == 0) + { // Legendre basis, using recurence relationship + double lmm = 1.0, lm = xn[d]; + vals[0] = 1.0; + vals[1] = lm * wavelets[1]; + for (int n = 2; n < pterms_; n++) + { + double l = ((2 * n - 1) * xn[d] * lm - (n - 1) * lmm) / static_cast(n); + vals[n] = l * wavelets[n]; + lmm = lm; + lm = l; + } + } + else + { + if (xn[d] < 0.0) + { + std::copy_n(wleft, pterms_, vals); + double mono = xn[d]; + for (int i = 1; i < pterms_; i++) + { +#pragma omp simd + for (int j = 0; j < pterms_; j++) + vals[j] += mono * wleft[i * pterms_ + j]; + mono *= xn[d]; + } + } + else + { + std::copy_n(wright, pterms_, vals); + double mono = xn[d]; + for (int i = 1; i < pterms_; i++) + { +#pragma omp simd + for (int j = 0; j < pterms_; j++) + vals[j] += mono * wright[i * pterms_ + j]; + mono *= xn[d]; + } + } + } + } - return c[0] * val; -} + double sum = 0.0; + for (auto i : indexof(block_size_)) + { + int t = i; + double v = w; + for (int d = num_dimensions - 1; d >= 0; d--) + { + v *= scratch[d][t % pterms_]; + t /= pterms_; + } -std::optional -reconstruct_solution::basis_value1(int const p[], double const x[], - double const c[]) const -{ - // first translat the x values to normalized values for the given cell - std::array xn; - std::array w; - int const &num_dimensions = cells_.num_dimensions(); - for (int d = 0; d < num_dimensions; d++) - { - int p2l2; - fm::intlog2_pow2pows2(p[d], p2l2, w[d]); - xn[d] = (p[d] > 1) ? (p2l2 * (x[d] + 1.0) - p[d]) : x[d]; + sum += v * c[i]; + } - // if the normalized point is out of bounds, return no-value - if (xn[d] < 0.0 or xn[d] > 1.0) - return std::optional(); + return std::optional(sum); } + else if constexpr (degree == 2) + { + // this is an alternative to scratch, sits on the stack + std::array, max_num_dimensions> vals; + for (auto d : indexof(num_dimensions)) + { + if (p[d] == 0) + { + vals[d][0] = quadratic_basis::pleg0(xn[d]); + vals[d][1] = quadratic_basis::pleg1(xn[d]); + vals[d][2] = quadratic_basis::pleg2(xn[d]); + } + else + { + if (xn[d] < 0.5) + { + vals[d][0] = quadratic_basis::pwav0L(xn[d]); + vals[d][1] = quadratic_basis::pwav1L(xn[d]); + vals[d][2] = quadratic_basis::pwav2L(xn[d]); + } + else + { + vals[d][0] = quadratic_basis::pwav0R(xn[d]); + vals[d][1] = quadratic_basis::pwav1R(xn[d]); + vals[d][2] = quadratic_basis::pwav2R(xn[d]); + } + } + } - // loop over all the basis inside the cell - double sum = 0.0; - for (int i = 0; i < block_size_; i++) + double sum = 0.0; + for (auto i : indexof(block_size_)) + { + int t = i; + double v = w; + for (int d = num_dimensions - 1; d >= 0; d--) + { + v *= vals[d][t % 3]; + t /= 3; + } + + sum += v * c[i]; + } + + return std::optional(sum); + } + else if constexpr (degree == 1) { - int t = i; - double val = 1.0; - for (int d = num_dimensions - 1; d >= 0; d--) + // this is an alternative to scratch, sits on the stack + std::array, max_num_dimensions> vals; + for (auto d : indexof(num_dimensions)) + { + if (p[d] == 0) + { + vals[d][0] = 1.0; + vals[d][1] = linear_basis::pleg1(xn[d]); + } + else + { + if (xn[d] < 0.5) + { + vals[d][0] = linear_basis::pwav0L(xn[d]); + vals[d][1] = linear_basis::pwav1L(xn[d]); + } + else + { + vals[d][0] = linear_basis::pwav0R(xn[d]); + vals[d][1] = linear_basis::pwav1R(xn[d]); + } + } + } + + double sum = 0.0; + for (int i = 0; i < block_size_; i++) { - val *= w[d] * linear_basis::pbasis(p[d], t % pterms_, xn[d]); - t /= pterms_; + int t = i; + double v = w; + for (int d = num_dimensions - 1; d >= 0; d--) + { + v *= vals[d][t % 2]; + t /= 2; + } + + sum += v * c[i]; } - sum += val * c[i]; + return std::optional(sum); } + else // degree == 0 + { + int v = 1; + for (auto d : indexof(num_dimensions)) + if (p[d] > 0 and xn[d] < 0.5) + v = -v; - return std::optional(sum); + return c[0] * w * v; + } } template double reconstruct_solution::walk_tree(double const x[]) const { + vector2d workspace; + if constexpr (degree == -1) + workspace = vector2d(pterms_, cells_.num_dimensions()); + std::array monkey_count; std::array monkey_tail; @@ -303,10 +479,8 @@ double reconstruct_solution::walk_tree(double const x[]) const double result = 0; for(const auto &r : roots){ - if constexpr (degree == 0) - basis = basis_value0(cells_[r], x, coeff_.data() + r); - else if constexpr (degree == 1) - basis = basis_value1(cells_[r], x, coeff_.data() + r * block_size_); + basis = basis_value(cells_[r], x, coeff_.data() + r * block_size_, + workspace); if (basis) { @@ -320,10 +494,8 @@ double reconstruct_solution::walk_tree(double const x[]) const { if (monkey_count[current] < pntr[monkey_tail[current]+1]){ int const p = indx[monkey_count[current]]; - if constexpr (degree == 0) - basis = basis_value0(cells_[p], x, coeff_.data() + p); - else if constexpr (degree == 1) - basis = basis_value1(cells_[p], x, coeff_.data() + p * block_size_); + basis = basis_value(cells_[p], x, coeff_.data() + p * block_size_, + workspace); if (basis) { diff --git a/src/asgard_reconstruct.hpp b/src/asgard_reconstruct.hpp index 2ecbda447..12daf6f7f 100644 --- a/src/asgard_reconstruct.hpp +++ b/src/asgard_reconstruct.hpp @@ -102,11 +102,11 @@ class reconstruct_solution * returns the value of the sum of basis times coefficients, * the optional is empty if the basis is not supported. */ + template std::optional - basis_value1(int const p[], double const x[], double const c[]) const; + basis_value(int const p[], double const x[], double const c[], + vector2d &scratch) const; //! \brief Simplest case, using constant basis - std::optional - basis_value0(int const p[], double const x[], double const c[]) const; //! Evaluate the loaded approximation at point x template double walk_tree(const double x[]) const; @@ -125,6 +125,13 @@ class reconstruct_solution std::vector roots; std::vector pntr; std::vector indx; + + // using singe vector so data is compact and cached more easily + // alias the location of the left-right basis + std::vector wavelets; + // aliases to the structure above + double *wleft = nullptr; + double *wright = nullptr; }; } // namespace asgard diff --git a/src/asgard_wavelet_basis.hpp b/src/asgard_wavelet_basis.hpp index de9a8a290..c495137f0 100644 --- a/src/asgard_wavelet_basis.hpp +++ b/src/asgard_wavelet_basis.hpp @@ -14,17 +14,6 @@ struct linear_basis static P pwav0R(P x) { return s3 * (-3 + 4 * x); } static P pwav1L(P x) { return -1 + 6 * x; } static P pwav1R(P x) { return -5 + 6 * x; } - // combine the projection basis together, maybe too much if-statements - static P pbasis(int global_index, int cell_index, P x) - { - if (global_index == 0) // Legendre cell - return (cell_index == 0) ? 1 : pleg1(x); - else // wavelet cell - if (cell_index == 0) - return (x < 0.5) ? pwav0L(x) : pwav0R(x); - else - return (x < 0.5) ? pwav1L(x) : pwav1R(x); - } // interpolation basis static P ibas0(P x) { return -3 * x + 2; } static P ibas1(P x) { return 3 * x - 1; } @@ -34,5 +23,23 @@ struct linear_basis static P iwav1R(P x) { return 6 * x - 4; } }; +template +struct quadratic_basis +{ + static P constexpr s3 = linear_basis

::s3; // sqrt(3.0) + static P constexpr s5 = 2.2360679774997897; // sqrt(5.0) + + static constexpr auto pleg0 = linear_basis

::pleg0; + static constexpr auto pleg1 = linear_basis

::pleg1; + static P pleg2(P x) { return s5 * (6.0 * x * x - 6.0 * x + 1.0); } + + static P pwav0L(P x) { return -(1.0 - 12.0 * x + 24.0 * x * x) * s5; } + static P pwav1L(P x) { return s3 * (30.0 * x * x - 14.0 * x + 1.0); } + static P pwav2L(P x) { return 1.0 - 6.0 * x; } + + static P pwav0R(P x) { return (13.0 - 36.0 * x + 24.0 * x * x) * s5; } + static P pwav1R(P x) { return s3 * (30.0 * x * x - 46.0 * x + 17.0); } + static P pwav2R(P x) { return 5.0 - 6.0 * x; } +}; } // namespace asgard