From a53f8de3a4dd90e995d39b684ed5c1e5005eb949 Mon Sep 17 00:00:00 2001 From: Will Saunders <77331509+will-saunders-ukaea@users.noreply.github.com> Date: Thu, 24 Aug 2023 11:49:16 +0100 Subject: [PATCH] Feature/3d basis evaluate cherry (#199) * cherry picked implementation from 3d-basis-evaluate * added reference implementation of basis functions from feature branch * added expansion looping and basis evaluations from feature branch * added jacobi coefficient computation from feature branch * added explicited template initialisation of function evaluation templates from fature branch * added unit and integration tests for 3d projection from feature branch * added 3d evaluation tests from feature branch * added test_resources for 3d evaluation and projection from feature branch * removed dead file * fixed include guard naming * remove unnessary io from test * updated NESO-Particles submodule * correction of comments, tidy up of namespaces and calls to std::pow --- CMakeLists.txt | 18 +- include/nektar_interface/basis_evaluation.hpp | 712 +-- include/nektar_interface/basis_reference.hpp | 167 + .../expansion_looping/basis_evaluate_base.hpp | 179 + .../expansion_looping/expansion_looping.hpp | 12 + .../geom_to_expansion_builder.hpp} | 4 +- .../expansion_looping/hexahedron.hpp | 100 + .../jacobi_coeff_mod_basis.hpp | 74 + .../jacobi_expansion_looping_interface.hpp | 213 + .../expansion_looping/prism.hpp | 111 + .../expansion_looping/pyramid.hpp | 111 + .../expansion_looping/quadrilateral.hpp | 90 + .../expansion_looping/tetrahedron.hpp | 115 + .../expansion_looping/triangle.hpp | 91 + .../function_bary_evaluation.hpp | 27 +- .../function_basis_evaluation.hpp | 218 +- .../function_basis_projection.hpp | 295 +- .../nektar_interface/function_evaluation.hpp | 18 +- .../nektar_interface/function_projection.hpp | 33 +- .../coarse_lookup_map.hpp | 4 +- .../nektar_interface/special_functions.hpp | 15 + include/nektar_interface/utilities.hpp | 130 +- neso-particles | 2 +- src/nektar_interface/basis_reference.cpp | 461 ++ .../jacobi_coeff_mod_basis.cpp | 82 + src/nektar_interface/function_evaluation.cpp | 9 + test/CMakeLists.txt | 5 + .../test_function_projection_order.cpp | 681 +++ .../test_function_projection_order_3d.cpp | 229 + .../reference_all_types_cube/conditions.xml | 15 +- .../conditions_cg.xml | 80 + .../reference_hex_cube/conditions.xml | 40 + .../reference_hex_cube/conditions_cg.xml | 40 + .../conditions_cg_nummodes_2.xml | 40 + .../conditions_nummodes_2.xml | 40 + .../hex_cube_0.3_perturbed.xml | 4949 +++++++++++++++++ .../reference_prism_tet_cube/conditions.xml | 41 + .../conditions_cg.xml | 41 + .../conditions_cg_nummodes_4.xml | 41 + .../conditions_nummodes_4.xml | 41 + .../prism_tet_cube_0.5_perturbed.xml | 2286 ++++++++ .../test_basis_evaluation.cpp | 471 +- .../test_kernel_basis_evaluation.cpp | 215 + .../test_particle_function_evaluation_3d.cpp | 180 + .../test_particle_function_projection.cpp | 651 --- .../test_particle_function_projection_3d.cpp | 166 + 46 files changed, 12136 insertions(+), 1407 deletions(-) create mode 100644 include/nektar_interface/basis_reference.hpp create mode 100644 include/nektar_interface/expansion_looping/basis_evaluate_base.hpp create mode 100644 include/nektar_interface/expansion_looping/expansion_looping.hpp rename include/nektar_interface/{function_coupling_base.hpp => expansion_looping/geom_to_expansion_builder.hpp} (90%) create mode 100644 include/nektar_interface/expansion_looping/hexahedron.hpp create mode 100644 include/nektar_interface/expansion_looping/jacobi_coeff_mod_basis.hpp create mode 100644 include/nektar_interface/expansion_looping/jacobi_expansion_looping_interface.hpp create mode 100644 include/nektar_interface/expansion_looping/prism.hpp create mode 100644 include/nektar_interface/expansion_looping/pyramid.hpp create mode 100644 include/nektar_interface/expansion_looping/quadrilateral.hpp create mode 100644 include/nektar_interface/expansion_looping/tetrahedron.hpp create mode 100644 include/nektar_interface/expansion_looping/triangle.hpp create mode 100644 src/nektar_interface/basis_reference.cpp create mode 100644 src/nektar_interface/expansion_looping/jacobi_coeff_mod_basis.cpp create mode 100644 src/nektar_interface/function_evaluation.cpp create mode 100644 test/integration/nektar_interface/test_function_projection_order.cpp create mode 100644 test/integration/nektar_interface/test_function_projection_order_3d.cpp create mode 100644 test/test_resources/reference_all_types_cube/conditions_cg.xml create mode 100644 test/test_resources/reference_hex_cube/conditions.xml create mode 100644 test/test_resources/reference_hex_cube/conditions_cg.xml create mode 100644 test/test_resources/reference_hex_cube/conditions_cg_nummodes_2.xml create mode 100644 test/test_resources/reference_hex_cube/conditions_nummodes_2.xml create mode 100644 test/test_resources/reference_hex_cube/hex_cube_0.3_perturbed.xml create mode 100644 test/test_resources/reference_prism_tet_cube/conditions.xml create mode 100644 test/test_resources/reference_prism_tet_cube/conditions_cg.xml create mode 100644 test/test_resources/reference_prism_tet_cube/conditions_cg_nummodes_4.xml create mode 100644 test/test_resources/reference_prism_tet_cube/conditions_nummodes_4.xml create mode 100644 test/test_resources/reference_prism_tet_cube/prism_tet_cube_0.5_perturbed.xml create mode 100644 test/unit/nektar_interface/test_kernel_basis_evaluation.cpp create mode 100644 test/unit/nektar_interface/test_particle_function_evaluation_3d.cpp create mode 100644 test/unit/nektar_interface/test_particle_function_projection_3d.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8456c30f..a156e390 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,6 +98,9 @@ set(LIB_SRC_FILES ${SRC_DIR}/diagnostics.cpp ${SRC_DIR}/linear_interpolator_1D.cpp ${SRC_DIR}/mesh.cpp + ${SRC_DIR}/nektar_interface/basis_reference.cpp + ${SRC_DIR}/nektar_interface/expansion_looping/jacobi_coeff_mod_basis.cpp + ${SRC_DIR}/nektar_interface/function_evaluation.cpp ${SRC_DIR}/nektar_interface/geometry_transport/geometry_transport_3d.cpp ${SRC_DIR}/nektar_interface/geometry_transport/halo_extension.cpp ${SRC_DIR}/nektar_interface/particle_cell_mapping/map_particles_2d.cpp @@ -111,7 +114,7 @@ set(LIB_SRC_FILES ${SRC_DIR}/run_info.cpp ${SRC_DIR}/simulation.cpp ${SRC_DIR}/species.cpp) - + set(LIB_SRC_FILES_IGNORE ${SRC_DIR}/main.cpp) check_file_list(${SRC_DIR} cpp "${LIB_SRC_FILES}" "${LIB_SRC_FILES_IGNORE}") @@ -128,13 +131,24 @@ set(HEADER_FILES ${INC_DIR}/linear_interpolator_1D.hpp ${INC_DIR}/mesh.hpp ${INC_DIR}/nektar_interface/basis_evaluation.hpp + ${INC_DIR}/nektar_interface/basis_reference.hpp ${INC_DIR}/nektar_interface/bounding_box_intersection.hpp ${INC_DIR}/nektar_interface/cell_id_translation.hpp ${INC_DIR}/nektar_interface/coordinate_mapping.hpp + ${INC_DIR}/nektar_interface/expansion_looping/basis_evaluate_base.hpp + ${INC_DIR}/nektar_interface/expansion_looping/expansion_looping.hpp + ${INC_DIR}/nektar_interface/expansion_looping/geom_to_expansion_builder.hpp + ${INC_DIR}/nektar_interface/expansion_looping/hexahedron.hpp + ${INC_DIR}/nektar_interface/expansion_looping/jacobi_coeff_mod_basis.hpp + ${INC_DIR}/nektar_interface/expansion_looping/jacobi_expansion_looping_interface.hpp + ${INC_DIR}/nektar_interface/expansion_looping/prism.hpp + ${INC_DIR}/nektar_interface/expansion_looping/pyramid.hpp + ${INC_DIR}/nektar_interface/expansion_looping/quadrilateral.hpp + ${INC_DIR}/nektar_interface/expansion_looping/tetrahedron.hpp + ${INC_DIR}/nektar_interface/expansion_looping/triangle.hpp ${INC_DIR}/nektar_interface/function_bary_evaluation.hpp ${INC_DIR}/nektar_interface/function_basis_evaluation.hpp ${INC_DIR}/nektar_interface/function_basis_projection.hpp - ${INC_DIR}/nektar_interface/function_coupling_base.hpp ${INC_DIR}/nektar_interface/function_evaluation.hpp ${INC_DIR}/nektar_interface/function_projection.hpp ${INC_DIR}/nektar_interface/geometry_transport/geometry_container_3d.hpp diff --git a/include/nektar_interface/basis_evaluation.hpp b/include/nektar_interface/basis_evaluation.hpp index 2beba816..ba1f4833 100644 --- a/include/nektar_interface/basis_evaluation.hpp +++ b/include/nektar_interface/basis_evaluation.hpp @@ -10,7 +10,9 @@ #include #include -#include "function_coupling_base.hpp" +#include "basis_reference.hpp" +#include "expansion_looping/geom_to_expansion_builder.hpp" +#include "expansion_looping/jacobi_coeff_mod_basis.hpp" #include "geometry_transport/shape_mapping.hpp" #include "special_functions.hpp" #include "utility_sycl.hpp" @@ -26,52 +28,8 @@ using namespace Nektar::StdRegions; #include #include -#include - namespace NESO { -/** - * Reference implementation to compute eModified_A at an order p and point z. - * - * @param p Polynomial order. - * @param z Point in [-1, 1] to evaluate at. - * @returns Basis function evaluated at point. - */ -inline double eval_modA_i(const int p, const double z) { - const double b0 = 0.5 * (1.0 - z); - const double b1 = 0.5 * (1.0 + z); - if (p == 0) { - return b0; - } - if (p == 1) { - return b1; - } - return b0 * b1 * jacobi(p - 2, z, 1, 1); -} - -/** - * Reference implementation to compute eModified_B at an order p,q and point z. - * - * @param p First index for basis. - * @param q Second index for basis. - * @param z Point in [-1, 1] to evaluate at. - * @returns Basis function evaluated at point. - */ -inline double eval_modB_ij(const int p, const int q, const double z) { - - double output; - - if (p == 0) { - output = eval_modA_i(q, z); - } else if (q == 0) { - output = std::pow(0.5 * (1.0 - z), (double)p); - } else { - output = std::pow(0.5 * (1.0 - z), (double)p) * 0.5 * (1.0 + z) * - jacobi(q - 1, z, 2 * p - 1, 1); - } - return output; -} - namespace BasisJacobi { /** @@ -99,19 +57,20 @@ namespace BasisJacobi { * and we linearise this two dimensional indexing to match the Nektar++ * ordering. */ -inline void mod_B(const int nummodes, const double z, const int k_stride_n, - const double *k_coeffs_pnm10, const double *k_coeffs_pnm11, - const double *k_coeffs_pnm2, double *output) { +inline void mod_B(const int nummodes, const REAL z, const int k_stride_n, + const REAL *const k_coeffs_pnm10, + const REAL *const k_coeffs_pnm11, + const REAL *const k_coeffs_pnm2, REAL *output) { int modey = 0; - const double b0 = 0.5 * (1.0 - z); - const double b1 = 0.5 * (1.0 + z); - double b1_pow = 1.0 / b0; + const REAL b0 = 0.5 * (1.0 - z); + const REAL b1 = 0.5 * (1.0 + z); + REAL b1_pow = 1.0 / b0; for (int px = 0; px < nummodes; px++) { - double pn, pnm1, pnm2; + REAL pn, pnm1, pnm2; b1_pow *= b0; const int alpha = 2 * px - 1; for (int qx = 0; qx < (nummodes - px); qx++) { - double etmp1; + REAL etmp1; // evaluate eModified_B at eta1 if (px == 0) { // evaluate eModified_A(q, eta1) @@ -127,9 +86,9 @@ inline void mod_B(const int nummodes, const double z, const int k_stride_n, etmp1 = b0 * b1 * pnm1; } else { const int nx = qx - 2; - const double c_pnm10 = k_coeffs_pnm10[k_stride_n * 1 + nx]; - const double c_pnm11 = k_coeffs_pnm11[k_stride_n * 1 + nx]; - const double c_pnm2 = k_coeffs_pnm2[k_stride_n * 1 + nx]; + const REAL c_pnm10 = k_coeffs_pnm10[k_stride_n * 1 + nx]; + const REAL c_pnm11 = k_coeffs_pnm11[k_stride_n * 1 + nx]; + const REAL c_pnm2 = k_coeffs_pnm2[k_stride_n * 1 + nx]; pn = c_pnm10 * pnm1 * z + c_pnm11 * pnm1 + c_pnm2 * pnm2; pnm2 = pnm1; pnm1 = pn; @@ -146,9 +105,10 @@ inline void mod_B(const int nummodes, const double z, const int k_stride_n, pnm1 = 0.5 * (2.0 * (alpha + 1) + (alpha + 3) * (z - 1.0)); etmp1 = b1_pow * b1 * pnm1; } else { - const double c_pnm10 = k_coeffs_pnm10[k_stride_n * alpha + nx]; - const double c_pnm11 = k_coeffs_pnm11[k_stride_n * alpha + nx]; - const double c_pnm2 = k_coeffs_pnm2[k_stride_n * alpha + nx]; + const REAL c_pnm10 = k_coeffs_pnm10[k_stride_n * alpha + nx]; + const REAL c_pnm11 = k_coeffs_pnm11[k_stride_n * alpha + nx]; + const REAL c_pnm2 = k_coeffs_pnm2[k_stride_n * alpha + nx]; + pn = c_pnm10 * pnm1 * z + c_pnm11 * pnm1 + c_pnm2 * pnm2; pnm2 = pnm1; pnm1 = pn; @@ -184,16 +144,17 @@ inline void mod_B(const int nummodes, const double z, const int k_stride_n, * @param[in, out] output entry i contains the i-th eModified_A basis function * evaluated at z. */ -inline void mod_A(const int nummodes, const double z, const int k_stride_n, - const double *k_coeffs_pnm10, const double *k_coeffs_pnm11, - const double *k_coeffs_pnm2, double *output) { - const double b0 = 0.5 * (1.0 - z); - const double b1 = 0.5 * (1.0 + z); +inline void mod_A(const int nummodes, const REAL z, const int k_stride_n, + const REAL *const k_coeffs_pnm10, + const REAL *const k_coeffs_pnm11, + const REAL *const k_coeffs_pnm2, REAL *output) { + const REAL b0 = 0.5 * (1.0 - z); + const REAL b1 = 0.5 * (1.0 + z); output[0] = b0; output[1] = b1; - double pn; - double pnm2 = 1.0; - double pnm1 = 2.0 + 2.0 * (z - 1.0); + REAL pn; + REAL pnm2 = 1.0; + REAL pnm1 = 2.0 + 2.0 * (z - 1.0); if (nummodes > 2) { output[2] = b0 * b1; } @@ -202,9 +163,9 @@ inline void mod_A(const int nummodes, const double z, const int k_stride_n, } for (int modex = 4; modex < nummodes; modex++) { const int nx = modex - 2; - const double c_pnm10 = k_coeffs_pnm10[k_stride_n * 1 + nx]; - const double c_pnm11 = k_coeffs_pnm11[k_stride_n * 1 + nx]; - const double c_pnm2 = k_coeffs_pnm2[k_stride_n * 1 + nx]; + const REAL c_pnm10 = k_coeffs_pnm10[k_stride_n * 1 + nx]; + const REAL c_pnm11 = k_coeffs_pnm11[k_stride_n * 1 + nx]; + const REAL c_pnm2 = k_coeffs_pnm2[k_stride_n * 1 + nx]; pn = c_pnm10 * pnm1 * z + c_pnm11 * pnm1 + c_pnm2 * pnm2; pnm2 = pnm1; pnm1 = pn; @@ -212,6 +173,219 @@ inline void mod_A(const int nummodes, const double z, const int k_stride_n, } } +/** + * Evaluate the eModified_C basis functions up to a given order placing the + * evaluations in an output array. For reference see the function + * eval_modC_ijk. Jacobi polynomials are evaluated using recusion relations: + * + * For brevity the (alpha, beta) superscripts are dropped. i.e. P_n(z) = + * P_n^{alpha, beta}(z). P_n(z) = C_{n-1}^0 P_{n-1}(z) * z + C_{n-1}^1 + * P_{n-1}(z) + C_{n-2} * P_{n-2}(z) P_0(z) = 1 P_1(z) = 2 + 2 * (z - 1) + * + * @param[in] nummodes Number of modes to compute, i.e. p modes evaluates at + * most an order p-1 polynomial. + * @param[in] z Evaluation point to evaluate basis at. + * @param[in] k_stride_n Stride between sets of coefficients for different + * alpha values in the coefficient arrays. + * @param[in] k_coeffs_pnm10 Coefficients for C_{n-1}^0 for different alpha + * values stored row wise for each alpha. + * @param[in] k_coeffs_pnm11 Coefficients for C_{n-1}^1 for different alpha + * values stored row wise for each alpha. + * @param[in] k_coeffs_pnm2 Coefficients for C_{n-2} for different alpha values + * stored row wise for each alpha. + * @param[in, out] output entry i contains the i-th eModified_C basis function + * evaluated at z. + */ +inline void mod_C(const int nummodes, const REAL z, const int k_stride_n, + const REAL *const k_coeffs_pnm10, + const REAL *const k_coeffs_pnm11, + const REAL *const k_coeffs_pnm2, REAL *output) { + + int mode = 0; + const REAL b0 = 0.5 * (1.0 - z); + const REAL b1 = 0.5 * (1.0 + z); + REAL outer_b1_pow = 1.0 / b0; + + for (int p = 0; p < nummodes; p++) { + outer_b1_pow *= b0; + REAL inner_b1_pow = outer_b1_pow; + + for (int q = 0; q < (nummodes - p); q++) { + const int px = p + q; + const int alpha = 2 * px - 1; + REAL pn, pnm1, pnm2; + + for (int r = 0; r < (nummodes - p - q); r++) { + const int qx = r; + REAL etmp1; + // evaluate eModified_B at eta + if (px == 0) { + // evaluate eModified_A(q, eta1) + if (qx == 0) { + etmp1 = b0; + } else if (qx == 1) { + etmp1 = b1; + } else if (qx == 2) { + etmp1 = b0 * b1; + pnm2 = 1.0; + } else if (qx == 3) { + pnm1 = (2.0 + 2.0 * (z - 1.0)); + etmp1 = b0 * b1 * pnm1; + } else { + const int nx = qx - 2; + const REAL c_pnm10 = k_coeffs_pnm10[k_stride_n * 1 + nx]; + const REAL c_pnm11 = k_coeffs_pnm11[k_stride_n * 1 + nx]; + const REAL c_pnm2 = k_coeffs_pnm2[k_stride_n * 1 + nx]; + pn = c_pnm10 * pnm1 * z + c_pnm11 * pnm1 + c_pnm2 * pnm2; + pnm2 = pnm1; + pnm1 = pn; + etmp1 = pn * b0 * b1; + } + } else if (qx == 0) { + etmp1 = inner_b1_pow; + } else { + const int nx = qx - 1; + if (qx == 1) { + pnm2 = 1.0; + etmp1 = inner_b1_pow * b1; + } else if (qx == 2) { + pnm1 = 0.5 * (2.0 * (alpha + 1) + (alpha + 3) * (z - 1.0)); + etmp1 = inner_b1_pow * b1 * pnm1; + } else { + const REAL c_pnm10 = k_coeffs_pnm10[k_stride_n * alpha + nx]; + const REAL c_pnm11 = k_coeffs_pnm11[k_stride_n * alpha + nx]; + const REAL c_pnm2 = k_coeffs_pnm2[k_stride_n * alpha + nx]; + pn = c_pnm10 * pnm1 * z + c_pnm11 * pnm1 + c_pnm2 * pnm2; + pnm2 = pnm1; + pnm1 = pn; + etmp1 = inner_b1_pow * b1 * pn; + } + } + + output[mode] = etmp1; + mode++; + } + inner_b1_pow *= b0; + } + } +} + +/** + * Evaluate the eModified_PyrC basis functions up to a given order placing the + * evaluations in an output array. For reference see the function + * eval_modPyrC_ijk. Jacobi polynomials are evaluated using recusion relations: + * + * For brevity the (alpha, beta) superscripts are dropped. i.e. P_n(z) = + * P_n^{alpha, beta}(z). P_n(z) = C_{n-1}^0 P_{n-1}(z) * z + C_{n-1}^1 + * P_{n-1}(z) + C_{n-2} * P_{n-2}(z) P_0(z) = 1 P_1(z) = 2 + 2 * (z - 1) + * + * @param[in] nummodes Number of modes to compute, i.e. p modes evaluates at + * most an order p-1 polynomial. + * @param[in] z Evaluation point to evaluate basis at. + * @param[in] k_stride_n Stride between sets of coefficients for different + * alpha values in the coefficient arrays. + * @param[in] k_coeffs_pnm10 Coefficients for C_{n-1}^0 for different alpha + * values stored row wise for each alpha. + * @param[in] k_coeffs_pnm11 Coefficients for C_{n-1}^1 for different alpha + * values stored row wise for each alpha. + * @param[in] k_coeffs_pnm2 Coefficients for C_{n-2} for different alpha values + * stored row wise for each alpha. + * @param[in, out] output entry i contains the i-th eModified_PyrC basis + * function evaluated at z. + */ +inline void mod_PyrC(const int nummodes, const REAL z, const int k_stride_n, + const REAL *const k_coeffs_pnm10, + const REAL *const k_coeffs_pnm11, + const REAL *const k_coeffs_pnm2, REAL *output) { + + REAL *output_base = output + nummodes; + + // The p==0 case if an eModified_B basis over indices q,r + mod_B(nummodes, z, k_stride_n, k_coeffs_pnm10, k_coeffs_pnm11, k_coeffs_pnm2, + output); + int mode = nummodes * (nummodes + 1) / 2; + output += mode; + + // The p==1, q==0 case is eModified_B over 1,r + for (int cx = 0; cx < (nummodes - 1); cx++) { + output[cx] = output_base[cx]; + } + mode += nummodes - 1; + output += nummodes - 1; + + // The p==1, q!=0 case is eModified_B over q,r + const int l_tmp = ((nummodes - 1) * (nummodes) / 2); + for (int cx = 0; cx < l_tmp; cx++) { + output[cx] = output_base[cx]; + } + output_base += nummodes - 1; + output += l_tmp; + mode += l_tmp; + + REAL one_m_z = 0.5 * (1.0 - z); + REAL one_p_z = 0.5 * (1.0 + z); + REAL r0_pow = 1.0; + + for (int p = 2; p < nummodes; ++p) { + r0_pow *= one_m_z; + REAL r0_pow_inner = r0_pow; + + // q < 2 case is eModified_B over p,r + const int l_tmp = (nummodes - p); + for (int cx = 0; cx < l_tmp; cx++) { + output[cx] = output_base[cx]; + output[l_tmp + cx] = output_base[cx]; + } + output += 2 * l_tmp; + output_base += l_tmp; + + for (int q = 2; q < nummodes; ++q) { + r0_pow_inner *= one_m_z; + + // p > 1, q > 1, r == 0 term (0.5 * (1 - z) ** (p + q - 2)) + output[0] = r0_pow_inner; + output++; + + REAL pn, pnm1, pnm2; + const int alpha = 2 * p + 2 * q - 3; + int maxpq = max(p, q); + + /* + * The remaining terms are of the form + * std::pow(0.5 * (1.0 - z), p + q - 2) * (0.5 * (1.0 + z)) * + * jacobi(r - 1, z, 2 * p + 2 * q - 3, 1) + * + * where we compute the Jacobi polynomials using the recursion + * relations. + */ + for (int r = 1; r < nummodes - maxpq; ++r) { + // this is the std::pow(0.5 * (1.0 - z), p + q - 2) * (0.5 * (1.0 + z)) + const REAL b0b1_coefficient = r0_pow_inner * one_p_z; + // compute the P_{r-1}^{2p+2q-3, 1} terms using recursion + REAL etmp; + if (r == 1) { + etmp = b0b1_coefficient; + pnm2 = 1.0; + } else if (r == 2) { + pnm1 = 0.5 * (2.0 * (alpha + 1) + (alpha + 3) * (z - 1.0)); + etmp = pnm1 * b0b1_coefficient; + } else { + const int nx = r - 1; + const REAL c_pnm10 = k_coeffs_pnm10[k_stride_n * alpha + nx]; + const REAL c_pnm11 = k_coeffs_pnm11[k_stride_n * alpha + nx]; + const REAL c_pnm2 = k_coeffs_pnm2[k_stride_n * alpha + nx]; + pn = c_pnm10 * pnm1 * z + c_pnm11 * pnm1 + c_pnm2 * pnm2; + pnm2 = pnm1; + pnm1 = pn; + etmp = pn * b0b1_coefficient; + } + output[0] = etmp; + output++; + } + } + } +} + /** * Abstract base class for 1D basis evaluation functions which are based on * Jacobi polynomials. @@ -239,11 +413,10 @@ template struct Basis1D { * values stored row wise for each alpha. * @param[in, out] Output array for evaluations. */ - static inline void evaluate(const int nummodes, const double z, - const int k_stride_n, - const double *k_coeffs_pnm10, - const double *k_coeffs_pnm11, - const double *k_coeffs_pnm2, double *output) { + static inline void evaluate(const int nummodes, const REAL z, + const int k_stride_n, const REAL *k_coeffs_pnm10, + const REAL *k_coeffs_pnm11, + const REAL *k_coeffs_pnm2, REAL *output) { SPECIALISATION::evaluate(nummodes, z, k_stride_n, k_coeffs_pnm10, k_coeffs_pnm11, k_coeffs_pnm2, output); } @@ -254,11 +427,10 @@ template struct Basis1D { * eModified_A. */ struct ModifiedA : Basis1D { - static inline void evaluate(const int nummodes, const double z, - const int k_stride_n, - const double *k_coeffs_pnm10, - const double *k_coeffs_pnm11, - const double *k_coeffs_pnm2, double *output) { + static inline void evaluate(const int nummodes, const REAL z, + const int k_stride_n, const REAL *k_coeffs_pnm10, + const REAL *k_coeffs_pnm11, + const REAL *k_coeffs_pnm2, REAL *output) { mod_A(nummodes, z, k_stride_n, k_coeffs_pnm10, k_coeffs_pnm11, k_coeffs_pnm2, output); } @@ -269,381 +441,45 @@ struct ModifiedA : Basis1D { * eModified_B. */ struct ModifiedB : Basis1D { - static inline void evaluate(const int nummodes, const double z, - const int k_stride_n, - const double *k_coeffs_pnm10, - const double *k_coeffs_pnm11, - const double *k_coeffs_pnm2, double *output) { + static inline void evaluate(const int nummodes, const REAL z, + const int k_stride_n, const REAL *k_coeffs_pnm10, + const REAL *k_coeffs_pnm11, + const REAL *k_coeffs_pnm2, REAL *output) { mod_B(nummodes, z, k_stride_n, k_coeffs_pnm10, k_coeffs_pnm11, k_coeffs_pnm2, output); } }; /** - * Abstract base class for kernels which require basis function evaluations - * indexed by p and q, e.g. eModified_A/B. - */ -template struct LoopingKernelBase { - - /** - * Call a kernel function with indices p and q along with the linearised - * mode corresponding to the indices p and q. - * - * @param p Basis function p index. - * @param p Basis function q index. - * @param mode Linearised mode corresponding to p and q. - */ - inline void kernel(const int px, const int qx, const int mode) { - auto &underlying = static_cast(*this); - underlying.kernel(px, qx, mode); - } -}; - -/** - * Abstract base class for looping over all modes in dimension0 with all modes - * in dimension1. - */ -template struct Indexing2D { - template - - /** - * Double loop over modes: - * For px in [0, nummodes0-1]: - * For qx in [0, nummodes1-1]: - * - * - * @param nummodes0 Number of modes in first dimension. - * @param nummodes1 Number of modes in second dimension. - * @param kernel Kernel to be called for each iteration of the double loop. - */ - static inline void loop(const int nummodes0, const int nummodes1, - LoopingKernelBase &kernel) { - SPECIALISATION::loop(nummodes0, nummodes1, kernel); - } -}; - -/** - * Loop over modes in dimension 0 and 1 for Quadrilateral elements where there - * is an eModified_A basis in dimensions 0 and 1. + * Specialisation of Basis1D that calls the mod_B function that implements + * eModified_C. */ -struct IndexingQuad : Indexing2D { - template - static inline void loop(const int nummodes0, const int nummodes1, - LoopingKernelBase &kernel) { - for (int qx = 0; qx < nummodes1; qx++) { - for (int px = 0; px < nummodes0; px++) { - const int mode = qx * nummodes0 + px; - kernel.kernel(px, qx, mode); - } - } +struct ModifiedC : Basis1D { + static inline void evaluate(const int nummodes, const REAL z, + const int k_stride_n, const REAL *k_coeffs_pnm10, + const REAL *k_coeffs_pnm11, + const REAL *k_coeffs_pnm2, REAL *output) { + mod_C(nummodes, z, k_stride_n, k_coeffs_pnm10, k_coeffs_pnm11, + k_coeffs_pnm2, output); } }; /** - * Loop over modes in dimension 0 and 1 for Triangle elements where there - * is an eModified_A basis and an eModified_B basis. + * Specialisation of Basis1D that calls the mod_B function that implements + * eModifiedPyr_C. */ -struct IndexingTriangle : Indexing2D { - template - static inline void loop(const int nummodes0, const int nummodes1, - LoopingKernelBase &kernel) { - int modey = 0; - for (int px = 0; px < nummodes0; px++) { - for (int qx = 0; qx < nummodes1 - px; qx++) { - const int mode = modey++; - kernel.kernel(px, qx, mode); - } - } +struct ModifiedPyrC : Basis1D { + static inline void evaluate(const int nummodes, const REAL z, + const int k_stride_n, const REAL *k_coeffs_pnm10, + const REAL *k_coeffs_pnm11, + const REAL *k_coeffs_pnm2, REAL *output) { + mod_PyrC(nummodes, z, k_stride_n, k_coeffs_pnm10, k_coeffs_pnm11, + k_coeffs_pnm2, output); } }; } // namespace BasisJacobi -class JacobiCoeffModBasis { - -protected: -public: - /// Disable (implicit) copies. - JacobiCoeffModBasis(const JacobiCoeffModBasis &st) = delete; - /// Disable (implicit) copies. - JacobiCoeffModBasis &operator=(JacobiCoeffModBasis const &a) = delete; - - /** - * Coefficients such that - * P_^{alpha, 1}_{n} = - * (coeffs_pnm10) * P_^{alpha, 1}_{n-1} * z - * + (coeffs_pnm11) * P_^{alpha, 1}_{n-1} - * + (coeffs_pnm2) * P_^{alpha, 1}_{n-2} - * - * Coefficients are stored in a matrix (row major) where each row gives the - * coefficients for a fixed alpha. i.e. the columns are the orders. - */ - std::vector coeffs_pnm10; - std::vector coeffs_pnm11; - std::vector coeffs_pnm2; - - const int max_n; - const int max_alpha; - const int stride_n; - - /** - * Compute coefficients for computing Jacobi polynomial values via recursion - * relation. Coefficients are computed such that: - * P_^{alpha, 1}_{n} = - * (coeffs_pnm10) * P_^{alpha, 1}_{n-1} * z - * + (coeffs_pnm11) * P_^{alpha, 1}_{n-1} - * + (coeffs_pnm2) * P_^{alpha, 1}_{n-2} - * - * @param max_n Maximum polynomial order required. - * @param max_alpha Maximum alpha value required. - */ - JacobiCoeffModBasis(const int max_n, const int max_alpha) - : max_n(max_n), max_alpha(max_alpha), stride_n(max_n + 1) { - - const int beta = 1; - this->coeffs_pnm10.reserve((max_n + 1) * (max_alpha + 1)); - this->coeffs_pnm11.reserve((max_n + 1) * (max_alpha + 1)); - this->coeffs_pnm2.reserve((max_n + 1) * (max_alpha + 1)); - - for (int alphax = 0; alphax <= max_alpha; alphax++) { - for (int nx = 0; nx <= max_n; nx++) { - const double a = nx + alphax; - const double b = nx + beta; - const double c = a + b; - const double n = nx; - - const double c_pn = 2.0 * n * (c - n) * (c - 2.0); - const double c_pnm10 = (c - 1.0) * c * (c - 2); - const double c_pnm11 = (c - 1.0) * (a - b) * (c - 2 * n); - const double c_pnm2 = -2.0 * (a - 1.0) * (b - 1.0) * c; - - const double ic_pn = 1.0 / c_pn; - - this->coeffs_pnm10.push_back(ic_pn * c_pnm10); - this->coeffs_pnm11.push_back(ic_pn * c_pnm11); - this->coeffs_pnm2.push_back(ic_pn * c_pnm2); - } - } - } - - /** - * Compute P^{alpha,1}_n(z) using recursion. - * - * @param n Order of Jacobi polynomial - * @param alpha Alpha value. - * @param z Point to evaluate at. - * @returns P^{alpha,1}_n(z). - */ - inline double host_evaluate(const int n, const int alpha, const double z) { - - double pnm2 = 1.0; - if (n == 0) { - return pnm2; - } - const int beta = 1; - double pnm1 = 0.5 * (2 * (alpha + 1) + (alpha + beta + 2) * (z - 1.0)); - if (n == 1) { - return pnm1; - } - - double pn; - for (int nx = 2; nx <= n; nx++) { - const double c_pnm10 = this->coeffs_pnm10[this->stride_n * alpha + nx]; - const double c_pnm11 = this->coeffs_pnm11[this->stride_n * alpha + nx]; - const double c_pnm2 = this->coeffs_pnm2[this->stride_n * alpha + nx]; - pn = c_pnm10 * pnm1 * z + c_pnm11 * pnm1 + c_pnm2 * pnm2; - pnm2 = pnm1; - pnm1 = pn; - } - - return pn; - } -}; - -/** - * Reference implementation to map xi to eta for TriGeoms. - * - * @param xi XI value. - * @param eta Output pointer for eta. - */ -inline void to_collapsed_triangle(Array &xi, double *eta) { - const REAL xi0 = xi[0]; - const REAL xi1 = xi[1]; - - const NekDouble d1_original = 1.0 - xi1; - const bool mask_small_cond = (fabs(d1_original) < NekConstants::kNekZeroTol); - NekDouble d1 = d1_original; - - d1 = - (mask_small_cond && (d1 >= 0.0)) - ? NekConstants::kNekZeroTol - : ((mask_small_cond && (d1 < 0.0)) ? -NekConstants::kNekZeroTol : d1); - eta[0] = 2. * (1. + xi0) / d1 - 1.0; - eta[1] = xi1; -}; - -/** - * Base class for derived classes that evaluate eModified_A and eModified_B - * Nektar++ basis. - */ -template class BasisEvaluateBase : GeomToExpansionBuilder { -protected: - std::shared_ptr field; - ParticleMeshInterfaceSharedPtr mesh; - CellIDTranslationSharedPtr cell_id_translation; - SYCLTargetSharedPtr sycl_target; - - std::vector cells_quads; - std::vector cells_tris; - - BufferDeviceHost dh_nummodes0; - BufferDeviceHost dh_nummodes1; - BufferDeviceHost dh_cells_quads; - BufferDeviceHost dh_cells_tris; - - BufferDeviceHost dh_coeffs_offsets; - BufferDeviceHost dh_global_coeffs; - - BufferDeviceHost dh_coeffs_pnm10; - BufferDeviceHost dh_coeffs_pnm11; - BufferDeviceHost dh_coeffs_pnm2; - int stride_n; - int max_nummodes_0; - int max_nummodes_1; - - int max_total_nummodes0; - int max_total_nummodes1; - -public: - /// Disable (implicit) copies. - BasisEvaluateBase(const BasisEvaluateBase &st) = delete; - /// Disable (implicit) copies. - BasisEvaluateBase &operator=(BasisEvaluateBase const &a) = delete; - - /** - * Create new instance. Expected to be called by a derived class - not a user. - * - * @param field Example field this class will be used to evaluate basis - * functions for. - * @param mesh Interface between NESO-Particles and Nektar++ meshes. - * @param cell_id_translation Map between NESO-Particles cells and Nektar++ - * cells. - */ - BasisEvaluateBase(std::shared_ptr field, - ParticleMeshInterfaceSharedPtr mesh, - CellIDTranslationSharedPtr cell_id_translation) - : field(field), mesh(mesh), cell_id_translation(cell_id_translation), - sycl_target(cell_id_translation->sycl_target), - dh_nummodes0(sycl_target, 1), dh_nummodes1(sycl_target, 1), - dh_cells_quads(sycl_target, 1), dh_cells_tris(sycl_target, 1), - dh_global_coeffs(sycl_target, 1), dh_coeffs_offsets(sycl_target, 1), - dh_coeffs_pnm10(sycl_target, 1), dh_coeffs_pnm11(sycl_target, 1), - dh_coeffs_pnm2(sycl_target, 1) { - - // build the map from geometry ids to expansion ids - std::map geom_to_exp; - build_geom_to_expansion_map(this->field, geom_to_exp); - - auto geom_type_lookup = - this->cell_id_translation->dh_map_to_geom_type.h_buffer.ptr; - - const int index_tri_geom = - shape_type_to_int(LibUtilities::ShapeType::eTriangle); - const int index_quad_geom = - shape_type_to_int(LibUtilities::ShapeType::eQuadrilateral); - - const int neso_cell_count = mesh->get_cell_count(); - - this->dh_nummodes0.realloc_no_copy(neso_cell_count); - this->dh_nummodes1.realloc_no_copy(neso_cell_count); - this->dh_coeffs_offsets.realloc_no_copy(neso_cell_count); - - int max_n = 1; - int max_alpha = 1; - this->max_nummodes_0 = 0; - this->max_nummodes_1 = 0; - this->max_total_nummodes0 = 0; - this->max_total_nummodes1 = 0; - - for (int neso_cellx = 0; neso_cellx < neso_cell_count; neso_cellx++) { - - const int nektar_geom_id = - this->cell_id_translation->map_to_nektar[neso_cellx]; - const int expansion_id = geom_to_exp[nektar_geom_id]; - // get the nektar expansion - auto expansion = this->field->GetExp(expansion_id); - - auto basis0 = expansion->GetBasis(0); - auto basis1 = expansion->GetBasis(1); - const int nummodes0 = basis0->GetNumModes(); - const int nummodes1 = basis1->GetNumModes(); - - max_total_nummodes0 = - std::max(max_total_nummodes0, basis0->GetTotNumModes()); - max_total_nummodes1 = - std::max(max_total_nummodes1, basis1->GetTotNumModes()); - - this->dh_nummodes0.h_buffer.ptr[neso_cellx] = nummodes0; - this->dh_nummodes1.h_buffer.ptr[neso_cellx] = nummodes1; - - max_n = std::max(max_n, nummodes0 - 1); - max_n = std::max(max_n, nummodes1 - 1); - max_alpha = std::max(max_alpha, (nummodes0 - 1) * 2 - 1); - this->max_nummodes_0 = std::max(this->max_nummodes_0, nummodes0); - this->max_nummodes_1 = std::max(this->max_nummodes_1, nummodes1); - - // is this a tri expansion? - if (geom_type_lookup[neso_cellx] == index_tri_geom) { - this->cells_tris.push_back(neso_cellx); - } - // is this a quad expansion? - if (geom_type_lookup[neso_cellx] == index_quad_geom) { - this->cells_quads.push_back(neso_cellx); - } - - // record offsets and number of coefficients - this->dh_coeffs_offsets.h_buffer.ptr[neso_cellx] = - this->field->GetCoeff_Offset(expansion_id); - } - - NESOASSERT((this->cells_tris.size() + this->cells_quads.size()) == - neso_cell_count, - "Missmatch in number of quad cells triangle cells and total " - "number of cells."); - - JacobiCoeffModBasis jacobi_coeff(max_n, max_alpha); - - this->dh_cells_tris.realloc_no_copy(this->cells_tris.size()); - this->dh_cells_quads.realloc_no_copy(this->cells_quads.size()); - for (int px = 0; px < this->cells_tris.size(); px++) { - this->dh_cells_tris.h_buffer.ptr[px] = this->cells_tris[px]; - } - for (int px = 0; px < this->cells_quads.size(); px++) { - this->dh_cells_quads.h_buffer.ptr[px] = this->cells_quads[px]; - } - - const int num_coeffs = jacobi_coeff.coeffs_pnm10.size(); - this->dh_coeffs_pnm10.realloc_no_copy(num_coeffs); - this->dh_coeffs_pnm11.realloc_no_copy(num_coeffs); - this->dh_coeffs_pnm2.realloc_no_copy(num_coeffs); - for (int cx = 0; cx < num_coeffs; cx++) { - this->dh_coeffs_pnm10.h_buffer.ptr[cx] = jacobi_coeff.coeffs_pnm10[cx]; - this->dh_coeffs_pnm11.h_buffer.ptr[cx] = jacobi_coeff.coeffs_pnm11[cx]; - this->dh_coeffs_pnm2.h_buffer.ptr[cx] = jacobi_coeff.coeffs_pnm2[cx]; - } - this->stride_n = jacobi_coeff.stride_n; - - this->dh_coeffs_offsets.host_to_device(); - this->dh_nummodes0.host_to_device(); - this->dh_nummodes1.host_to_device(); - this->dh_cells_tris.host_to_device(); - this->dh_cells_quads.host_to_device(); - this->dh_coeffs_pnm10.host_to_device(); - this->dh_coeffs_pnm11.host_to_device(); - this->dh_coeffs_pnm2.host_to_device(); - } -}; - } // namespace NESO #endif diff --git a/include/nektar_interface/basis_reference.hpp b/include/nektar_interface/basis_reference.hpp new file mode 100644 index 00000000..7f07ab62 --- /dev/null +++ b/include/nektar_interface/basis_reference.hpp @@ -0,0 +1,167 @@ +#ifndef __BASIS_REFERENCE_H_ +#define __BASIS_REFERENCE_H_ +#include +#include +#include +#include + +#include + +#include "geometry_transport/shape_mapping.hpp" +#include "special_functions.hpp" + +using namespace NESO::Particles; +using namespace Nektar; +using namespace Nektar::LibUtilities; +using namespace Nektar::LocalRegions; +using namespace Nektar::StdRegions; + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace NESO::BasisReference { + +/** + * Reference implementation to compute eModified_A at an order p and point z. + * + * @param p Polynomial order. + * @param z Point in [-1, 1] to evaluate at. + * @returns Basis function evaluated at point. + */ +double eval_modA_i(const int p, const double z); + +/** + * Reference implementation to compute eModified_B at an order p,q and point z. + * + * @param p First index for basis. + * @param q Second index for basis. + * @param z Point in [-1, 1] to evaluate at. + * @returns Basis function evaluated at point. + */ +double eval_modB_ij(const int p, const int q, const double z); + +/** + * Reference implementation to compute eModified_C at an order p,q,r and point + * z. + * + * @param p First index for basis. + * @param q Second index for basis. + * @param r Third index for basis. + * @param z Point in [-1, 1] to evaluate at. + * @returns Basis function evaluated at point. + */ +double eval_modC_ijk(const int p, const int q, const int r, const double z); + +/** + * Reference implementation to compute eModifiedPyr_C at an order p,q,r and + * point z. + * + * @param p First index for basis. + * @param q Second index for basis. + * @param r Third index for basis. + * @param z Point in [-1, 1] to evaluate at. + * @returns Basis function evaluated at point. + */ +double eval_modPyrC_ijk(const int p, const int q, const int r, const double z); + +/** + * Get the total number of modes in a given basis for a given number of input + * modes. See Nektar GetTotNumModes. + * + * @param basis_type Basis type to query number of values for. + * @param P Number of modes, i.e. Nektar GetNumModes(); + * @returns Total number of values required to represent the basis with the + * given number of modes. + */ +int get_total_num_modes(const BasisType basis_type, const int P); + +/** + * Reference implementation to compute eModified_A for order P-1 and point z. + * + * @param[in] P Number of modes to compute. + * @param[in] z Point in [-1, 1] to evaluate at. + * @param[in, out] b Basis functions evaluated at z for each mode. + */ +void eval_modA(const int P, const double z, std::vector &b); + +/** + * Reference implementation to compute eModified_B for order P-1 and point z. + * + * @param[in] P Number of modes to compute. + * @param[in] z Point in [-1, 1] to evaluate at. + * @param[in, out] b Basis functions evaluated at z for each mode. + */ +void eval_modB(const int P, const double z, std::vector &b); + +/** + * Reference implementation to compute eModified_C for order P-1 and point z. + * + * @param[in] P Number of modes to compute. + * @param[in] z Point in [-1, 1] to evaluate at. + * @param[in, out] b Basis functions evaluated at z for each mode. + */ +void eval_modC(const int P, const double z, std::vector &b); + +/** + * Reference implementation to compute eModifiedPyr_C for order P-1 and point + * z. + * + * @param[in] P Number of modes to compute. + * @param[in] z Point in [-1, 1] to evaluate at. + * @param[in, out] b Basis functions evaluated at z for each mode. + */ +void eval_modPyrC(const int P, const double z, std::vector &b); + +/** + * Reference implementation to compute a modified basis for order P-1 and + * point z. + * + * @param[in] basis_type Basis type to compute. + * @param[in] P Number of modes to compute. + * @param[in] z Point in [-1, 1] to evaluate at. + * @param[in, out] b Basis functions evaluated at z for each mode. + */ +void eval_basis(const BasisType basis_type, const int P, const double z, + std::vector &b); + +/** + * Get the total number of modes in a given shape for a given number of input + * modes. + * + * @param[in] shape_type Shape type to query number of values for. + * @param[in] P Number of modes, i.e. Nektar GetNumModes(), in each dimension; + * @param[in, out] max_n (optional) Get the maximum Jacobi polynomial order + * required. + * @param[in, out] max_alpha (optional) Get the maximum Jacobi alpha value + * required. + * @returns Total number of values required to represent the basis with the + * given number of modes. + */ +int get_total_num_modes(const ShapeType shape_type, const int P, + int *max_n = nullptr, int *max_alpha = nullptr); + +/** + * Evaluate all the basis function modes for a geometry object with P modes + * in each coordinate direction using calls to eval_modA, ..., eval_modPyrC. + * + * @param[in] shape_type Geometry shape type to compute modes for, e.g. + * eHexahedron. + * @param[in] P Number of modes in each dimesion. + * @param[in] eta0 Evaluation point, first dimension. + * @param[in] eta1 Evaluation point, second dimension. + * @param[in] eta2 Evaluation point, third dimension. + * @param[in, out] b Output vector of mode evaluations. + */ +void eval_modes(const LibUtilities::ShapeType shape_type, const int P, + const double eta0, const double eta1, const double eta2, + std::vector &b); + +} // namespace NESO::BasisReference + +#endif diff --git a/include/nektar_interface/expansion_looping/basis_evaluate_base.hpp b/include/nektar_interface/expansion_looping/basis_evaluate_base.hpp new file mode 100644 index 00000000..d7eff3f2 --- /dev/null +++ b/include/nektar_interface/expansion_looping/basis_evaluate_base.hpp @@ -0,0 +1,179 @@ +#ifndef __BASIS_EVALUATE_BASE_H_ +#define __BASIS_EVALUATE_BASE_H_ + +#include "geom_to_expansion_builder.hpp" + +namespace NESO { + +/** + * Base class for derived classes that evaluate Nektar++ basis functions to + * evaluate and project onto fields. + */ +template class BasisEvaluateBase : GeomToExpansionBuilder { +protected: + std::shared_ptr field; + ParticleMeshInterfaceSharedPtr mesh; + CellIDTranslationSharedPtr cell_id_translation; + SYCLTargetSharedPtr sycl_target; + + BufferDeviceHost dh_nummodes; + + std::map map_shape_to_count; + std::map> map_shape_to_cells; + std::map>> + map_shape_to_dh_cells; + + BufferDeviceHost dh_coeffs_offsets; + BufferDeviceHost dh_global_coeffs; + BufferDeviceHost dh_coeffs_pnm10; + BufferDeviceHost dh_coeffs_pnm11; + BufferDeviceHost dh_coeffs_pnm2; + int stride_n; + std::map> map_total_nummodes; + +public: + /// Disable (implicit) copies. + BasisEvaluateBase(const BasisEvaluateBase &st) = delete; + /// Disable (implicit) copies. + BasisEvaluateBase &operator=(BasisEvaluateBase const &a) = delete; + + /** + * Create new instance. Expected to be called by a derived class - not a user. + * + * @param field Example field this class will be used to evaluate basis + * functions for. + * @param mesh Interface between NESO-Particles and Nektar++ meshes. + * @param cell_id_translation Map between NESO-Particles cells and Nektar++ + * cells. + */ + BasisEvaluateBase(std::shared_ptr field, + ParticleMeshInterfaceSharedPtr mesh, + CellIDTranslationSharedPtr cell_id_translation) + : field(field), mesh(mesh), cell_id_translation(cell_id_translation), + sycl_target(cell_id_translation->sycl_target), + dh_nummodes(sycl_target, 1), dh_global_coeffs(sycl_target, 1), + dh_coeffs_offsets(sycl_target, 1), dh_coeffs_pnm10(sycl_target, 1), + dh_coeffs_pnm11(sycl_target, 1), dh_coeffs_pnm2(sycl_target, 1) { + + // build the map from geometry ids to expansion ids + std::map geom_to_exp; + build_geom_to_expansion_map(this->field, geom_to_exp); + + auto geom_type_lookup = + this->cell_id_translation->dh_map_to_geom_type.h_buffer.ptr; + + const int index_tri_geom = + shape_type_to_int(LibUtilities::ShapeType::eTriangle); + const int index_quad_geom = + shape_type_to_int(LibUtilities::ShapeType::eQuadrilateral); + + const int neso_cell_count = mesh->get_cell_count(); + + this->dh_nummodes.realloc_no_copy(neso_cell_count); + this->dh_coeffs_offsets.realloc_no_copy(neso_cell_count); + + int max_n = 1; + int max_alpha = 1; + + std::array shapes = {eTriangle, eQuadrilateral, eHexahedron, + ePrism, ePyramid, eTetrahedron}; + for (auto shape : shapes) { + this->map_shape_to_count[shape] = 0; + this->map_shape_to_count[shape] = 0; + for (int dimx = 0; dimx < 3; dimx++) { + this->map_total_nummodes[shape][dimx] = 0; + } + } + + for (int neso_cellx = 0; neso_cellx < neso_cell_count; neso_cellx++) { + + const int nektar_geom_id = + this->cell_id_translation->map_to_nektar[neso_cellx]; + const int expansion_id = geom_to_exp[nektar_geom_id]; + // get the nektar expansion + auto expansion = this->field->GetExp(expansion_id); + auto basis = expansion->GetBase(); + const int expansion_ndim = basis.size(); + + // build the map from shape types to neso cells + auto shape_type = expansion->DetShapeType(); + this->map_shape_to_cells[shape_type].push_back(neso_cellx); + + for (int dimx = 0; dimx < expansion_ndim; dimx++) { + const int basis_nummodes = basis[dimx]->GetNumModes(); + const int basis_total_nummodes = basis[dimx]->GetTotNumModes(); + max_n = std::max(max_n, basis_nummodes - 1); + if (dimx == 0) { + this->dh_nummodes.h_buffer.ptr[neso_cellx] = basis_nummodes; + } else { + NESOASSERT(this->dh_nummodes.h_buffer.ptr[neso_cellx] == + basis_nummodes, + "Differing numbers of modes in coordinate directions."); + } + this->map_total_nummodes.at(shape_type).at(dimx) = + std::max(this->map_total_nummodes.at(shape_type).at(dimx), + basis_total_nummodes); + } + + // determine the maximum Jacobi order and alpha value required to + // evaluate the basis functions for this expansion + int alpha_tmp = 0; + int n_tmp = 0; + BasisReference::get_total_num_modes( + shape_type, this->dh_nummodes.h_buffer.ptr[neso_cellx], &n_tmp, + &alpha_tmp); + max_alpha = std::max(max_alpha, alpha_tmp); + max_n = std::max(max_n, n_tmp); + + // record offsets and number of coefficients + this->dh_coeffs_offsets.h_buffer.ptr[neso_cellx] = + this->field->GetCoeff_Offset(expansion_id); + } + + int expansion_count = 0; + // create the maps from shape types to NESO::Particles cells which + // correpond to the shape type. + + for (auto &item : this->map_shape_to_cells) { + expansion_count += item.second.size(); + auto shape_type = item.first; + auto &cells = item.second; + const int num_cells = cells.size(); + // allocate and build the map. + this->map_shape_to_dh_cells[shape_type] = + std::make_unique>(this->sycl_target, num_cells); + for (int cellx = 0; cellx < num_cells; cellx++) { + const int cell = cells[cellx]; + this->map_shape_to_dh_cells[shape_type]->h_buffer.ptr[cellx] = cell; + } + this->map_shape_to_dh_cells[shape_type]->host_to_device(); + this->map_shape_to_count[shape_type] = num_cells; + } + + NESOASSERT(expansion_count == neso_cell_count, + "Missmatch in number of cells found and total number of cells."); + + JacobiCoeffModBasis jacobi_coeff(max_n, max_alpha); + + const int num_coeffs = jacobi_coeff.coeffs_pnm10.size(); + this->dh_coeffs_pnm10.realloc_no_copy(num_coeffs); + this->dh_coeffs_pnm11.realloc_no_copy(num_coeffs); + this->dh_coeffs_pnm2.realloc_no_copy(num_coeffs); + for (int cx = 0; cx < num_coeffs; cx++) { + this->dh_coeffs_pnm10.h_buffer.ptr[cx] = jacobi_coeff.coeffs_pnm10[cx]; + this->dh_coeffs_pnm11.h_buffer.ptr[cx] = jacobi_coeff.coeffs_pnm11[cx]; + this->dh_coeffs_pnm2.h_buffer.ptr[cx] = jacobi_coeff.coeffs_pnm2[cx]; + } + this->stride_n = jacobi_coeff.stride_n; + + this->dh_coeffs_offsets.host_to_device(); + this->dh_nummodes.host_to_device(); + this->dh_coeffs_pnm10.host_to_device(); + this->dh_coeffs_pnm11.host_to_device(); + this->dh_coeffs_pnm2.host_to_device(); + } +}; + +} // namespace NESO + +#endif diff --git a/include/nektar_interface/expansion_looping/expansion_looping.hpp b/include/nektar_interface/expansion_looping/expansion_looping.hpp new file mode 100644 index 00000000..eac5df98 --- /dev/null +++ b/include/nektar_interface/expansion_looping/expansion_looping.hpp @@ -0,0 +1,12 @@ +#ifndef __EXPANSION_LOOPING_H__ +#define __EXPANSION_LOOPING_H__ + +#include "hexahedron.hpp" +#include "jacobi_expansion_looping_interface.hpp" +#include "prism.hpp" +#include "pyramid.hpp" +#include "quadrilateral.hpp" +#include "tetrahedron.hpp" +#include "triangle.hpp" + +#endif diff --git a/include/nektar_interface/function_coupling_base.hpp b/include/nektar_interface/expansion_looping/geom_to_expansion_builder.hpp similarity index 90% rename from include/nektar_interface/function_coupling_base.hpp rename to include/nektar_interface/expansion_looping/geom_to_expansion_builder.hpp index cee006e8..030ae727 100644 --- a/include/nektar_interface/function_coupling_base.hpp +++ b/include/nektar_interface/expansion_looping/geom_to_expansion_builder.hpp @@ -1,6 +1,8 @@ #ifndef __FUNCTION_COUPLING_BASE_H_ #define __FUNCTION_COUPLING_BASE_H_ -#include "particle_interface.hpp" +#include "nektar_interface/basis_reference.hpp" +#include "nektar_interface/expansion_looping/jacobi_coeff_mod_basis.hpp" +#include "nektar_interface/particle_interface.hpp" #include #include #include diff --git a/include/nektar_interface/expansion_looping/hexahedron.hpp b/include/nektar_interface/expansion_looping/hexahedron.hpp new file mode 100644 index 00000000..c1d01a8d --- /dev/null +++ b/include/nektar_interface/expansion_looping/hexahedron.hpp @@ -0,0 +1,100 @@ +#ifndef __EXPANSION_LOOPING_HEXAHEDRON_H__ +#define __EXPANSION_LOOPING_HEXAHEDRON_H__ + +#include "jacobi_expansion_looping_interface.hpp" + +namespace NESO::ExpansionLooping { + +/** + * Implements evaluation and projection for Hexahedron elements with + * eModified_A basis functions in each dimension. + */ +struct Hexahedron : JacobiExpansionLoopingInterface { + + inline void loc_coord_to_loc_collapsed_v(const REAL xi0, const REAL xi1, + const REAL xi2, REAL *eta0, + REAL *eta1, REAL *eta2) { + GeometryInterface::Hexahedron geom{}; + geom.loc_coord_to_loc_collapsed(xi0, xi1, xi2, eta0, eta1, eta2); + } + + inline void evaluate_basis_0_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + + BasisJacobi::ModifiedA::evaluate(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + inline void evaluate_basis_1_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + + BasisJacobi::ModifiedA::evaluate(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + inline void evaluate_basis_2_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + BasisJacobi::ModifiedA::evaluate(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + + inline void loop_evaluate_v(const int nummodes, const REAL *const dofs, + const REAL *const local_space_0, + const REAL *const local_space_1, + const REAL *const local_space_2, REAL *output) { + REAL evaluation = 0.0; + for (int rx = 0; rx < nummodes; rx++) { + const int mode_r = rx * nummodes * nummodes; + const REAL etmp2 = local_space_2[rx]; + for (int qx = 0; qx < nummodes; qx++) { + const int mode_q = qx * nummodes + mode_r; + const REAL etmp1 = local_space_1[qx] * etmp2; + for (int px = 0; px < nummodes; px++) { + const int mode = px + mode_q; + const REAL coeff = dofs[mode]; + const REAL etmp0 = local_space_0[px]; + evaluation += coeff * etmp0 * etmp1; + } + } + } + *output = evaluation; + } + + inline void loop_project_v(const int nummodes, const REAL value, + const REAL *const local_space_0, + const REAL *const local_space_1, + const REAL *const local_space_2, REAL *dofs) { + + for (int rx = 0; rx < nummodes; rx++) { + const int mode_r = rx * nummodes * nummodes; + const REAL etmp2 = local_space_2[rx] * value; + for (int qx = 0; qx < nummodes; qx++) { + const int mode_q = qx * nummodes + mode_r; + const REAL etmp1 = local_space_1[qx] * etmp2; + for (int px = 0; px < nummodes; px++) { + const int mode = px + mode_q; + const REAL evaluation = local_space_0[px] * etmp1; + sycl::atomic_ref + coeff_atomic_ref(dofs[mode]); + coeff_atomic_ref.fetch_add(evaluation); + } + } + } + } + + inline ShapeType get_shape_type_v() { return eHexahedron; } + + inline int get_ndim_v() { return 3; } +}; + +} // namespace NESO::ExpansionLooping + +#endif diff --git a/include/nektar_interface/expansion_looping/jacobi_coeff_mod_basis.hpp b/include/nektar_interface/expansion_looping/jacobi_coeff_mod_basis.hpp new file mode 100644 index 00000000..f6b6c0db --- /dev/null +++ b/include/nektar_interface/expansion_looping/jacobi_coeff_mod_basis.hpp @@ -0,0 +1,74 @@ +#ifndef __JACOBI_COEFF_MOD_BASIS_H_ +#define __JACOBI_COEFF_MOD_BASIS_H_ + +#include +#include +#include +#include + +#include +#include +#include + +using namespace NESO::Particles; + +namespace NESO { + +/** + * Pre-compute the coefficients required to compute series of Jacobi + * polynomials via recursion. + */ +class JacobiCoeffModBasis { + +protected: +public: + /// Disable (implicit) copies. + JacobiCoeffModBasis(const JacobiCoeffModBasis &st) = delete; + /// Disable (implicit) copies. + JacobiCoeffModBasis &operator=(JacobiCoeffModBasis const &a) = delete; + + /** + * Coefficients such that + * P_^{alpha, 1}_{n} = + * (coeffs_pnm10) * P_^{alpha, 1}_{n-1} * z + * + (coeffs_pnm11) * P_^{alpha, 1}_{n-1} + * + (coeffs_pnm2) * P_^{alpha, 1}_{n-2} + * + * Coefficients are stored in a matrix (row major) where each row gives the + * coefficients for a fixed alpha. i.e. the columns are the orders. + */ + std::vector coeffs_pnm10; + std::vector coeffs_pnm11; + std::vector coeffs_pnm2; + + const int max_n; + const int max_alpha; + const int stride_n; + + /** + * Compute coefficients for computing Jacobi polynomial values via recursion + * relation. Coefficients are computed such that: + * P_^{alpha, 1}_{n} = + * (coeffs_pnm10) * P_^{alpha, 1}_{n-1} * z + * + (coeffs_pnm11) * P_^{alpha, 1}_{n-1} + * + (coeffs_pnm2) * P_^{alpha, 1}_{n-2} + * + * @param max_n Maximum polynomial order required. + * @param max_alpha Maximum alpha value required. + */ + JacobiCoeffModBasis(const int max_n, const int max_alpha); + + /** + * Compute P^{alpha,1}_n(z) using recursion. + * + * @param n Order of Jacobi polynomial + * @param alpha Alpha value. + * @param z Point to evaluate at. + * @returns P^{alpha,1}_n(z). + */ + double host_evaluate(const int n, const int alpha, const double z); +}; + +} // namespace NESO + +#endif diff --git a/include/nektar_interface/expansion_looping/jacobi_expansion_looping_interface.hpp b/include/nektar_interface/expansion_looping/jacobi_expansion_looping_interface.hpp new file mode 100644 index 00000000..38c8f742 --- /dev/null +++ b/include/nektar_interface/expansion_looping/jacobi_expansion_looping_interface.hpp @@ -0,0 +1,213 @@ +#ifndef __JACOBI_EXPANSION_LOOPING_INTERFACE_H__ +#define __JACOBI_EXPANSION_LOOPING_INTERFACE_H__ + +#include "../basis_evaluation.hpp" +#include "../coordinate_mapping.hpp" +#include +#include +#include + +using namespace Nektar::SpatialDomains; +using namespace NESO; +using namespace NESO::Particles; + +namespace NESO::ExpansionLooping { + +/** + * Abstract base class for projection and evaluation implementations for each + * element type. Assumes that the derived classes all require evaluation of + * Jacobi polynomials (with beta=1). i.e. the methods will receive coefficients + * such that + * + * P_^{alpha, 1}_{n} = + * (coeffs_pnm10) * P_^{alpha, 1}_{n-1} * z + * + (coeffs_pnm11) * P_^{alpha, 1}_{n-1} + * + (coeffs_pnm2) * P_^{alpha, 1}_{n-2} + * + * See JacobiCoeffModBasis for further details relating to coefficient + * computation. + */ +template struct JacobiExpansionLoopingInterface { + + /** + * Compute the collapsed coordinate for a given input local coordinate. + * + * @param[in] xi0 Local coordinate, x component. + * @param[in] xi1 Local coordinate, y component. + * @param[in] xi2 Local coordinate, z component. + * @param[out] eta0 Local collapsed coordinate, x component. + * @param[out] eta1 Local collapsed coordinate, y component. + * @param[out] eta2 Local collapsed coordinate, z component. + */ + inline void loc_coord_to_loc_collapsed(const REAL xi0, const REAL xi1, + const REAL xi2, REAL *eta0, REAL *eta1, + REAL *eta2) { + auto &underlying = static_cast(*this); + underlying.loc_coord_to_loc_collapsed_v(xi0, xi1, xi2, eta0, eta1, eta2); + } + + /** + * Evaluate the set of basis functions in the x direction of the reference + * element. + * + * @param[in] nummodes Number of modes in the expansion. + * @param[in] z Point to evaluate each of the basis functions at. + * @param[in] coeffs_stride Integer stride required to index into Jacobi + * coefficients. + * @param[in] coeffs_pnm10 First set of coefficients for Jacobi recursion. + * @param[in] coeffs_pnm11 Second set of coefficients for Jacobi recursion. + * @param[in] coeffs_pnm2 Third set of coefficients for Jacobi recursion. + * @param[out] output Output array with size at least the total number of + * modes for the expansion with nummodes. + */ + inline void evaluate_basis_0(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + auto &underlying = static_cast(*this); + underlying.evaluate_basis_0_v(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + + /** + * Evaluate the set of basis functions in the y direction of the reference + * element. + * + * @param[in] nummodes Number of modes in the expansion. + * @param[in] z Point to evaluate each of the basis functions at. + * @param[in] coeffs_stride Integer stride required to index into Jacobi + * coefficients. + * @param[in] coeffs_pnm10 First set of coefficients for Jacobi recursion. + * @param[in] coeffs_pnm11 Second set of coefficients for Jacobi recursion. + * @param[in] coeffs_pnm2 Third set of coefficients for Jacobi recursion. + * @param[out] output Output array with size at least the total number of + * modes for the expansion with nummodes. + */ + inline void evaluate_basis_1(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + + auto &underlying = static_cast(*this); + underlying.evaluate_basis_1_v(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + + /** + * Evaluate the set of basis functions in the z direction of the reference + * element. + * + * @param[in] nummodes Number of modes in the expansion. + * @param[in] z Point to evaluate each of the basis functions at. + * @param[in] coeffs_stride Integer stride required to index into Jacobi + * coefficients. + * @param[in] coeffs_pnm10 First set of coefficients for Jacobi recursion. + * @param[in] coeffs_pnm11 Second set of coefficients for Jacobi recursion. + * @param[in] coeffs_pnm2 Third set of coefficients for Jacobi recursion. + * @param[out] output Output array with size at least the total number of + * modes for the expansion with nummodes. + */ + inline void evaluate_basis_2(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + auto &underlying = static_cast(*this); + underlying.evaluate_basis_2_v(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + + /** + * Construct each mode of the expansion over the element using the expansions + * in each direction of the reference element. Multiply each of these modes + * with the corresponding degree of freedom (coefficient) and sum the result. + * Mathematically this method computes and returns + * + * \f[ + * \sum_{i} \phi_i(x) \alpha_i, + * \f] + * + * where \f$\phi_i\f$ and \f$\alpha_i\f$ are the \f$i\f$-th basis function + * and degree of freedom respectively. + * + * @param[in] nummodes Number of modes in the expansion. + * @param[in] dofs Pointer to degrees of freedom (\f$\alpha_i\f$) to use when + * evaluating the expansion. + * @param[in] local_space_0 Output of `evaluate_basis_0`. + * @param[in] local_space_1 Output of `evaluate_basis_1`. + * @param[in] local_space_2 Output of `evaluate_basis_2`. + * @param[output] output Output space for the evaluation (pointer to a single + * REAL). + */ + inline void loop_evaluate(const int nummodes, const REAL *dofs, + const REAL *local_space_0, + const REAL *local_space_1, + const REAL *local_space_2, REAL *output) { + auto &underlying = static_cast(*this); + underlying.loop_evaluate_v(nummodes, dofs, local_space_0, local_space_1, + local_space_2, output); + } + + /** + * Construct each mode of the expansion over the element using the expansions + * in each direction of the reference element. For each basis function + * \f$i\f$, atomically increment the i-th element in the RHS of + * + * \f[ + * M \vec{a} = \vec{\psi}, + * \f] + * + * where + * \f[ + * \vec{\psi}_i = \sum{\text{particles}~j} \phi_i(\vec{r}_j) q_i, + * \f] + * \f$\vec{r}_j\f$ is the position of particle \f$j\f$, \f$q_i\f$ is the + * quantity of interest on particle \f$i$\f and \f$M\f$ is the system mass + * matrix. + * + * @param[in] nummodes Number of modes in the expansion. + * @param[in] value Pointer to degrees of freedom (\f$\alpha_i\f$) to use when + * evaluating the expansion. + * @param[in] local_space_0 Output of `evaluate_basis_0`. + * @param[in] local_space_1 Output of `evaluate_basis_1`. + * @param[in] local_space_2 Output of `evaluate_basis_2`. + * @param[output] output Output space for the evaluation of each basis + * function times quantity of interest. + */ + inline void loop_project(const int nummodes, const REAL value, + const REAL *local_space_0, const REAL *local_space_1, + const REAL *local_space_2, REAL *dofs) { + auto &underlying = static_cast(*this); + underlying.loop_project_v(nummodes, value, local_space_0, local_space_1, + local_space_2, dofs); + } + + /** + * Return the number of coordinate dimensions this expansion is based. i.e. + * if there are only two dimensions then calls to the methods in this class + * will pass dummy values for z components. + * + * @returns Number of spatial dimensions. + */ + inline int get_ndim() { + auto &underlying = static_cast(*this); + return underlying.get_ndim_v(); + } + + /** + * Return the Nektar++ enumeration for the element type the implementation + * computes evaluation and projection for. + * + * @returns Nektar++ shape type enumeration + */ + inline ShapeType get_shape_type() { + auto &underlying = static_cast(*this); + return underlying.get_shape_type_v(); + } +}; + +} // namespace NESO::ExpansionLooping + +#endif diff --git a/include/nektar_interface/expansion_looping/prism.hpp b/include/nektar_interface/expansion_looping/prism.hpp new file mode 100644 index 00000000..eabbf618 --- /dev/null +++ b/include/nektar_interface/expansion_looping/prism.hpp @@ -0,0 +1,111 @@ +#ifndef __EXPANSION_LOOPING_PRISM_H__ +#define __EXPANSION_LOOPING_PRISM_H__ + +#include "jacobi_expansion_looping_interface.hpp" + +namespace NESO::ExpansionLooping { + +/** + * Implements evaluation and projection for Prism elements with eModified_A/B + * basis functions. + */ +struct Prism : JacobiExpansionLoopingInterface { + + inline void loc_coord_to_loc_collapsed_v(const REAL xi0, const REAL xi1, + const REAL xi2, REAL *eta0, + REAL *eta1, REAL *eta2) { + GeometryInterface::Prism geom{}; + geom.loc_coord_to_loc_collapsed(xi0, xi1, xi2, eta0, eta1, eta2); + } + + inline void evaluate_basis_0_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + BasisJacobi::ModifiedA::evaluate(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + inline void evaluate_basis_1_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + BasisJacobi::ModifiedA::evaluate(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + inline void evaluate_basis_2_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + BasisJacobi::ModifiedB::evaluate(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + + inline void loop_evaluate_v(const int nummodes, const REAL *const dofs, + const REAL *const local_space_0, + const REAL *const local_space_1, + const REAL *const local_space_2, REAL *output) { + REAL evaluation = 0.0; + int mode = 0; + int mode_r = 0; + for (int p = 0; p < nummodes; p++) { + const REAL etmp0 = local_space_0[p]; + for (int q = 0; q < nummodes; q++) { + const REAL etmp1 = local_space_1[q]; + for (int r = 0; r < nummodes - p; r++) { + const REAL etmp2 = local_space_2[mode_r + r]; + REAL tmp; + if ((p == 0) && (r == 1)) { + tmp = etmp1 * etmp2; + } else { + tmp = etmp0 * etmp1 * etmp2; + } + const REAL coeff = dofs[mode]; + evaluation += coeff * tmp; + mode++; + } + } + mode_r += nummodes - p; + } + *output = evaluation; + } + + inline void loop_project_v(const int nummodes, const REAL value, + const REAL *const local_space_0, + const REAL *const local_space_1, + const REAL *const local_space_2, REAL *dofs) { + int mode = 0; + int mode_r = 0; + for (int p = 0; p < nummodes; p++) { + const REAL etmp0 = local_space_0[p]; + for (int q = 0; q < nummodes; q++) { + const REAL etmp1 = local_space_1[q]; + for (int r = 0; r < nummodes - p; r++) { + const REAL etmp2 = local_space_2[mode_r + r]; + REAL evaluation; + if ((p == 0) && (r == 1)) { + evaluation = value * etmp1 * etmp2; + } else { + evaluation = value * etmp0 * etmp1 * etmp2; + } + sycl::atomic_ref + coeff_atomic_ref(dofs[mode]); + coeff_atomic_ref.fetch_add(evaluation); + mode++; + } + } + mode_r += nummodes - p; + } + } + + inline ShapeType get_shape_type_v() { return ePrism; } + + inline int get_ndim_v() { return 3; } +}; + +} // namespace NESO::ExpansionLooping + +#endif diff --git a/include/nektar_interface/expansion_looping/pyramid.hpp b/include/nektar_interface/expansion_looping/pyramid.hpp new file mode 100644 index 00000000..d1ceec51 --- /dev/null +++ b/include/nektar_interface/expansion_looping/pyramid.hpp @@ -0,0 +1,111 @@ +#ifndef __EXPANSION_LOOPING_PYRAMID_H__ +#define __EXPANSION_LOOPING_PYRAMID_H__ + +#include "jacobi_expansion_looping_interface.hpp" + +namespace NESO::ExpansionLooping { + +/** + * Implements evaluation and projection for Pyramid elements with + * eModified_A/A/PyrC basis functions. + */ +struct Pyramid : JacobiExpansionLoopingInterface { + + inline void loc_coord_to_loc_collapsed_v(const REAL xi0, const REAL xi1, + const REAL xi2, REAL *eta0, + REAL *eta1, REAL *eta2) { + GeometryInterface::Pyramid geom{}; + geom.loc_coord_to_loc_collapsed(xi0, xi1, xi2, eta0, eta1, eta2); + } + + inline void evaluate_basis_0_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + + BasisJacobi::ModifiedA::evaluate(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + inline void evaluate_basis_1_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + + BasisJacobi::ModifiedA::evaluate(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + inline void evaluate_basis_2_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + BasisJacobi::ModifiedPyrC::evaluate(nummodes, z, coeffs_stride, + coeffs_pnm10, coeffs_pnm11, coeffs_pnm2, + output); + } + + inline void loop_evaluate_v(const int nummodes, const REAL *const dofs, + const REAL *const local_space_0, + const REAL *const local_space_1, + const REAL *const local_space_2, REAL *output) { + REAL evaluation = 0.0; + int mode = 0; + for (int p = 0; p < nummodes; p++) { + const REAL etmp0 = local_space_0[p]; + for (int q = 0; q < nummodes; q++) { + const REAL etmp1 = local_space_1[q]; + const int l = std::max(p, q); + for (int r = 0; r < nummodes - l; r++) { + const REAL etmp2 = local_space_2[mode]; + const REAL coeff = dofs[mode]; + if (mode == 1) { + evaluation += coeff * etmp2; + } else { + evaluation += coeff * etmp0 * etmp1 * etmp2; + } + mode++; + } + } + } + *output = evaluation; + } + + inline void loop_project_v(const int nummodes, const REAL value, + const REAL *const local_space_0, + const REAL *const local_space_1, + const REAL *const local_space_2, REAL *dofs) { + int mode = 0; + for (int p = 0; p < nummodes; p++) { + const REAL etmp0 = local_space_0[p]; + for (int q = 0; q < nummodes; q++) { + const REAL etmp1 = local_space_1[q]; + const int l = std::max(p, q); + for (int r = 0; r < nummodes - l; r++) { + const REAL etmp2 = local_space_2[mode]; + const REAL coeff = dofs[mode]; + REAL evaluation; + if (mode == 1) { + evaluation = value * etmp2; + } else { + evaluation = value * etmp0 * etmp1 * etmp2; + } + sycl::atomic_ref + coeff_atomic_ref(dofs[mode]); + coeff_atomic_ref.fetch_add(evaluation); + mode++; + } + } + } + } + + inline ShapeType get_shape_type_v() { return ePyramid; } + + inline int get_ndim_v() { return 3; } +}; + +} // namespace NESO::ExpansionLooping + +#endif diff --git a/include/nektar_interface/expansion_looping/quadrilateral.hpp b/include/nektar_interface/expansion_looping/quadrilateral.hpp new file mode 100644 index 00000000..3a81deb5 --- /dev/null +++ b/include/nektar_interface/expansion_looping/quadrilateral.hpp @@ -0,0 +1,90 @@ +#ifndef __EXPANSION_LOOPING_QUADRILATERAL_H__ +#define __EXPANSION_LOOPING_QUADRILATERAL_H__ + +#include "jacobi_expansion_looping_interface.hpp" + +namespace NESO::ExpansionLooping { + +/** + * Implements evaluation and projection for Quadrilateral elements with + * eModified_A basis functions in each dimension. + */ +struct Quadrilateral : JacobiExpansionLoopingInterface { + + inline void loc_coord_to_loc_collapsed_v(const REAL xi0, const REAL xi1, + const REAL xi2, REAL *eta0, + REAL *eta1, REAL *eta2) { + GeometryInterface::Quadrilateral geom{}; + geom.loc_coord_to_loc_collapsed(xi0, xi1, eta0, eta1); + } + + inline void evaluate_basis_0_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + + BasisJacobi::ModifiedA::evaluate(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + inline void evaluate_basis_1_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + + BasisJacobi::ModifiedA::evaluate(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + inline void evaluate_basis_2_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) {} + + inline void loop_evaluate_v(const int nummodes, const REAL *const dofs, + const REAL *const local_space_0, + const REAL *const local_space_1, + const REAL *const local_space_2, REAL *output) { + int mode = 0; + REAL evaluation = 0.0; + for (int qx = 0; qx < nummodes; qx++) { + for (int px = 0; px < nummodes; px++) { + const int mode = qx * nummodes + px; + const REAL coeff = dofs[mode]; + const REAL etmp0 = local_space_0[px]; + const REAL etmp1 = local_space_1[qx]; + evaluation += coeff * etmp0 * etmp1; + } + } + *output = evaluation; + } + + inline void loop_project_v(const int nummodes, const REAL value, + const REAL *const local_space_0, + const REAL *const local_space_1, + const REAL *const local_space_2, REAL *dofs) { + + for (int qx = 0; qx < nummodes; qx++) { + for (int px = 0; px < nummodes; px++) { + const int mode = qx * nummodes + px; + const REAL etmp0 = local_space_0[px]; + const REAL etmp1 = local_space_1[qx]; + + const REAL evaluation = value * etmp0 * etmp1; + sycl::atomic_ref + coeff_atomic_ref(dofs[mode]); + coeff_atomic_ref.fetch_add(evaluation); + } + } + } + + inline ShapeType get_shape_type_v() { return eQuadrilateral; } + + inline int get_ndim_v() { return 2; } +}; + +} // namespace NESO::ExpansionLooping + +#endif diff --git a/include/nektar_interface/expansion_looping/tetrahedron.hpp b/include/nektar_interface/expansion_looping/tetrahedron.hpp new file mode 100644 index 00000000..80ccf73e --- /dev/null +++ b/include/nektar_interface/expansion_looping/tetrahedron.hpp @@ -0,0 +1,115 @@ +#ifndef __EXPANSION_LOOPING_TETRAHEDRON_H__ +#define __EXPANSION_LOOPING_TETRAHEDRON_H__ + +#include "jacobi_expansion_looping_interface.hpp" + +namespace NESO::ExpansionLooping { + +/** + * Implements evaluation and projection for Prism elements with eModified_A/B/C + * basis functions. + */ +struct Tetrahedron : JacobiExpansionLoopingInterface { + + inline void loc_coord_to_loc_collapsed_v(const REAL xi0, const REAL xi1, + const REAL xi2, REAL *eta0, + REAL *eta1, REAL *eta2) { + GeometryInterface::Tetrahedron geom{}; + geom.loc_coord_to_loc_collapsed(xi0, xi1, xi2, eta0, eta1, eta2); + } + + inline void evaluate_basis_0_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + + BasisJacobi::ModifiedA::evaluate(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + inline void evaluate_basis_1_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + + BasisJacobi::ModifiedB::evaluate(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + inline void evaluate_basis_2_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + BasisJacobi::ModifiedC::evaluate(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + + inline void loop_evaluate_v(const int nummodes, const REAL *const dofs, + const REAL *const local_space_0, + const REAL *const local_space_1, + const REAL *const local_space_2, REAL *output) { + REAL evaluation = 0.0; + int mode = 0; + int mode_q = 0; + for (int p = 0; p < nummodes; p++) { + const REAL etmp0 = local_space_0[p]; + for (int q = 0; q < (nummodes - p); q++) { + const REAL etmp1 = local_space_1[mode_q]; + mode_q++; + for (int r = 0; r < nummodes - p - q; r++) { + const REAL etmp2 = local_space_2[mode]; + const REAL coeff = dofs[mode]; + if (mode == 1) { + evaluation += coeff * etmp2; + } else if (p == 0 && q == 1) { + evaluation += coeff * etmp1 * etmp2; + } else { + evaluation += coeff * etmp0 * etmp1 * etmp2; + } + mode++; + } + } + } + *output = evaluation; + } + + inline void loop_project_v(const int nummodes, const REAL value, + const REAL *const local_space_0, + const REAL *const local_space_1, + const REAL *const local_space_2, REAL *dofs) { + int mode = 0; + int mode_q = 0; + for (int p = 0; p < nummodes; p++) { + const REAL etmp0 = local_space_0[p]; + for (int q = 0; q < (nummodes - p); q++) { + const REAL etmp1 = local_space_1[mode_q]; + mode_q++; + for (int r = 0; r < nummodes - p - q; r++) { + const REAL etmp2 = local_space_2[mode]; + REAL evaluation; + if (mode == 1) { + evaluation = value * etmp2; + } else if (p == 0 && q == 1) { + evaluation = value * etmp1 * etmp2; + } else { + evaluation = value * etmp0 * etmp1 * etmp2; + } + sycl::atomic_ref + coeff_atomic_ref(dofs[mode]); + coeff_atomic_ref.fetch_add(evaluation); + mode++; + } + } + } + } + + inline ShapeType get_shape_type_v() { return eTetrahedron; } + + inline int get_ndim_v() { return 3; } +}; + +} // namespace NESO::ExpansionLooping + +#endif diff --git a/include/nektar_interface/expansion_looping/triangle.hpp b/include/nektar_interface/expansion_looping/triangle.hpp new file mode 100644 index 00000000..16e47663 --- /dev/null +++ b/include/nektar_interface/expansion_looping/triangle.hpp @@ -0,0 +1,91 @@ +#ifndef __EXPANSION_LOOPING_TRIANGLE_H__ +#define __EXPANSION_LOOPING_TRIANGLE_H__ + +#include "jacobi_expansion_looping_interface.hpp" + +namespace NESO::ExpansionLooping { + +/** + * Implements evaluation and projection for Triangle elements with eModified_A/B + * basis functions. + */ +struct Triangle : JacobiExpansionLoopingInterface { + + inline void loc_coord_to_loc_collapsed_v(const REAL xi0, const REAL xi1, + const REAL xi2, REAL *eta0, + REAL *eta1, REAL *eta2) { + GeometryInterface::Triangle geom{}; + geom.loc_coord_to_loc_collapsed(xi0, xi1, eta0, eta1); + } + + inline void evaluate_basis_0_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + + BasisJacobi::ModifiedA::evaluate(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + inline void evaluate_basis_1_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) { + + BasisJacobi::ModifiedB::evaluate(nummodes, z, coeffs_stride, coeffs_pnm10, + coeffs_pnm11, coeffs_pnm2, output); + } + inline void evaluate_basis_2_v(const int nummodes, const REAL z, + const int coeffs_stride, + const REAL *coeffs_pnm10, + const REAL *coeffs_pnm11, + const REAL *coeffs_pnm2, REAL *output) {} + + inline void loop_evaluate_v(const int nummodes, const REAL *const dofs, + const REAL *const local_space_0, + const REAL *const local_space_1, + const REAL *const local_space_2, REAL *output) { + int mode = 0; + REAL evaluation = 0.0; + for (int px = 0; px < nummodes; px++) { + for (int qx = 0; qx < nummodes - px; qx++) { + const REAL coeff = dofs[mode]; + // There exists a correction for mode == 1 in the Nektar++ + // definition of this 2D basis which we apply here. + const REAL etmp0 = (mode == 1) ? 1.0 : local_space_0[px]; + const REAL etmp1 = local_space_1[mode]; + evaluation += coeff * etmp0 * etmp1; + mode++; + } + } + *output = evaluation; + } + + inline void loop_project_v(const int nummodes, const REAL value, + const REAL *const local_space_0, + const REAL *const local_space_1, + const REAL *const local_space_2, REAL *dofs) { + int modey = 0; + for (int px = 0; px < nummodes; px++) { + for (int qx = 0; qx < nummodes - px; qx++) { + const int mode = modey++; + const REAL etmp0 = (mode == 1) ? 1.0 : local_space_0[px]; + const REAL etmp1 = local_space_1[mode]; + const REAL evaluation = value * etmp0 * etmp1; + sycl::atomic_ref + coeff_atomic_ref(dofs[mode]); + coeff_atomic_ref.fetch_add(evaluation); + } + } + } + + inline ShapeType get_shape_type_v() { return eTriangle; } + + inline int get_ndim_v() { return 2; } +}; + +} // namespace NESO::ExpansionLooping + +#endif diff --git a/include/nektar_interface/function_bary_evaluation.hpp b/include/nektar_interface/function_bary_evaluation.hpp index f8e94ca1..97737fc0 100644 --- a/include/nektar_interface/function_bary_evaluation.hpp +++ b/include/nektar_interface/function_bary_evaluation.hpp @@ -10,8 +10,8 @@ #include #include -#include "function_coupling_base.hpp" -#include "nektar_interface/geometry_transport/shape_mapping.hpp" +#include "expansion_looping/geom_to_expansion_builder.hpp" +#include "geometry_transport/shape_mapping.hpp" #include "utility_sycl.hpp" using namespace NESO::Particles; @@ -52,9 +52,9 @@ struct Evaluate { */ static inline void preprocess_weights(const int stride_base, const int num_phys, const REAL coord, - const REAL *z_values, - const REAL *bw_values, int *exact_index, - REAL *div_values) { + const REAL *const z_values, + const REAL *const bw_values, + int *exact_index, REAL *div_values) { const sycl::vec coord_vec{coord}; sycl::global_ptr z_ptr{z_values}; @@ -97,7 +97,7 @@ struct Evaluate { * @returns Sum of the first num_phys values of div_space. */ static inline REAL preprocess_denominator(const int num_phys, - const REAL *div_space) { + const REAL *const div_space) { REAL denom = 0.0; for (int ix = 0; ix < num_phys; ix++) { const REAL tmp = div_space[ix]; @@ -121,9 +121,10 @@ struct Evaluate { * preprocess_denominator. * @returns Contribution to Bary interpolation from a dimension 0 evaluation. */ - static inline REAL compute_dir_0(const int num_phys, const REAL *physvals, - const REAL *div_space, const int exact_i, - const REAL denom) { + static inline REAL compute_dir_0(const int num_phys, + const REAL *const physvals, + const REAL *const div_space, + const int exact_i, const REAL denom) { if ((exact_i > -1) && (exact_i < num_phys)) { const REAL exact_quadrature_val = physvals[exact_i]; return exact_quadrature_val; @@ -157,10 +158,10 @@ struct Evaluate { * @returns Bary evaluation of a function at a coordinate. */ static inline REAL compute_dir_10(const int num_phys0, const int num_phys1, - const REAL *physvals, - const REAL *div_space0, - const REAL *div_space1, const int exact_i0, - const int exact_i1) { + const REAL *const physvals, + const REAL *const div_space0, + const REAL *const div_space1, + const int exact_i0, const int exact_i1) { const REAL denom0 = Bary::Evaluate::preprocess_denominator(num_phys0, div_space0); if ((exact_i1 > -1) && (exact_i1 < num_phys1)) { diff --git a/include/nektar_interface/function_basis_evaluation.hpp b/include/nektar_interface/function_basis_evaluation.hpp index 6393590e..b57571aa 100644 --- a/include/nektar_interface/function_basis_evaluation.hpp +++ b/include/nektar_interface/function_basis_evaluation.hpp @@ -12,7 +12,9 @@ #include #include "basis_evaluation.hpp" -#include "function_coupling_base.hpp" +#include "expansion_looping/basis_evaluate_base.hpp" +#include "expansion_looping/expansion_looping.hpp" +#include "expansion_looping/geom_to_expansion_builder.hpp" #include "special_functions.hpp" using namespace NESO::Particles; @@ -28,61 +30,6 @@ using namespace Nektar::StdRegions; namespace NESO { -/** - * Abstract base class for 2D kernels used within evaluation loops. - */ -struct EvaluateKernelBase2D { - - double evaluation; - const double *dofs; - const double *local_space_0; - const double *local_space_1; - - EvaluateKernelBase2D(const double *dofs, const double *local_space_0, - const double *local_space_1) - : evaluation(0.0), dofs(dofs), local_space_0(local_space_0), - local_space_1(local_space_1) {} -}; - -/** - * Evaluation kernel for a 2D quadrilateral - */ -struct EvaluateKernelQuad : BasisJacobi::LoopingKernelBase, - EvaluateKernelBase2D { - - EvaluateKernelQuad(const double *dofs, const double *local_space_0, - const double *local_space_1) - : EvaluateKernelBase2D(dofs, local_space_0, local_space_1) {} - - inline void kernel(const int px, const int qx, const int mode) { - const double coeff = dofs[mode]; - const double basis0 = local_space_0[px]; - const double basis1 = local_space_1[qx]; - evaluation += coeff * basis0 * basis1; - } -}; - -/** - * Evaluation kernel for a triangle. - */ -struct EvaluateKernelTriangle - : BasisJacobi::LoopingKernelBase, - EvaluateKernelBase2D { - - EvaluateKernelTriangle(const double *dofs, const double *local_space_0, - const double *local_space_1) - : EvaluateKernelBase2D(dofs, local_space_0, local_space_1) {} - - inline void kernel(const int px, const int qx, const int mode) { - const double coeff = dofs[mode]; - // There exists a correction for mode == 1 in the Nektar++ - // definition of this 2D basis which we apply here. - const double etmp0 = (mode == 1) ? 1.0 : local_space_0[px]; - const double etmp1 = local_space_1[mode]; - evaluation += coeff * etmp0 * etmp1; - } -}; - /** * Class to evaluate Nektar++ fields by evaluating basis functions. */ @@ -92,16 +39,21 @@ class FunctionEvaluateBasis : public BasisEvaluateBase { /** * Templated evaluation function for CRTP. */ - template + template inline sycl::event evaluate_inner( - GeometryInterface::BaseCoordinateMapping2D coordinate_mapping, - BasisJacobi::Basis1D basis_0, - BasisJacobi::Basis1D basis_1, - BasisJacobi::Indexing2D coeff_looping, + ExpansionLooping::JacobiExpansionLoopingInterface + evaluation_type, ParticleGroupSharedPtr particle_group, Sym sym, - const int component, const int cells_iterset_size, - const int *k_cells_iterset) { + const int component) { + + const ShapeType shape_type = evaluation_type.get_shape_type(); + const int cells_iterset_size = this->map_shape_to_count.at(shape_type); + if (cells_iterset_size == 0) { + return sycl::event{}; + } + + const auto k_cells_iterset = + this->map_shape_to_dh_cells.at(shape_type)->d_buffer.ptr; auto mpi_rank_dat = particle_group->mpi_rank_dat; @@ -115,8 +67,7 @@ class FunctionEvaluateBasis : public BasisEvaluateBase { const auto d_npart_cell = mpi_rank_dat->d_npart_cell; const auto k_global_coeffs = this->dh_global_coeffs.d_buffer.ptr; const auto k_coeffs_offsets = this->dh_coeffs_offsets.d_buffer.ptr; - const auto k_nummodes0 = this->dh_nummodes0.d_buffer.ptr; - const auto k_nummodes1 = this->dh_nummodes1.d_buffer.ptr; + const auto k_nummodes = this->dh_nummodes.d_buffer.ptr; // jacobi coefficients const auto k_coeffs_pnm10 = this->dh_coeffs_pnm10.d_buffer.ptr; @@ -124,29 +75,36 @@ class FunctionEvaluateBasis : public BasisEvaluateBase { const auto k_coeffs_pnm2 = this->dh_coeffs_pnm2.d_buffer.ptr; const int k_stride_n = this->stride_n; - const int k_max_total_nummodes0 = this->max_total_nummodes0; - const int k_max_total_nummodes1 = this->max_total_nummodes1; + const int k_max_total_nummodes0 = + this->map_total_nummodes.at(shape_type).at(0); + const int k_max_total_nummodes1 = + this->map_total_nummodes.at(shape_type).at(1); + const int k_max_total_nummodes2 = + this->map_total_nummodes.at(shape_type).at(2); - const std::size_t local_size = get_num_local_work_items( + const size_t local_size = get_num_local_work_items( this->sycl_target, - static_cast(k_max_total_nummodes0 + - k_max_total_nummodes1) * - sizeof(double), + static_cast(k_max_total_nummodes0 + k_max_total_nummodes1 + + k_max_total_nummodes2) * + sizeof(REAL), 128); const int local_mem_num_items = - (k_max_total_nummodes0 + k_max_total_nummodes1) * local_size; - const std::size_t outer_size = + (k_max_total_nummodes0 + k_max_total_nummodes1 + + k_max_total_nummodes2) * + local_size; + const size_t outer_size = get_particle_loop_global_size(mpi_rank_dat, local_size); - sycl::range<2> cell_iterset_range{ - static_cast(cells_iterset_size), - static_cast(outer_size) * - static_cast(local_size)}; + const int k_ndim = evaluation_type.get_ndim(); + + sycl::range<2> cell_iterset_range{static_cast(cells_iterset_size), + static_cast(outer_size) * + static_cast(local_size)}; sycl::range<2> local_iterset{1, local_size}; auto event_loop = this->sycl_target->queue.submit([&](sycl::handler &cgh) { - sycl::accessor local_mem(sycl::range<1>(local_mem_num_items), cgh); @@ -158,43 +116,52 @@ class FunctionEvaluateBasis : public BasisEvaluateBase { const INT cellx = k_cells_iterset[iter_cell]; const INT layerx = idx.get_global_id(1); + ExpansionLooping::JacobiExpansionLoopingInterface + loop_type{}; if (layerx < d_npart_cell[cellx]) { - const double *dofs = &k_global_coeffs[k_coeffs_offsets[cellx]]; + const REAL *dofs = &k_global_coeffs[k_coeffs_offsets[cellx]]; - // Get the number of modes in x and y - const int nummodes0 = k_nummodes0[cellx]; - const int nummodes1 = k_nummodes1[cellx]; + // Get the number of modes in x,y and z. + const int nummodes = k_nummodes[cellx]; - const double xi0 = k_ref_positions[cellx][0][layerx]; - const double xi1 = k_ref_positions[cellx][1][layerx]; - double eta0, eta1; + REAL xi0, xi1, xi2, eta0, eta1, eta2; + xi0 = k_ref_positions[cellx][0][layerx]; + if (k_ndim > 1) { + xi1 = k_ref_positions[cellx][1][layerx]; + } + if (k_ndim > 2) { + xi2 = k_ref_positions[cellx][2][layerx]; + } - GeometryInterface::BaseCoordinateMapping2D - coord_mapping{}; - coord_mapping.loc_coord_to_loc_collapsed(xi0, xi1, &eta0, &eta1); + loop_type.loc_coord_to_loc_collapsed(xi0, xi1, xi2, &eta0, &eta1, + &eta2); - // Get the local space for the 1D evaluations in dim0 and dim1 - double *local_space_0 = + // Get the local space for the 1D evaluations in each dimension. + REAL *local_space_0 = &local_mem[idx_local * - (k_max_total_nummodes0 + k_max_total_nummodes1)]; - double *local_space_1 = local_space_0 + k_max_total_nummodes0; - - // Compute the basis functions in dim0 and dim1 - BasisJacobi::Basis1D::evaluate( - nummodes0, eta0, k_stride_n, k_coeffs_pnm10, k_coeffs_pnm11, - k_coeffs_pnm2, local_space_0); - BasisJacobi::Basis1D::evaluate( - nummodes1, eta1, k_stride_n, k_coeffs_pnm10, k_coeffs_pnm11, - k_coeffs_pnm2, local_space_1); - - // Multiply out the basis functions along with the DOFs - EVALUATE_TYPE evaluate_kernel{dofs, local_space_0, local_space_1}; - - BasisJacobi::Indexing2D::loop(nummodes0, nummodes1, - evaluate_kernel); - - k_output[cellx][k_component][layerx] = evaluate_kernel.evaluation; + (k_max_total_nummodes0 + k_max_total_nummodes1 + + k_max_total_nummodes2)]; + REAL *local_space_1 = local_space_0 + k_max_total_nummodes0; + REAL *local_space_2 = local_space_1 + k_max_total_nummodes1; + + // Compute the basis functions in each dimension. + loop_type.evaluate_basis_0(nummodes, eta0, k_stride_n, + k_coeffs_pnm10, k_coeffs_pnm11, + k_coeffs_pnm2, local_space_0); + loop_type.evaluate_basis_1(nummodes, eta1, k_stride_n, + k_coeffs_pnm10, k_coeffs_pnm11, + k_coeffs_pnm2, local_space_1); + loop_type.evaluate_basis_2(nummodes, eta2, k_stride_n, + k_coeffs_pnm10, k_coeffs_pnm11, + k_coeffs_pnm2, local_space_2); + + REAL evaluation = 0.0; + loop_type.loop_evaluate(nummodes, dofs, local_space_0, + local_space_1, local_space_2, + &evaluation); + + k_output[cellx][k_component][layerx] = evaluation; } }); }); @@ -242,21 +209,26 @@ class FunctionEvaluateBasis : public BasisEvaluateBase { } this->dh_global_coeffs.host_to_device(); - const auto k_cells_quads = this->dh_cells_quads.d_buffer.ptr; - const auto k_cells_tris = this->dh_cells_tris.d_buffer.ptr; - - auto event_quad = evaluate_inner( - GeometryInterface::Quadrilateral{}, BasisJacobi::ModifiedA{}, - BasisJacobi::ModifiedA{}, BasisJacobi::IndexingQuad{}, particle_group, - sym, component, this->cells_quads.size(), k_cells_quads); - - auto event_tri = evaluate_inner( - GeometryInterface::Triangle{}, BasisJacobi::ModifiedA{}, - BasisJacobi::ModifiedB{}, BasisJacobi::IndexingTriangle{}, - particle_group, sym, component, this->cells_tris.size(), k_cells_tris); + EventStack event_stack{}; + + if (this->mesh->get_ndim() == 2) { + event_stack.push(evaluate_inner(ExpansionLooping::Quadrilateral{}, + particle_group, sym, component)); + + event_stack.push(evaluate_inner(ExpansionLooping::Triangle{}, + particle_group, sym, component)); + } else { + event_stack.push(evaluate_inner(ExpansionLooping::Hexahedron{}, + particle_group, sym, component)); + event_stack.push(evaluate_inner(ExpansionLooping::Pyramid{}, + particle_group, sym, component)); + event_stack.push(evaluate_inner(ExpansionLooping::Prism{}, particle_group, + sym, component)); + event_stack.push(evaluate_inner(ExpansionLooping::Tetrahedron{}, + particle_group, sym, component)); + } - event_quad.wait_and_throw(); - event_tri.wait_and_throw(); + event_stack.wait(); } }; diff --git a/include/nektar_interface/function_basis_projection.hpp b/include/nektar_interface/function_basis_projection.hpp index 6bafcfb3..6c24dd36 100644 --- a/include/nektar_interface/function_basis_projection.hpp +++ b/include/nektar_interface/function_basis_projection.hpp @@ -12,7 +12,9 @@ #include #include "basis_evaluation.hpp" -#include "function_coupling_base.hpp" +#include "expansion_looping/basis_evaluate_base.hpp" +#include "expansion_looping/expansion_looping.hpp" +#include "expansion_looping/geom_to_expansion_builder.hpp" #include "special_functions.hpp" #include "utility_sycl.hpp" @@ -34,46 +36,24 @@ namespace NESO { */ template class FunctionProjectBasis : public BasisEvaluateBase { protected: -public: - /// Disable (implicit) copies. - FunctionProjectBasis(const FunctionProjectBasis &st) = delete; - /// Disable (implicit) copies. - FunctionProjectBasis &operator=(FunctionProjectBasis const &a) = delete; - /** - * Constructor to create instance to project onto Nektar++ fields. - * - * @param field Example Nektar++ field of the same mesh and function space as - * the destination fields that this instance will be called with. - * @param mesh ParticleMeshInterface constructed over same mesh as the - * function. - * @param cell_id_translation Map between NESO-Particles cells and Nektar++ - * cells. + * Templated projection function for CRTP. */ - FunctionProjectBasis(std::shared_ptr field, - ParticleMeshInterfaceSharedPtr mesh, - CellIDTranslationSharedPtr cell_id_translation) - : BasisEvaluateBase(field, mesh, cell_id_translation) {} - - /** - * Project particle data onto a function. - * - * @param particle_group Source container of particles. - * @param sym Symbol of ParticleDat within the ParticleGroup. - * @param component Determine which component of the ParticleDat is - * projected. - * @param global_coeffs[in,out] RHS in the Ax=b L2 projection system. - */ - template - inline void project(ParticleGroupSharedPtr particle_group, Sym sym, - const int component, V &global_coeffs) { - - const int num_global_coeffs = global_coeffs.size(); - this->dh_global_coeffs.realloc_no_copy(num_global_coeffs); - for (int px = 0; px < num_global_coeffs; px++) { - this->dh_global_coeffs.h_buffer.ptr[px] = 0.0; + template + inline sycl::event + project_inner(ExpansionLooping::JacobiExpansionLoopingInterface + project_type, + ParticleGroupSharedPtr particle_group, Sym sym, + const int component) { + + const ShapeType shape_type = project_type.get_shape_type(); + const int cells_iterset_size = this->map_shape_to_count.at(shape_type); + if (cells_iterset_size == 0) { + return sycl::event{}; } - this->dh_global_coeffs.host_to_device(); + + const auto k_cells_iterset = + this->map_shape_to_dh_cells.at(shape_type)->d_buffer.ptr; auto mpi_rank_dat = particle_group->mpi_rank_dat; @@ -81,17 +61,13 @@ template class FunctionProjectBasis : public BasisEvaluateBase { (*particle_group)[Sym("NESO_REFERENCE_POSITIONS")] ->cell_dat.device_ptr(); - const auto k_input = (*particle_group)[sym]->cell_dat.device_ptr(); + auto k_input = (*particle_group)[sym]->cell_dat.device_ptr(); const int k_component = component; const auto d_npart_cell = mpi_rank_dat->d_npart_cell; - const auto k_cells_quads = this->dh_cells_quads.d_buffer.ptr; - const auto k_cells_tris = this->dh_cells_tris.d_buffer.ptr; - - const auto k_global_coeffs = this->dh_global_coeffs.d_buffer.ptr; + auto k_global_coeffs = this->dh_global_coeffs.d_buffer.ptr; const auto k_coeffs_offsets = this->dh_coeffs_offsets.d_buffer.ptr; - const auto k_nummodes0 = this->dh_nummodes0.d_buffer.ptr; - const auto k_nummodes1 = this->dh_nummodes1.d_buffer.ptr; + const auto k_nummodes = this->dh_nummodes.d_buffer.ptr; // jacobi coefficients const auto k_coeffs_pnm10 = this->dh_coeffs_pnm10.d_buffer.ptr; @@ -99,147 +75,158 @@ template class FunctionProjectBasis : public BasisEvaluateBase { const auto k_coeffs_pnm2 = this->dh_coeffs_pnm2.d_buffer.ptr; const int k_stride_n = this->stride_n; - const int k_max_total_nummodes0 = this->max_total_nummodes0; - const int k_max_total_nummodes1 = this->max_total_nummodes1; + const int k_max_total_nummodes0 = + this->map_total_nummodes.at(shape_type).at(0); + const int k_max_total_nummodes1 = + this->map_total_nummodes.at(shape_type).at(1); + const int k_max_total_nummodes2 = + this->map_total_nummodes.at(shape_type).at(2); - const std::size_t local_size = get_num_local_work_items( + const size_t local_size = get_num_local_work_items( this->sycl_target, - static_cast(k_max_total_nummodes0 + - k_max_total_nummodes1) * - sizeof(double), + static_cast(k_max_total_nummodes0 + k_max_total_nummodes1 + + k_max_total_nummodes2) * + sizeof(REAL), 128); const int local_mem_num_items = - (k_max_total_nummodes0 + k_max_total_nummodes1) * local_size; - - const int max_cell_occupancy = mpi_rank_dat->cell_dat.get_nrow_max(); - const auto div_mod = std::div(max_cell_occupancy, local_size); - const int outer_size = div_mod.quot + (div_mod.rem == 0 ? 0 : 1); - - sycl::range<2> cell_iterset_quad{ - static_cast(this->cells_quads.size()), - static_cast(outer_size) * - static_cast(local_size)}; - sycl::range<2> cell_iterset_tri{ - static_cast(this->cells_tris.size()), - static_cast(outer_size) * - static_cast(local_size)}; + (k_max_total_nummodes0 + k_max_total_nummodes1 + + k_max_total_nummodes2) * + local_size; + const size_t outer_size = + get_particle_loop_global_size(mpi_rank_dat, local_size); + + const int k_ndim = project_type.get_ndim(); + + sycl::range<2> cell_iterset_range{static_cast(cells_iterset_size), + static_cast(outer_size) * + static_cast(local_size)}; sycl::range<2> local_iterset{1, local_size}; - auto event_quad = this->sycl_target->queue.submit([&](sycl::handler &cgh) { - sycl::accessorsycl_target->queue.submit([&](sycl::handler &cgh) { + sycl::accessor local_mem(sycl::range<1>(local_mem_num_items), cgh); cgh.parallel_for<>( - sycl::nd_range<2>(cell_iterset_quad, local_iterset), + sycl::nd_range<2>(cell_iterset_range, local_iterset), [=](sycl::nd_item<2> idx) { const int iter_cell = idx.get_global_id(0); const int idx_local = idx.get_local_id(1); - const INT cellx = k_cells_quads[iter_cell]; + const INT cellx = k_cells_iterset[iter_cell]; const INT layerx = idx.get_global_id(1); + ExpansionLooping::JacobiExpansionLoopingInterface + loop_type{}; + if (layerx < d_npart_cell[cellx]) { - const auto dofs = &k_global_coeffs[k_coeffs_offsets[cellx]]; - const int nummodes0 = k_nummodes0[cellx]; - const int nummodes1 = k_nummodes1[cellx]; + REAL *dofs = &k_global_coeffs[k_coeffs_offsets[cellx]]; + + // Get the number of modes in x and y + const int nummodes = k_nummodes[cellx]; - const double xi0 = k_ref_positions[cellx][0][layerx]; - const double xi1 = k_ref_positions[cellx][1][layerx]; const double value = k_input[cellx][k_component][layerx]; + REAL xi0, xi1, xi2, eta0, eta1, eta2; + xi0 = k_ref_positions[cellx][0][layerx]; + if (k_ndim > 1) { + xi1 = k_ref_positions[cellx][1][layerx]; + } + if (k_ndim > 2) { + xi2 = k_ref_positions[cellx][2][layerx]; + } - auto local_space_0 = + loop_type.loc_coord_to_loc_collapsed(xi0, xi1, xi2, &eta0, &eta1, + &eta2); + + // Get the local space for the 1D evaluations in dim0 and dim1 + REAL *local_space_0 = &local_mem[idx_local * - (k_max_total_nummodes0 + k_max_total_nummodes1)]; - auto local_space_1 = local_space_0 + k_max_total_nummodes0; + (k_max_total_nummodes0 + k_max_total_nummodes1 + + k_max_total_nummodes2)]; + REAL *local_space_1 = local_space_0 + k_max_total_nummodes0; + REAL *local_space_2 = local_space_1 + k_max_total_nummodes1; // Compute the basis functions in dim0 and dim1 - BasisJacobi::mod_A(nummodes0, xi0, k_stride_n, k_coeffs_pnm10, - k_coeffs_pnm11, k_coeffs_pnm2, local_space_0); - BasisJacobi::mod_A(nummodes1, xi1, k_stride_n, k_coeffs_pnm10, - k_coeffs_pnm11, k_coeffs_pnm2, local_space_1); - - // Multiply the basis functions for dimension 0 and 1 togeather - // along with the value to project from the particle and - // atomically increment the DOF location in the RHS vector of the - // Ax=B projection system. - for (int qx = 0; qx < nummodes1; qx++) { - const double basis1 = local_space_1[qx]; - for (int px = 0; px < nummodes0; px++) { - const double basis0 = local_space_0[px]; - const double evaluation = value * basis0 * basis1; - sycl::atomic_ref - coeff_atomic_ref(dofs[qx * nummodes0 + px]); - const double prev = coeff_atomic_ref.fetch_add(evaluation); - } - } + loop_type.evaluate_basis_0(nummodes, eta0, k_stride_n, + k_coeffs_pnm10, k_coeffs_pnm11, + k_coeffs_pnm2, local_space_0); + loop_type.evaluate_basis_1(nummodes, eta1, k_stride_n, + k_coeffs_pnm10, k_coeffs_pnm11, + k_coeffs_pnm2, local_space_1); + loop_type.evaluate_basis_2(nummodes, eta2, k_stride_n, + k_coeffs_pnm10, k_coeffs_pnm11, + k_coeffs_pnm2, local_space_2); + + loop_type.loop_project(nummodes, value, local_space_0, + local_space_1, local_space_2, dofs); } }); }); - auto event_tri = this->sycl_target->queue.submit([&](sycl::handler &cgh) { - sycl::accessor - local_mem(sycl::range<1>(local_mem_num_items), cgh); + return event_loop; + } - cgh.parallel_for<>( - sycl::nd_range<2>(cell_iterset_tri, local_iterset), - [=](sycl::nd_item<2> idx) { - const int iter_cell = idx.get_global_id(0); - const int idx_local = idx.get_local_id(1); +public: + /// Disable (implicit) copies. + FunctionProjectBasis(const FunctionProjectBasis &st) = delete; + /// Disable (implicit) copies. + FunctionProjectBasis &operator=(FunctionProjectBasis const &a) = delete; - const INT cellx = k_cells_tris[iter_cell]; - const INT layerx = idx.get_global_id(1); + /** + * Constructor to create instance to project onto Nektar++ fields. + * + * @param field Example Nektar++ field of the same mesh and function space as + * the destination fields that this instance will be called with. + * @param mesh ParticleMeshInterface constructed over same mesh as the + * function. + * @param cell_id_translation Map between NESO-Particles cells and Nektar++ + * cells. + */ + FunctionProjectBasis(std::shared_ptr field, + ParticleMeshInterfaceSharedPtr mesh, + CellIDTranslationSharedPtr cell_id_translation) + : BasisEvaluateBase(field, mesh, cell_id_translation) {} - if (layerx < d_npart_cell[cellx]) { - const auto dofs = &k_global_coeffs[k_coeffs_offsets[cellx]]; - const int nummodes0 = k_nummodes0[cellx]; - const int nummodes1 = k_nummodes1[cellx]; - const double value = k_input[cellx][k_component][layerx]; - const double xi0 = k_ref_positions[cellx][0][layerx]; - const double xi1 = k_ref_positions[cellx][1][layerx]; - - // map from xi to eta (the collapsed coordinate) - double eta0, eta1; - constexpr int shape_type_tri = - shape_type_to_int(LibUtilities::eTriangle); - GeometryInterface::loc_coord_to_loc_collapsed_2d( - shape_type_tri, xi0, xi1, &eta0, &eta1); - auto local_space_0 = - &local_mem[idx_local * - (k_max_total_nummodes0 + k_max_total_nummodes1)]; - auto local_space_1 = local_space_0 + k_max_total_nummodes0; + /** + * Project particle data onto a function. + * + * @param particle_group Source container of particles. + * @param sym Symbol of ParticleDat within the ParticleGroup. + * @param component Determine which component of the ParticleDat is + * projected. + * @param global_coeffs[in,out] RHS in the Ax=b L2 projection system. + */ + template + inline void project(ParticleGroupSharedPtr particle_group, Sym sym, + const int component, V &global_coeffs) { - // Compute the basis functions in dim0 and dim1 - BasisJacobi::mod_A(nummodes0, eta0, k_stride_n, k_coeffs_pnm10, - k_coeffs_pnm11, k_coeffs_pnm2, local_space_0); - BasisJacobi::mod_B(nummodes1, eta1, k_stride_n, k_coeffs_pnm10, - k_coeffs_pnm11, k_coeffs_pnm2, local_space_1); - - // Multiply the basis functions for dimension 0 and 1 togeather - // along with the value to project from the particle and - // atomically increment the DOF location in the RHS vector of the - // Ax=B projection system. - int modey = 0; - for (int px = 0; px < nummodes1; px++) { - for (int qx = 0; qx < nummodes1 - px; qx++) { - const int mode = modey++; - const double etmp0 = (mode == 1) ? 1.0 : local_space_0[px]; - const double etmp1 = local_space_1[mode]; - const double evaluation = value * etmp0 * etmp1; - sycl::atomic_ref - coeff_atomic_ref(dofs[mode]); - coeff_atomic_ref.fetch_add(evaluation); - } - } - } - }); - }); + const int num_global_coeffs = global_coeffs.size(); + this->dh_global_coeffs.realloc_no_copy(num_global_coeffs); + for (int px = 0; px < num_global_coeffs; px++) { + this->dh_global_coeffs.h_buffer.ptr[px] = 0.0; + } + this->dh_global_coeffs.host_to_device(); + + EventStack event_stack{}; + + if (this->mesh->get_ndim() == 2) { + event_stack.push(project_inner(ExpansionLooping::Quadrilateral{}, + particle_group, sym, component)); + + event_stack.push(project_inner(ExpansionLooping::Triangle{}, + particle_group, sym, component)); + } else { + event_stack.push(project_inner(ExpansionLooping::Hexahedron{}, + particle_group, sym, component)); + event_stack.push(project_inner(ExpansionLooping::Pyramid{}, + particle_group, sym, component)); + event_stack.push(project_inner(ExpansionLooping::Prism{}, particle_group, + sym, component)); + event_stack.push(project_inner(ExpansionLooping::Tetrahedron{}, + particle_group, sym, component)); + } - event_quad.wait_and_throw(); - event_tri.wait_and_throw(); + event_stack.wait(); this->dh_global_coeffs.device_to_host(); for (int px = 0; px < num_global_coeffs; px++) { global_coeffs[px] = this->dh_global_coeffs.h_buffer.ptr[px]; diff --git a/include/nektar_interface/function_evaluation.hpp b/include/nektar_interface/function_evaluation.hpp index a73233e2..89018ac7 100644 --- a/include/nektar_interface/function_evaluation.hpp +++ b/include/nektar_interface/function_evaluation.hpp @@ -5,11 +5,13 @@ #include #include +#include +#include + #include #include "function_bary_evaluation.hpp" #include "function_basis_evaluation.hpp" -#include "function_coupling_base.hpp" #include "particle_interface.hpp" using namespace Nektar::LibUtilities; @@ -61,11 +63,13 @@ template class FieldEvaluate { cell_id_translation(cell_id_translation), derivative(derivative) { if (this->derivative) { - this->bary_evaluate_base = std::make_shared>( - field, + auto particle_mesh_interface = std::dynamic_pointer_cast( - particle_group->domain->mesh), - cell_id_translation); + particle_group->domain->mesh); + NESOASSERT(particle_mesh_interface->ndim == 2, + "Derivative evaluation supported in 2D only."); + this->bary_evaluate_base = std::make_shared>( + field, particle_mesh_interface, cell_id_translation); } else { auto mesh = std::dynamic_pointer_cast( particle_group->domain->mesh); @@ -106,6 +110,10 @@ template class FieldEvaluate { } }; +extern template void +FieldEvaluate::evaluate(Sym sym); +extern template void +FieldEvaluate::evaluate(Sym sym); } // namespace NESO #endif diff --git a/include/nektar_interface/function_projection.hpp b/include/nektar_interface/function_projection.hpp index 429a3e4b..b2df5783 100644 --- a/include/nektar_interface/function_projection.hpp +++ b/include/nektar_interface/function_projection.hpp @@ -10,8 +10,8 @@ #include #include +#include "basis_reference.hpp" #include "function_basis_projection.hpp" -#include "function_coupling_base.hpp" #include "particle_interface.hpp" using namespace Nektar::MultiRegions; @@ -260,7 +260,8 @@ template class FieldProject : GeomToExpansionBuilder { } // EvaluateBasis is called with this argument holding the reference position - Array local_coord(particle_ndim); + Array local_coord(3); + Array local_collapsed(3); // event stack for copy operations EventStack event_stack; @@ -295,10 +296,14 @@ template class FieldProject : GeomToExpansionBuilder { // Get the expansion object that corresponds to the first expansion auto nektar_expansion_0 = this->fields[0]->GetExp(nektar_expansion_id); // get the number of modes in this expansion - const int num_modes = nektar_expansion_0->GetNcoeffs(); + const int num_modes_total = nektar_expansion_0->GetNcoeffs(); // get the offset in the expansion values for this mesh cell const auto expansion_offset = this->fields[0]->GetCoeff_Offset(nektar_expansion_id); + // get the shape type this expansion is over + const ShapeType shape_type = nektar_expansion_0->DetShapeType(); + // create space for the mode evaluation + std::vector mode_evaluations(num_modes_total); for (int fieldx = 0; fieldx < nfields; fieldx++) { NESOASSERT(this->fields[fieldx]->GetCoeff_Offset(nektar_expansion_id) == @@ -306,22 +311,37 @@ template class FieldProject : GeomToExpansionBuilder { "Missmatch in expansion offset."); } + const int num_modes = nektar_expansion_0->GetBasis(0)->GetNumModes(); + for (int dimx = 0; dimx < particle_ndim; dimx++) { + NESOASSERT(nektar_expansion_0->GetBasis(dimx)->GetNumModes() == + num_modes, + "Missmatch in number of modes across dimensions."); + } + // wait for the copy of particle data to host event_stack.wait(); const int nrow = input_dats[0]->cell_dat.nrow[neso_cellx]; // for each particle in the cell for (int rowx = 0; rowx < nrow; rowx++) { // read the reference position from the particle + for (int dimx = 0; dimx < 3; dimx++) { + local_collapsed[dimx] = 0.0; + local_coord[dimx] = 0.0; + } for (int dimx = 0; dimx < particle_ndim; dimx++) { local_coord[dimx] = ref_positions_tmp[dimx][rowx]; } + nektar_expansion_0->LocCoordToLocCollapsed(local_coord, + local_collapsed); + BasisReference::eval_modes(shape_type, num_modes, local_collapsed[0], + local_collapsed[1], local_collapsed[2], + mode_evaluations); // for each mode in the expansion evaluate the basis function // corresponding to that node at the location of the particle, then // re-weight with the value on the particle. - for (int modex = 0; modex < num_modes; modex++) { - const double phi_j = - nektar_expansion_0->PhysEvaluateBasis(local_coord, modex); + for (int modex = 0; modex < num_modes_total; modex++) { + const double phi_j = mode_evaluations[modex]; // for each field reuse the computed basis function value for (int fieldx = 0; fieldx < nfields; fieldx++) { const int componentx = components[fieldx]; @@ -441,7 +461,6 @@ template class FieldProject : GeomToExpansionBuilder { components[fieldx], *global_phi[fieldx]); } - if (this->is_testing) { this->testing_device_rhs.clear(); this->testing_device_rhs.reserve(nfields * ncoeffs); diff --git a/include/nektar_interface/particle_cell_mapping/coarse_lookup_map.hpp b/include/nektar_interface/particle_cell_mapping/coarse_lookup_map.hpp index db758477..e46248af 100644 --- a/include/nektar_interface/particle_cell_mapping/coarse_lookup_map.hpp +++ b/include/nektar_interface/particle_cell_mapping/coarse_lookup_map.hpp @@ -1,5 +1,5 @@ -#ifndef __CANDIDATE_CELL_MAPPING -#define __CANDIDATE_CELL_MAPPING +#ifndef __COARSE_LOOKUP_MAP_H_ +#define __COARSE_LOOKUP_MAP_H_ #include "../bounding_box_intersection.hpp" #include "../utility_mesh_cartesian.hpp" diff --git a/include/nektar_interface/special_functions.hpp b/include/nektar_interface/special_functions.hpp index e94b63db..b136f41c 100644 --- a/include/nektar_interface/special_functions.hpp +++ b/include/nektar_interface/special_functions.hpp @@ -1,6 +1,8 @@ #ifndef __NESO_SPECIAL_FUNCTIONS_H_ #define __NESO_SPECIAL_FUNCTIONS_H_ +#include + namespace NESO { /** @@ -63,6 +65,19 @@ inline double jacobi(const int p, const double z, const int alpha, return pnp1; }; +/** + * Compute relative error between a correct value and a test value. + * + * @param correct Correct value to test against. + * @param to_test Value to compare with the correct value. + * @returns relative error. + */ +inline double relative_error(const double correct, const double to_test) { + const double abs_correct = std::abs(correct); + const double abs_error = std::abs(correct - to_test); + return abs_correct == 0 ? abs_error : abs_error / abs_correct; +} + } // namespace NESO #endif diff --git a/include/nektar_interface/utilities.hpp b/include/nektar_interface/utilities.hpp index 0437ac5e..d663e14b 100644 --- a/include/nektar_interface/utilities.hpp +++ b/include/nektar_interface/utilities.hpp @@ -13,6 +13,8 @@ #include #include +#include "cell_id_translation.hpp" + using namespace Nektar; using namespace Nektar::SpatialDomains; using namespace Nektar::MultiRegions; @@ -66,12 +68,52 @@ class NektarFieldIndexMap { }; /** - * Interpolate f(x,y) onto a Nektar++ field. + * Interpolate f(x,y) or f(x,y,z) onto a Nektar++ field. * * @param func Function matching a signature like: double func(double x, - * double y); + * double y) or func(double x, double y, double z). * @param field Output Nektar++ field. + */ +template +inline void interpolate_onto_nektar_field_3d(T &func, + std::shared_ptr field) { + + // space for quadrature points + const int tot_points = field->GetTotPoints(); + Array x(tot_points); + Array y(tot_points); + Array f(tot_points); + Array z(tot_points); + + // Evaluate function at quadrature points. + field->GetCoords(x, y, z); + for (int pointx = 0; pointx < tot_points; pointx++) { + f[pointx] = func(x[pointx], y[pointx], z[pointx]); + } + + const int num_coeffs = field->GetNcoeffs(); + Array coeffs_f(num_coeffs); + for (int cx = 0; cx < num_coeffs; cx++) { + coeffs_f[cx] = 0.0; + } + + // interpolate onto expansion + field->FwdTrans(f, coeffs_f); + for (int cx = 0; cx < num_coeffs; cx++) { + field->SetCoeff(cx, coeffs_f[cx]); + } + + // transform backwards onto phys + field->BwdTrans(coeffs_f, f); + field->SetPhys(f); +} + +/** + * Interpolate f(x,y) onto a Nektar++ field. * + * @param func Function matching a signature like: double func(double x, + * double y); + * @parma field Output Nektar++ field. */ template inline void interpolate_onto_nektar_field_2d(T &func, @@ -159,6 +201,31 @@ inline double evaluate_scalar_2d(std::shared_ptr field, const double x, return eval; } +/** + * Evaluate a scalar valued Nektar++ function at a point. Avoids assertion + * issue. + * + * @param field Nektar++ field. + * @param x X coordinate. + * @param y Y coordinate. + * @returns Evaluation. + */ +template +inline double evaluate_scalar_3d(std::shared_ptr field, const double x, + const double y, const double z) { + Array xi(3); + Array coords(3); + + coords[0] = x; + coords[1] = y; + coords[2] = z; + int elmtIdx = field->GetExpIndex(coords, xi); + auto elmtPhys = field->GetPhys() + field->GetPhys_Offset(elmtIdx); + + const double eval = field->GetExp(elmtIdx)->StdPhysEvaluate(xi, elmtPhys); + return eval; +} + /** * Evaluate the derivative of scalar valued Nektar++ function at a point. * Avoids assertion issue. @@ -284,6 +351,65 @@ inline bool find_owning_geom(Nektar::SpatialDomains::MeshGraphSharedPtr graph, return geom_found; } +/** + * Helper class to map directly from NESO-Particles cells to Nektar++ + * expansions. + */ +class NESOCellsToNektarExp { +protected: + ExpListSharedPtr exp_list; + std::map map; + +public: + /** + * Create map for a given DisContField or ContField. + * + * @param exp_list DistContField or ContField (ExpList deriviative) + * containing expansions for each cell in the mesh. + * @param cell_id_translation CellIDTranslation instance for the MeshGraph + * used by the expansion list. + */ + template + NESOCellsToNektarExp(std::shared_ptr exp_list, + CellIDTranslationSharedPtr cell_id_translation) { + + this->exp_list = std::dynamic_pointer_cast(exp_list); + + std::map map_geom_to_exp; + for (int ei = 0; ei < exp_list->GetNumElmts(); ei++) { + auto ex = exp_list->GetExp(ei); + auto geom = ex->GetGeom(); + const int gid = geom->GetGlobalID(); + map_geom_to_exp[gid] = ei; + } + + const int num_cells = cell_id_translation->map_to_nektar.size(); + for (int neso_cell = 0; neso_cell < num_cells; neso_cell++) { + const int gid = cell_id_translation->map_to_nektar[neso_cell]; + const int exp_id = map_geom_to_exp.at(gid); + this->map[neso_cell] = exp_id; + } + } + + /** + * Get the expansion that corresponds to a input NESO::Particles cell. + * + * @param neso_cell NESO::Particles cell to get expansion for. + * @returns Nektar++ expansion for requested cell. + */ + inline LocalRegions::ExpansionSharedPtr get_exp(const int neso_cell) { + return this->exp_list->GetExp(this->get_exp_id(neso_cell)); + } + + /** + * Get the expansion id that corresponds to a input NESO::Particles cell. + * + * @param neso_cell NESO::Particles cell to get expansion for. + * @returns Nektar++ expansion id for requested cell. + */ + inline int get_exp_id(const int neso_cell) { return this->map.at(neso_cell); } +}; + } // namespace NESO #endif diff --git a/neso-particles b/neso-particles index c63f79db..47d91880 160000 --- a/neso-particles +++ b/neso-particles @@ -1 +1 @@ -Subproject commit c63f79dbc3d29691eb1d35b6c0d16a19246ccf01 +Subproject commit 47d91880b3047cacf608cd87e07e27af569afd7b diff --git a/src/nektar_interface/basis_reference.cpp b/src/nektar_interface/basis_reference.cpp new file mode 100644 index 00000000..e8057339 --- /dev/null +++ b/src/nektar_interface/basis_reference.cpp @@ -0,0 +1,461 @@ +#include + +namespace NESO::BasisReference { + +/** + * Reference implementation to compute eModified_A at an order p and point z. + * + * @param p Polynomial order. + * @param z Point in [-1, 1] to evaluate at. + * @returns Basis function evaluated at point. + */ +double eval_modA_i(const int p, const double z) { + const double b0 = 0.5 * (1.0 - z); + const double b1 = 0.5 * (1.0 + z); + if (p == 0) { + return b0; + } + if (p == 1) { + return b1; + } + return b0 * b1 * jacobi(p - 2, z, 1, 1); +} + +/** + * Reference implementation to compute eModified_B at an order p,q and point z. + * + * @param p First index for basis. + * @param q Second index for basis. + * @param z Point in [-1, 1] to evaluate at. + * @returns Basis function evaluated at point. + */ +double eval_modB_ij(const int p, const int q, const double z) { + + double output; + + if (p == 0) { + output = eval_modA_i(q, z); + } else if (q == 0) { + output = std::pow(0.5 * (1.0 - z), p); + } else { + output = std::pow(0.5 * (1.0 - z), p) * 0.5 * (1.0 + z) * + jacobi(q - 1, z, 2 * p - 1, 1); + } + return output; +} + +/** + * Reference implementation to compute eModified_C at an order p,q,r and point + * z. + * + * @param p First index for basis. + * @param q Second index for basis. + * @param r Third index for basis. + * @param z Point in [-1, 1] to evaluate at. + * @returns Basis function evaluated at point. + */ +double eval_modC_ijk(const int p, const int q, const int r, const double z) { + return eval_modB_ij(p + q, r, z); +} + +/** + * Reference implementation to compute eModifiedPyr_C at an order p,q,r and + * point z. + * + * @param p First index for basis. + * @param q Second index for basis. + * @param r Third index for basis. + * @param z Point in [-1, 1] to evaluate at. + * @returns Basis function evaluated at point. + */ +double eval_modPyrC_ijk(const int p, const int q, const int r, const double z) { + if (p == 0) { + return eval_modB_ij(q, r, z); + } else if (p == 1) { + if (q == 0) { + return eval_modB_ij(1, r, z); + } else { + return eval_modB_ij(q, r, z); + } + } else { + if (q < 2) { + return eval_modB_ij(p, r, z); + } else { + if (r == 0) { + return std::pow(0.5 * (1.0 - z), p + q - 2); + } else { + return std::pow(0.5 * (1.0 - z), p + q - 2) * (0.5 * (1.0 + z)) * + jacobi(r - 1, z, 2 * p + 2 * q - 3, 1); + } + } + } +} + +/** + * Get the total number of modes in a given basis for a given number of input + * modes. See Nektar GetTotNumModes. + * + * @param basis_type Basis type to query number of values for. + * @param P Number of modes, i.e. Nektar GetNumModes(); + * @returns Total number of values required to represent the basis with the + * given number of modes. + */ +int get_total_num_modes(const BasisType basis_type, const int P) { + if (basis_type == eModified_A) { + return P; + } else if (basis_type == eModified_B) { + int mode = 0; + for (int p = 0; p < P; p++) { + for (int q = 0; q < (P - p); q++) { + mode++; + } + } + return mode; + } else if (basis_type == eModified_C) { + int mode = 0; + for (int p = 0; p < P; p++) { + for (int q = 0; q < (P - p); q++) { + for (int r = 0; r < (P - p - q); r++) { + mode++; + } + } + } + return mode; + } else if (basis_type == eModifiedPyr_C) { + int mode = 0; + for (int p = 0; p < P; p++) { + for (int q = 0; q < P; q++) { + for (int r = 0; r < (P - std::max(p, q)); r++) { + mode++; + } + } + } + return mode; + } else { + NESOASSERT(false, "unknown basis type"); + return -1; + } +} + +/** + * Reference implementation to compute eModified_A for order P-1 and point z. + * + * @param[in] P Number of modes to compute. + * @param[in] z Point in [-1, 1] to evaluate at. + * @param[in, out] b Basis functions evaluated at z for each mode. + */ +void eval_modA(const int P, const double z, std::vector &b) { + NESOASSERT(b.size() >= get_total_num_modes(eModified_A, P), + "Output vector too small - see get_total_num_modes."); + for (int p = 0; p < P; p++) { + b.at(p) = eval_modA_i(p, z); + } +} + +/** + * Reference implementation to compute eModified_B for order P-1 and point z. + * + * @param[in] P Number of modes to compute. + * @param[in] z Point in [-1, 1] to evaluate at. + * @param[in, out] b Basis functions evaluated at z for each mode. + */ +void eval_modB(const int P, const double z, std::vector &b) { + NESOASSERT(b.size() >= get_total_num_modes(eModified_B, P), + "Output vector too small - see get_total_num_modes."); + int mode = 0; + for (int p = 0; p < P; p++) { + for (int q = 0; q < (P - p); q++) { + b.at(mode) = eval_modB_ij(p, q, z); + mode++; + } + } +} + +/** + * Reference implementation to compute eModified_C for order P-1 and point z. + * + * @param[in] P Number of modes to compute. + * @param[in] z Point in [-1, 1] to evaluate at. + * @param[in, out] b Basis functions evaluated at z for each mode. + */ +void eval_modC(const int P, const double z, std::vector &b) { + NESOASSERT(b.size() >= get_total_num_modes(eModified_C, P), + "Output vector too small - see get_total_num_modes."); + int mode = 0; + for (int p = 0; p < P; p++) { + for (int q = 0; q < (P - p); q++) { + for (int r = 0; r < (P - p - q); r++) { + b.at(mode) = eval_modC_ijk(p, q, r, z); + mode++; + } + } + } +} + +/** + * Reference implementation to compute eModifiedPyr_C for order P-1 and point + * z. + * + * @param[in] P Number of modes to compute. + * @param[in] z Point in [-1, 1] to evaluate at. + * @param[in, out] b Basis functions evaluated at z for each mode. + */ +void eval_modPyrC(const int P, const double z, std::vector &b) { + NESOASSERT(b.size() >= get_total_num_modes(eModifiedPyr_C, P), + "Output vector too small - see get_total_num_modes."); + int mode = 0; + for (int p = 0; p < P; p++) { + for (int q = 0; q < P; q++) { + for (int r = 0; r < (P - std::max(p, q)); r++) { + b.at(mode) = eval_modPyrC_ijk(p, q, r, z); + mode++; + } + } + } +} + +/** + * Reference implementation to compute a modified basis for order P-1 and + * point z. + * + * @param[in] basis_type Basis type to compute. + * @param[in] P Number of modes to compute. + * @param[in] z Point in [-1, 1] to evaluate at. + * @param[in, out] b Basis functions evaluated at z for each mode. + */ +void eval_basis(const BasisType basis_type, const int P, const double z, + std::vector &b) { + if (basis_type == eModified_A) { + return eval_modA(P, z, b); + } else if (basis_type == eModified_B) { + return eval_modB(P, z, b); + } else if (basis_type == eModified_C) { + return eval_modC(P, z, b); + } else if (basis_type == eModifiedPyr_C) { + return eval_modPyrC(P, z, b); + } else { + NESOASSERT(false, "unknown basis type"); + } +} + +/** + * Get the total number of modes in a given shape for a given number of input + * modes. + * + * @param[in] shape_type Shape type to query number of values for. + * @param[in] P Number of modes, i.e. Nektar GetNumModes(), in each dimension; + * @param[in, out] max_n (optional) Get the maximum Jacobi polynomial order + * required. + * @param[in, out] max_alpha (optional) Get the maximum Jacobi alpha value + * required. + * @returns Total number of values required to represent the basis with the + * given number of modes. + */ +int get_total_num_modes(const ShapeType shape_type, const int P, int *max_n, + int *max_alpha) { + + int n = 0; + int alpha = 0; + + auto lambda_A = [&](const int p) { + n = std::max(n, p - 2); + alpha = std::max(alpha, 1); + }; + auto lambda_B = [&](const int p, const int q) { + n = std::max(n, q - 1); + alpha = std::max(alpha, 2 * p - 1); + }; + auto lambda_C = [&](const int p, const int q, const int r) { + lambda_B(p + q, r); + }; + auto lambda_PyrC = [&](const int p, const int q, const int r) { + lambda_B(q, r); + lambda_B(1, r); + lambda_B(p, r); + n = std::max(n, r - 1); + alpha = std::max(alpha, 2 * p + 2 * q - 3); + }; + + int num_modes = -1; + if (shape_type == eTriangle) { + int mode = 0; + for (int p = 0; p < P; p++) { + for (int q = 0; q < P - p; q++) { + lambda_A(p); + lambda_B(p, q); + mode++; + } + } + num_modes = mode; + } else if (shape_type == eQuadrilateral) { + num_modes = P * P; + lambda_A(P - 1); + } else if (shape_type == eHexahedron) { + num_modes = P * P * P; + lambda_A(P - 1); + } else if (shape_type == ePyramid) { + int mode = 0; + for (int p = 0; p < P; ++p) { + for (int q = 0; q < P; ++q) { + int maxpq = max(p, q); + for (int r = 0; r < P - maxpq; ++r) { + lambda_A(p); + lambda_A(q); + lambda_PyrC(p, q, r); + mode++; + } + } + } + num_modes = mode; + } else if (shape_type == ePrism) { + int mode = 0; + for (int p = 0; p < P; p++) { + for (int q = 0; q < P; q++) { + for (int r = 0; r < (P - p); r++) { + lambda_A(p); + lambda_A(q); + lambda_B(p, r); + mode++; + } + } + } + num_modes = mode; + } else if (shape_type == eTetrahedron) { + int mode = 0; + for (int p = 0; p < P; p++) { + for (int q = 0; q < (P - p); q++) { + for (int r = 0; r < (P - p - q); r++) { + lambda_A(p); + lambda_B(p, q); + lambda_C(p, q, r); + mode++; + } + } + } + num_modes = mode; + } else { + NESOASSERT(false, "unknown shape type."); + } + + if (max_n != nullptr) { + *max_n = n; + } + if (max_alpha != nullptr) { + *max_alpha = alpha; + } + + return num_modes; +} + +/** + * Evaluate all the basis function modes for a geometry object with P modes in + * each coordinate direction using calls to eval_modA, ..., eval_modPyrC. + * + * @param[in] shape_type Geometry shape type to compute modes for, e.g. + * eHexahedron. + * @param[in] P Number of modes in each dimesion. + * @param[in] eta0 Evaluation point, first dimension. + * @param[in] eta1 Evaluation point, second dimension. + * @param[in] eta2 Evaluation point, third dimension. + * @param[in, out] b Output vector of mode evaluations. + */ +void eval_modes(const LibUtilities::ShapeType shape_type, const int P, + const double eta0, const double eta1, const double eta2, + std::vector &b) { + + NESOASSERT(b.size() >= get_total_num_modes(shape_type, P), + "Output vector too small - see get_total_num_modes."); + + if (shape_type == eTriangle) { + int mode = 0; + for (int px = 0; px < P; px++) { + for (int qx = 0; qx < P - px; qx++) { + const REAL etmp0 = (mode == 1) ? 1.0 : eval_modA_i(px, eta0); + const REAL etmp1 = eval_modB_ij(px, qx, eta1); + b[mode] = etmp0 * etmp1; + mode++; + } + } + } else if (shape_type == eQuadrilateral) { + int mode = 0; + for (int my = 0; my < P; my++) { + for (int mx = 0; mx < P; mx++) { + b[mode] = eval_modA_i(mx, eta0) * eval_modA_i(my, eta1); + mode++; + } + } + } else if (shape_type == eHexahedron) { + int mode = 0; + for (int mz = 0; mz < P; mz++) { + for (int my = 0; my < P; my++) { + for (int mx = 0; mx < P; mx++) { + b[mode] = eval_modA_i(mx, eta0) * eval_modA_i(my, eta1) * + eval_modA_i(mz, eta2); + mode++; + } + } + } + } else if (shape_type == ePyramid) { + int mode = 0; + for (int p = 0; p < P; p++) { + for (int q = 0; q < P; q++) { + for (int r = 0; r < P - std::max(p, q); r++) { + const double contrib_0 = eval_modA_i(p, eta0); + const double contrib_1 = eval_modA_i(q, eta1); + const double contrib_2 = eval_modPyrC_ijk(p, q, r, eta2); + if (mode == 1) { + b[mode] = contrib_2; + } else { + b[mode] = contrib_0 * contrib_1 * contrib_2; + } + mode++; + } + } + } + } else if (shape_type == ePrism) { + int mode = 0; + int mode_pr = 0; + for (int p = 0; p < P; p++) { + for (int q = 0; q < P; q++) { + for (int r = 0; r < (P - p); r++) { + + const double contrib_0 = eval_modA_i(p, eta0); + const double contrib_1 = eval_modA_i(q, eta1); + const double contrib_2 = eval_modB_ij(p, r, eta2); + + if ((p == 0) && (r == 1)) { + b[mode] = contrib_1 * contrib_2; + } else { + b[mode] = contrib_0 * contrib_1 * contrib_2; + } + mode++; + } + } + mode_pr += P - p; + } + } else if (shape_type == eTetrahedron) { + int mode = 0; + for (int p = 0; p < P; p++) { + for (int q = 0; q < (P - p); q++) { + for (int r = 0; r < (P - p - q); r++) { + const double contrib_0 = eval_modA_i(p, eta0); + const double contrib_1 = eval_modB_ij(p, q, eta1); + const double contrib_2 = eval_modC_ijk(p, q, r, eta2); + + if (mode == 1) { + b[mode] = contrib_2; + } else if (p == 0 && q == 1) { + b[mode] = contrib_1 * contrib_2; + } else { + b[mode] = contrib_0 * contrib_1 * contrib_2; + } + mode++; + } + } + } + } else { + NESOASSERT(false, "unknown shape type."); + } +} + +} // namespace NESO::BasisReference diff --git a/src/nektar_interface/expansion_looping/jacobi_coeff_mod_basis.cpp b/src/nektar_interface/expansion_looping/jacobi_coeff_mod_basis.cpp new file mode 100644 index 00000000..52b8e4c9 --- /dev/null +++ b/src/nektar_interface/expansion_looping/jacobi_coeff_mod_basis.cpp @@ -0,0 +1,82 @@ +#include + +namespace NESO { + +/** + * Compute coefficients for computing Jacobi polynomial values via recursion + * relation. Coefficients are computed such that: + * P_^{alpha, 1}_{n} = + * (coeffs_pnm10) * P_^{alpha, 1}_{n-1} * z + * + (coeffs_pnm11) * P_^{alpha, 1}_{n-1} + * + (coeffs_pnm2) * P_^{alpha, 1}_{n-2} + * + * @param max_n Maximum polynomial order required. + * @param max_alpha Maximum alpha value required. + */ +JacobiCoeffModBasis::JacobiCoeffModBasis(const int max_n, const int max_alpha) + : max_n(max_n), max_alpha(max_alpha), stride_n(max_n + 1) { + + const int beta = 1; + this->coeffs_pnm10.reserve((max_n + 1) * (max_alpha + 1)); + this->coeffs_pnm11.reserve((max_n + 1) * (max_alpha + 1)); + this->coeffs_pnm2.reserve((max_n + 1) * (max_alpha + 1)); + + for (int alphax = 0; alphax <= max_alpha; alphax++) { + for (int nx = 0; nx <= max_n; nx++) { + const double a = nx + alphax; + const double b = nx + beta; + const double c = a + b; + const double n = nx; + + const double c_pn = 2.0 * n * (c - n) * (c - 2.0); + const double c_pnm10 = (c - 1.0) * c * (c - 2); + const double c_pnm11 = (c - 1.0) * (a - b) * (c - 2 * n); + const double c_pnm2 = -2.0 * (a - 1.0) * (b - 1.0) * c; + const double ic_pn = 1.0 / c_pn; + + this->coeffs_pnm10.push_back(ic_pn * c_pnm10); + this->coeffs_pnm11.push_back(ic_pn * c_pnm11); + this->coeffs_pnm2.push_back(ic_pn * c_pnm2); + } + } +} + +/** + * Compute P^{alpha,1}_n(z) using recursion. + * + * @param n Order of Jacobi polynomial + * @param alpha Alpha value. + * @param z Point to evaluate at. + * @returns P^{alpha,1}_n(z). + */ +double JacobiCoeffModBasis::host_evaluate(const int n, const int alpha, + const double z) { + + NESOASSERT((0 <= n) && (n <= this->max_n), "Bad order - not in [0, max_n]."); + NESOASSERT((0 <= alpha) && (alpha <= this->max_alpha), + "Bad alpha - not in [0, max_alpha]."); + + double pnm2 = 1.0; + if (n == 0) { + return pnm2; + } + const int beta = 1; + double pnm1 = 0.5 * (2 * (alpha + 1) + (alpha + beta + 2) * (z - 1.0)); + if (n == 1) { + return pnm1; + } + + double pn; + for (int nx = 2; nx <= n; nx++) { + const double c_pnm10 = this->coeffs_pnm10[this->stride_n * alpha + nx]; + const double c_pnm11 = this->coeffs_pnm11[this->stride_n * alpha + nx]; + const double c_pnm2 = this->coeffs_pnm2[this->stride_n * alpha + nx]; + pn = c_pnm10 * pnm1 * z + c_pnm11 * pnm1 + c_pnm2 * pnm2; + pnm2 = pnm1; + pnm1 = pn; + } + + return pn; +} + +} // namespace NESO diff --git a/src/nektar_interface/function_evaluation.cpp b/src/nektar_interface/function_evaluation.cpp new file mode 100644 index 00000000..4010915f --- /dev/null +++ b/src/nektar_interface/function_evaluation.cpp @@ -0,0 +1,9 @@ +#include + +namespace NESO { + +template void +FieldEvaluate::evaluate(Sym sym); +template void FieldEvaluate::evaluate(Sym sym); + +} // namespace NESO diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ea91a39d..6d26a219 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -40,11 +40,14 @@ set(UNIT_SRC_FILES ${UNIT_SRC}/particle_utility/test_position_distribution.cpp ${UNIT_SRC}/particle_utility/test_particle_initialisation_line.cpp ${UNIT_SRC}/nektar_interface/test_particle_function_evaluation.cpp + ${UNIT_SRC}/nektar_interface/test_particle_function_evaluation_3d.cpp ${UNIT_SRC}/nektar_interface/test_parameter_store.cpp ${UNIT_SRC}/nektar_interface/test_particle_function_projection.cpp + ${UNIT_SRC}/nektar_interface/test_particle_function_projection_3d.cpp ${UNIT_SRC}/nektar_interface/test_particle_geometry_interface.cpp ${UNIT_SRC}/nektar_interface/test_particle_geometry_interface_3d.cpp ${UNIT_SRC}/nektar_interface/test_basis_evaluation.cpp + ${UNIT_SRC}/nektar_interface/test_kernel_basis_evaluation.cpp ${UNIT_SRC}/nektar_interface/test_particle_mapping.cpp ${UNIT_SRC}/nektar_interface/test_utility_cartesian_mesh.cpp ${UNIT_SRC}/test_solver_callback.cpp) @@ -58,6 +61,8 @@ set(INTEGRATION_SRC_FILES ${INTEGRATION_SRC}/solvers/solver_test_utils.cpp ${INTEGRATION_SRC}/solvers/SimpleSOL/test_SimpleSOL.cpp ${INTEGRATION_SRC}/nektar_interface/test_particle_advection.cpp + ${INTEGRATION_SRC}/nektar_interface/test_function_projection_order.cpp + ${INTEGRATION_SRC}/nektar_interface/test_function_projection_order_3d.cpp ${INTEGRATION_SRC}/solvers/Electrostatic2D3V/TwoStream/test_two_stream.cpp ${INTEGRATION_SRC}/solvers/Electrostatic2D3V/ElectronBernsteinWaves/test_ebw.cpp ${INTEGRATION_SRC}/solvers/Electrostatic2D3V/Integrators/boris_uniform_b.cpp diff --git a/test/integration/nektar_interface/test_function_projection_order.cpp b/test/integration/nektar_interface/test_function_projection_order.cpp new file mode 100644 index 00000000..58420632 --- /dev/null +++ b/test/integration/nektar_interface/test_function_projection_order.cpp @@ -0,0 +1,681 @@ +#include "nektar_interface/function_projection.hpp" +#include "nektar_interface/particle_interface.hpp" +#include "nektar_interface/utilities.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Nektar; +using namespace Nektar::SolverUtils; +using namespace Nektar::SpatialDomains; +using namespace Nektar::MultiRegions; +using namespace NESO::Particles; + +static inline void copy_to_cstring(std::string input, char **output) { + *output = new char[input.length() + 1]; + std::strcpy(*output, input.c_str()); +} + +TEST(ParticleFunctionProjection, DisContScalarExpQuantity) { + + auto project_run = [&](const int N_total, const int samplex) { + const double tol = 1.0e-10; + int argc = 3; + char *argv[3]; + + std::filesystem::path source_file = __FILE__; + std::filesystem::path source_dir = source_file.parent_path(); + std::filesystem::path test_resources_dir = + source_dir / "../../test_resources"; + std::filesystem::path mesh_file = + test_resources_dir / "square_triangles_quads_nummodes_2.xml"; + std::filesystem::path conditions_file = + test_resources_dir / "conditions.xml"; + + copy_to_cstring(std::string("test_particle_function_projection"), &argv[0]); + copy_to_cstring(std::string(mesh_file), &argv[1]); + copy_to_cstring(std::string(conditions_file), &argv[2]); + + LibUtilities::SessionReaderSharedPtr session; + SpatialDomains::MeshGraphSharedPtr graph; + // Create session reader. + session = LibUtilities::SessionReader::CreateInstance(argc, argv); + graph = SpatialDomains::MeshGraph::Read(session); + + auto dis_cont_field = std::make_shared(session, graph, "u"); + + auto mesh = std::make_shared(graph); + auto sycl_target = std::make_shared(0, mesh->get_comm()); + + auto nektar_graph_local_mapper = + std::make_shared(sycl_target, mesh); + + auto domain = std::make_shared(mesh, nektar_graph_local_mapper); + + const int ndim = 2; + ParticleSpec particle_spec{ParticleProp(Sym("P"), ndim, true), + ParticleProp(Sym("CELL_ID"), 1, true), + ParticleProp(Sym("Q"), 1)}; + + auto A = + std::make_shared(domain, particle_spec, sycl_target); + + NektarCartesianPeriodic pbc(sycl_target, graph, A->position_dat); + auto cell_id_translation = + std::make_shared(sycl_target, A->cell_id_dat, mesh); + + const int rank = sycl_target->comm_pair.rank_parent; + const int size = sycl_target->comm_pair.size_parent; + + std::mt19937 rng_pos(52234234 + samplex); + + int rstart, rend; + get_decomp_1d(size, N_total, rank, &rstart, &rend); + const int N = rend - rstart; + + int N_check = -1; + MPICHK(MPI_Allreduce(&N, &N_check, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD)); + NESOASSERT(N_check == N_total, "Error creating particles"); + + const int cell_count = domain->mesh->get_cell_count(); + + if (N > 0) { + auto positions = + uniform_within_extents(N_total, ndim, pbc.global_extent, rng_pos); + + std::uniform_int_distribution uniform_dist( + 0, sycl_target->comm_pair.size_parent - 1); + ParticleSet initial_distribution(N, A->get_particle_spec()); + for (int px = 0; px < N; px++) { + for (int dimx = 0; dimx < ndim; dimx++) { + const double pos_orig = + positions[dimx][rstart + px] + pbc.global_origin[dimx]; + initial_distribution[Sym("P")][px][dimx] = pos_orig; + } + initial_distribution[Sym("CELL_ID")][px][0] = px % cell_count; + } + A->add_particles_local(initial_distribution); + } + reset_mpi_ranks((*A)[Sym("NESO_MPI_RANK")]); + + MeshHierarchyGlobalMap mesh_hierarchy_global_map( + sycl_target, domain->mesh, A->position_dat, A->cell_id_dat, + A->mpi_rank_dat); + + pbc.execute(); + mesh_hierarchy_global_map.execute(); + A->hybrid_move(); + cell_id_translation->execute(); + A->cell_move(); + + const auto k_P = (*A)[Sym("P")]->cell_dat.device_ptr(); + auto k_Q = (*A)[Sym("Q")]->cell_dat.device_ptr(); + + const auto pl_iter_range = A->mpi_rank_dat->get_particle_loop_iter_range(); + const auto pl_stride = A->mpi_rank_dat->get_particle_loop_cell_stride(); + const auto pl_npart_cell = A->mpi_rank_dat->get_particle_loop_npart_cell(); + const REAL two_over_sqrt_pi = 1.1283791670955126; + const REAL reweight = + pbc.global_extent[0] * pbc.global_extent[1] / ((REAL)N_total); + + sycl_target->queue + .submit([&](sycl::handler &cgh) { + cgh.parallel_for<>( + sycl::range<1>(pl_iter_range), [=](sycl::id<1> idx) { + NESO_PARTICLES_KERNEL_START + const INT cellx = NESO_PARTICLES_KERNEL_CELL; + const INT layerx = NESO_PARTICLES_KERNEL_LAYER; + + const REAL x = k_P[cellx][0][layerx]; + const REAL y = k_P[cellx][1][layerx]; + const REAL exp_eval = + two_over_sqrt_pi * exp(-(4.0 * ((x) * (x) + (y) * (y)))); + k_Q[cellx][0][layerx] = exp_eval * reweight; + + NESO_PARTICLES_KERNEL_END + }); + }) + .wait_and_throw(); + + // create projection object + auto field_project = std::make_shared>( + dis_cont_field, A, cell_id_translation); + + // evaluate field at particle locations + field_project->project(Sym("Q")); + + const int tot_quad_points = dis_cont_field->GetTotPoints(); + Array phys_projected(tot_quad_points); + Array phys_correct(tot_quad_points); + + phys_projected = dis_cont_field->GetPhys(); + + // H5Part h5part("exp.h5part", A, Sym("P"), Sym("NESO_MPI_RANK"), + // Sym("NESO_REFERENCE_POSITIONS"), Sym("Q")); + // h5part.write(); + // h5part.close(); + + // write_vtu(dis_cont_field, "func_projected.vtu", "u"); + + auto lambda_f = [&](const NekDouble x, const NekDouble y) { + const REAL two_over_sqrt_pi = 1.1283791670955126; + const REAL exp_eval = + two_over_sqrt_pi * exp(-(4.0 * ((x) * (x) + (y) * (y)))); + return exp_eval; + }; + interpolate_onto_nektar_field_2d(lambda_f, dis_cont_field); + phys_correct = dis_cont_field->GetPhys(); + + // write_vtu(dis_cont_field, "func_correct.vtu", "u"); + + const double err = dis_cont_field->L2(phys_projected, phys_correct); + + mesh->free(); + + delete[] argv[0]; + delete[] argv[1]; + delete[] argv[2]; + + return err; + }; + + const int Nsample = 4; + + std::vector Nparticles; + Nparticles.push_back(200000); + Nparticles.push_back(800000); + + std::map> R_errors; + + for (auto N_total : Nparticles) { + for (int samplex = 0; samplex < Nsample; samplex++) { + const double err = project_run(N_total, samplex); + // nprint(N_total, err); + R_errors[N_total].push_back(err); + } + } + + auto lambda_average = [=](std::vector errors) { + double avg = 0.0; + for (auto &err : errors) { + avg += err; + } + avg /= errors.size(); + return avg; + }; + + const double err_0 = lambda_average(R_errors[Nparticles[0]]); + const double err_1 = lambda_average(R_errors[Nparticles[1]]); + + // nprint(err_0, err_1); + // nprint(err_0 / err_1); + + ASSERT_NEAR(ABS(err_0 / err_1), 2.0, 0.075); +} + +TEST(ParticleFunctionProjection, ContScalarExpQuantity) { + + auto project_run = [&](const int N_total, const int samplex) { + const double tol = 1.0e-10; + int argc = 3; + char *argv[3]; + + std::filesystem::path source_file = __FILE__; + std::filesystem::path source_dir = source_file.parent_path(); + std::filesystem::path test_resources_dir = + source_dir / "../../test_resources"; + std::filesystem::path mesh_file = + test_resources_dir / "square_triangles_quads_nummodes_2.xml"; + std::filesystem::path conditions_file = + test_resources_dir / "conditions_cg.xml"; + + copy_to_cstring(std::string("test_particle_function_projection"), &argv[0]); + copy_to_cstring(std::string(mesh_file), &argv[1]); + copy_to_cstring(std::string(conditions_file), &argv[2]); + + LibUtilities::SessionReaderSharedPtr session; + SpatialDomains::MeshGraphSharedPtr graph; + // Create session reader. + session = LibUtilities::SessionReader::CreateInstance(argc, argv); + graph = SpatialDomains::MeshGraph::Read(session); + + auto cont_field = std::make_shared(session, graph, "u"); + + auto mesh = std::make_shared(graph); + auto sycl_target = std::make_shared(0, mesh->get_comm()); + + auto nektar_graph_local_mapper = + std::make_shared(sycl_target, mesh); + + auto domain = std::make_shared(mesh, nektar_graph_local_mapper); + + const int ndim = 2; + ParticleSpec particle_spec{ParticleProp(Sym("P"), ndim, true), + ParticleProp(Sym("CELL_ID"), 1, true), + ParticleProp(Sym("Q"), 1)}; + + auto A = + std::make_shared(domain, particle_spec, sycl_target); + + NektarCartesianPeriodic pbc(sycl_target, graph, A->position_dat); + auto cell_id_translation = + std::make_shared(sycl_target, A->cell_id_dat, mesh); + + const int rank = sycl_target->comm_pair.rank_parent; + const int size = sycl_target->comm_pair.size_parent; + + std::mt19937 rng_pos(52234234 + samplex); + + int rstart, rend; + get_decomp_1d(size, N_total, rank, &rstart, &rend); + int N = rend - rstart; + + int N_check = -1; + MPICHK(MPI_Allreduce(&N, &N_check, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD)); + NESOASSERT(N_check == N_total, "Error creating particles"); + + const int cell_count = domain->mesh->get_cell_count(); + + if (N > 0) { + auto positions = + uniform_within_extents(N_total, ndim, pbc.global_extent, rng_pos); + + std::uniform_int_distribution uniform_dist( + 0, sycl_target->comm_pair.size_parent - 1); + ParticleSet initial_distribution(N, A->get_particle_spec()); + for (int px = 0; px < N; px++) { + for (int dimx = 0; dimx < ndim; dimx++) { + const double pos_orig = + positions[dimx][rstart + px] + pbc.global_origin[dimx]; + initial_distribution[Sym("P")][px][dimx] = pos_orig; + } + initial_distribution[Sym("CELL_ID")][px][0] = px % cell_count; + } + A->add_particles_local(initial_distribution); + } + reset_mpi_ranks((*A)[Sym("NESO_MPI_RANK")]); + + MeshHierarchyGlobalMap mesh_hierarchy_global_map( + sycl_target, domain->mesh, A->position_dat, A->cell_id_dat, + A->mpi_rank_dat); + + pbc.execute(); + mesh_hierarchy_global_map.execute(); + A->hybrid_move(); + cell_id_translation->execute(); + A->cell_move(); + + const auto k_P = (*A)[Sym("P")]->cell_dat.device_ptr(); + auto k_Q = (*A)[Sym("Q")]->cell_dat.device_ptr(); + + const auto pl_iter_range = A->mpi_rank_dat->get_particle_loop_iter_range(); + const auto pl_stride = A->mpi_rank_dat->get_particle_loop_cell_stride(); + const auto pl_npart_cell = A->mpi_rank_dat->get_particle_loop_npart_cell(); + const REAL two_over_sqrt_pi = 1.1283791670955126; + const REAL reweight = + pbc.global_extent[0] * pbc.global_extent[1] / ((REAL)N_total); + + sycl_target->queue + .submit([&](sycl::handler &cgh) { + cgh.parallel_for<>( + sycl::range<1>(pl_iter_range), [=](sycl::id<1> idx) { + NESO_PARTICLES_KERNEL_START + const INT cellx = NESO_PARTICLES_KERNEL_CELL; + const INT layerx = NESO_PARTICLES_KERNEL_LAYER; + + const REAL x = k_P[cellx][0][layerx]; + const REAL y = k_P[cellx][1][layerx]; + const REAL exp_eval = + two_over_sqrt_pi * exp(-(4.0 * ((x) * (x) + (y) * (y)))); + k_Q[cellx][0][layerx] = exp_eval * reweight; + + NESO_PARTICLES_KERNEL_END + }); + }) + .wait_and_throw(); + + // create projection object + auto field_project = std::make_shared>( + cont_field, A, cell_id_translation); + + // evaluate field at particle locations + field_project->project(Sym("Q")); + + const int tot_quad_points = cont_field->GetTotPoints(); + Array phys_projected(tot_quad_points); + Array phys_correct(tot_quad_points); + + phys_projected = cont_field->GetPhys(); + + // H5Part h5part("exp.h5part", A, Sym("P"), Sym("NESO_MPI_RANK"), + // Sym("NESO_REFERENCE_POSITIONS"), Sym("Q")); + // h5part.write(); + // h5part.close(); + + // write_vtu(cont_field, "func_projected_" + std::to_string(rank) + + // "_0.vtu", "u"); nprint("project integral:", cont_field->Integral(), + // 0.876184); + + auto lambda_f = [&](const NekDouble x, const NekDouble y) { + const REAL two_over_sqrt_pi = 1.1283791670955126; + const REAL exp_eval = + two_over_sqrt_pi * exp(-(4.0 * ((x) * (x) + (y) * (y)))); + return exp_eval; + }; + interpolate_onto_nektar_field_2d(lambda_f, cont_field); + phys_correct = cont_field->GetPhys(); + + // write_vtu(cont_field, "func_correct_" + std::to_string(rank) + "_0.vtu", + // "u"); nprint("correct integral:", cont_field->Integral(), 0.876184); + + const double err = cont_field->L2(phys_projected, phys_correct); + + mesh->free(); + + delete[] argv[0]; + delete[] argv[1]; + delete[] argv[2]; + + return err; + }; + + const int Nsample = 4; + + std::vector Nparticles; + Nparticles.push_back(200000); + Nparticles.push_back(800000); + + std::map> R_errors; + + for (auto N_total : Nparticles) { + for (int samplex = 0; samplex < Nsample; samplex++) { + const double err = project_run(N_total, samplex); + // nprint(N_total, err); + R_errors[N_total].push_back(err); + } + } + + auto lambda_average = [=](std::vector errors) { + double avg = 0.0; + for (auto &err : errors) { + avg += err; + } + avg /= errors.size(); + return avg; + }; + + const double err_0 = lambda_average(R_errors[Nparticles[0]]); + const double err_1 = lambda_average(R_errors[Nparticles[1]]); + + // nprint(err_0, err_1); + // nprint(err_0 / err_1); + + ASSERT_NEAR(ABS(err_0 / err_1), 2.0, 0.075); +} + +TEST(ParticleFunctionProjection, ContScalarExpQuantityMultiple) { + + auto project_run = [&](const int N_total, const int samplex, double *err) { + const double tol = 1.0e-10; + int argc = 3; + char *argv[3]; + + std::filesystem::path source_file = __FILE__; + std::filesystem::path source_dir = source_file.parent_path(); + std::filesystem::path test_resources_dir = + source_dir / "../../test_resources"; + std::filesystem::path mesh_file = + test_resources_dir / "square_triangles_quads_nummodes_2.xml"; + std::filesystem::path conditions_file = + test_resources_dir / "conditions_cg.xml"; + + copy_to_cstring(std::string("test_particle_function_projection"), &argv[0]); + copy_to_cstring(std::string(mesh_file), &argv[1]); + copy_to_cstring(std::string(conditions_file), &argv[2]); + + LibUtilities::SessionReaderSharedPtr session; + SpatialDomains::MeshGraphSharedPtr graph; + // Create session reader. + session = LibUtilities::SessionReader::CreateInstance(argc, argv); + graph = SpatialDomains::MeshGraph::Read(session); + + auto mesh = std::make_shared(graph); + auto sycl_target = std::make_shared(0, mesh->get_comm()); + + auto nektar_graph_local_mapper = + std::make_shared(sycl_target, mesh); + + auto domain = std::make_shared(mesh, nektar_graph_local_mapper); + + const int ndim = 2; + ParticleSpec particle_spec{ParticleProp(Sym("P"), ndim, true), + ParticleProp(Sym("CELL_ID"), 1, true), + ParticleProp(Sym("Q"), 2), + ParticleProp(Sym("Q2"), 3)}; + + auto A = + std::make_shared(domain, particle_spec, sycl_target); + + NektarCartesianPeriodic pbc(sycl_target, graph, A->position_dat); + auto cell_id_translation = + std::make_shared(sycl_target, A->cell_id_dat, mesh); + + const int rank = sycl_target->comm_pair.rank_parent; + const int size = sycl_target->comm_pair.size_parent; + + std::mt19937 rng_pos(52234234 + samplex); + + int rstart, rend; + get_decomp_1d(size, N_total, rank, &rstart, &rend); + int N = rend - rstart; + + int N_check = -1; + MPICHK(MPI_Allreduce(&N, &N_check, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD)); + NESOASSERT(N_check == N_total, "Error creating particles"); + + const int cell_count = domain->mesh->get_cell_count(); + + if (N > 0) { + auto positions = + uniform_within_extents(N_total, ndim, pbc.global_extent, rng_pos); + + std::uniform_int_distribution uniform_dist( + 0, sycl_target->comm_pair.size_parent - 1); + ParticleSet initial_distribution(N, A->get_particle_spec()); + for (int px = 0; px < N; px++) { + for (int dimx = 0; dimx < ndim; dimx++) { + const double pos_orig = + positions[dimx][rstart + px] + pbc.global_origin[dimx]; + initial_distribution[Sym("P")][px][dimx] = pos_orig; + } + initial_distribution[Sym("CELL_ID")][px][0] = px % cell_count; + } + A->add_particles_local(initial_distribution); + } + reset_mpi_ranks((*A)[Sym("NESO_MPI_RANK")]); + + MeshHierarchyGlobalMap mesh_hierarchy_global_map( + sycl_target, domain->mesh, A->position_dat, A->cell_id_dat, + A->mpi_rank_dat); + + pbc.execute(); + mesh_hierarchy_global_map.execute(); + A->hybrid_move(); + cell_id_translation->execute(); + A->cell_move(); + + const auto k_P = (*A)[Sym("P")]->cell_dat.device_ptr(); + auto k_Q = (*A)[Sym("Q")]->cell_dat.device_ptr(); + auto k_Q2 = (*A)[Sym("Q2")]->cell_dat.device_ptr(); + + const auto pl_iter_range = A->mpi_rank_dat->get_particle_loop_iter_range(); + const auto pl_stride = A->mpi_rank_dat->get_particle_loop_cell_stride(); + const auto pl_npart_cell = A->mpi_rank_dat->get_particle_loop_npart_cell(); + const REAL two_over_sqrt_pi = 1.1283791670955126; + const REAL reweight = + pbc.global_extent[0] * pbc.global_extent[1] / ((REAL)N_total); + + sycl_target->queue + .submit([&](sycl::handler &cgh) { + cgh.parallel_for<>( + sycl::range<1>(pl_iter_range), [=](sycl::id<1> idx) { + NESO_PARTICLES_KERNEL_START + const INT cellx = NESO_PARTICLES_KERNEL_CELL; + const INT layerx = NESO_PARTICLES_KERNEL_LAYER; + + const REAL x = k_P[cellx][0][layerx]; + const REAL y = k_P[cellx][1][layerx]; + const REAL exp_eval = + two_over_sqrt_pi * exp(-(4.0 * ((x) * (x) + (y) * (y)))); + k_Q[cellx][0][layerx] = exp_eval * reweight; + k_Q[cellx][1][layerx] = -1.0 * exp_eval * reweight; + k_Q2[cellx][1][layerx] = reweight; + + NESO_PARTICLES_KERNEL_END + }); + }) + .wait_and_throw(); + + auto cont_field_u = std::make_shared(session, graph, "u"); + auto cont_field_v = std::make_shared(session, graph, "v"); + auto cont_field_n = std::make_shared(session, graph, "n"); + + std::vector> cont_fields = { + cont_field_u, cont_field_v, cont_field_n}; + // create projection object + auto field_project = std::make_shared>( + cont_fields, A, cell_id_translation); + + // const double err = 1.0; + + // project field at particle locations + std::vector> project_syms = {Sym("Q"), Sym("Q"), + Sym("Q2")}; + std::vector project_components = {0, 1, 1}; + + if (samplex == 0) { + field_project->testing_enable(); + } + field_project->project(project_syms, project_components); + if (samplex == 0) { + // Checks that the SYCL version matches the original version computed + // using nektar + field_project->project_host(project_syms, project_components); + double *rhs_host, *rhs_device; + field_project->testing_get_rhs(&rhs_host, &rhs_device); + const int ncoeffs = cont_field_u->GetNcoeffs(); + for (int cx = 0; cx < ncoeffs; cx++) { + EXPECT_NEAR(rhs_host[cx], rhs_device[cx], 1.0e-5); + EXPECT_NEAR(rhs_host[cx + ncoeffs], rhs_device[cx + ncoeffs], 1.0e-5); + EXPECT_NEAR(rhs_host[cx + 2 * ncoeffs], rhs_device[cx + 2 * ncoeffs], + 1.0e-5); + } + } + + const int tot_quad_points = cont_field_u->GetTotPoints(); + Array phys_correct(tot_quad_points); + Array phys_projected_u(tot_quad_points); + Array phys_projected_v(tot_quad_points); + Array phys_projected_n(tot_quad_points); + + phys_projected_u = cont_field_u->GetPhys(); + phys_projected_v = cont_field_v->GetPhys(); + phys_projected_n = cont_field_n->GetPhys(); + + // write_vtu(cont_field_u, + // "func_projected_u_" + std::to_string(rank) + "_0.vtu", "u"); + // write_vtu(cont_field_v, + // "func_projected_v_" + std::to_string(rank) + "_0.vtu", "v"); + // write_vtu(cont_field_n, + // "func_projected_n_" + std::to_string(rank) + "_0.vtu", "n"); + + for (int cx = 0; cx < tot_quad_points; cx++) { + ASSERT_NEAR(phys_projected_u[cx], phys_projected_v[cx] * -1.0, 1.0e-2); + + // This bound is huge so there is also an L2 error norm test below. + ASSERT_NEAR(phys_projected_n[cx], 1.0, 0.4); + } + + // H5Part h5part("exp.h5part", A, Sym("P"), Sym("NESO_MPI_RANK"), + // Sym("NESO_REFERENCE_POSITIONS"), Sym("Q")); + // h5part.write(); + // h5part.close(); + // nprint("project integral:", cont_field_u->Integral(), + // 0.876184); + + auto lambda_f = [&](const NekDouble x, const NekDouble y) { + const REAL two_over_sqrt_pi = 1.1283791670955126; + const REAL exp_eval = + two_over_sqrt_pi * exp(-(4.0 * ((x) * (x) + (y) * (y)))); + return exp_eval; + }; + interpolate_onto_nektar_field_2d(lambda_f, cont_field_u); + phys_correct = cont_field_u->GetPhys(); + + // write_vtu(cont_field_u, "func_correct_" + std::to_string(rank) + + // "_0.vtu", "u"); nprint("correct integral:", cont_field_u->Integral(), + // 0.876184); + + *err = cont_field_u->L2(phys_projected_u, phys_correct); + + for (int cx = 0; cx < tot_quad_points; cx++) { + phys_correct[cx] = 1.0; + } + + const double err2 = cont_field_n->L2(phys_projected_n, phys_correct); + ASSERT_TRUE(err2 < 0.15); + + mesh->free(); + + delete[] argv[0]; + delete[] argv[1]; + delete[] argv[2]; + }; + + const int Nsample = 4; + + std::vector Nparticles; + Nparticles.push_back(200000); + Nparticles.push_back(800000); + + std::map> R_errors; + + for (auto N_total : Nparticles) { + for (int samplex = 0; samplex < Nsample; samplex++) { + double err = -1; + project_run(N_total, samplex, &err); + // nprint(N_total, err); + R_errors[N_total].push_back(err); + } + } + + auto lambda_average = [=](std::vector errors) { + double avg = 0.0; + for (auto &err : errors) { + avg += err; + } + avg /= errors.size(); + return avg; + }; + + const double err_0 = lambda_average(R_errors[Nparticles[0]]); + const double err_1 = lambda_average(R_errors[Nparticles[1]]); + + // nprint(err_0, err_1); + // nprint(err_0 / err_1); + + ASSERT_NEAR(ABS(err_0 / err_1), 2.0, 0.075); +} diff --git a/test/integration/nektar_interface/test_function_projection_order_3d.cpp b/test/integration/nektar_interface/test_function_projection_order_3d.cpp new file mode 100644 index 00000000..ade2b65e --- /dev/null +++ b/test/integration/nektar_interface/test_function_projection_order_3d.cpp @@ -0,0 +1,229 @@ +#include "nektar_interface/function_projection.hpp" +#include "nektar_interface/particle_interface.hpp" +#include "nektar_interface/utilities.hpp" +#include "particle_utility/position_distribution.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace Nektar; +using namespace Nektar::SolverUtils; +using namespace Nektar::SpatialDomains; +using namespace Nektar::MultiRegions; +using namespace NESO::Particles; + +static inline void copy_to_cstring(std::string input, char **output) { + *output = new char[input.length() + 1]; + std::strcpy(*output, input.c_str()); +} + +static inline NekDouble func(const NekDouble x, const NekDouble y, + const NekDouble z) { + return ((x + 1.0) * (x - 1.0) * (y + 1.0) * (y - 1.0) * (z + 1.0) * + (z - 1.0)); +}; + +template +static inline void projection_wrapper_order_3d(std::string condtions_file_s, + std::string mesh_file_s) { + + auto project_run = [&](const int N_total, const int samplex) { + std::filesystem::path source_file = __FILE__; + std::filesystem::path source_dir = source_file.parent_path(); + std::filesystem::path test_resources_dir = + source_dir / "../../test_resources"; + + std::filesystem::path condtions_file_basename{condtions_file_s}; + std::filesystem::path mesh_file_basename{mesh_file_s}; + std::filesystem::path conditions_file = + test_resources_dir / condtions_file_basename; + std::filesystem::path mesh_file = test_resources_dir / mesh_file_basename; + + int argc = 3; + char *argv[3]; + copy_to_cstring(std::string("test_particle_geometry_interface"), &argv[0]); + copy_to_cstring(std::string(conditions_file), &argv[1]); + copy_to_cstring(std::string(mesh_file), &argv[2]); + + LibUtilities::SessionReaderSharedPtr session; + SpatialDomains::MeshGraphSharedPtr graph; + // Create session reader. + session = LibUtilities::SessionReader::CreateInstance(argc, argv); + graph = SpatialDomains::MeshGraph::Read(session); + + auto mesh = std::make_shared(graph); + auto sycl_target = std::make_shared(0, mesh->get_comm()); + + std::mt19937 rng{182348}; + + auto nektar_graph_local_mapper = + std::make_shared(sycl_target, mesh); + auto domain = std::make_shared(mesh, nektar_graph_local_mapper); + + const int ndim = 3; + ParticleSpec particle_spec{ParticleProp(Sym("P"), ndim, true), + ParticleProp(Sym("CELL_ID"), 1, true), + ParticleProp(Sym("Q"), 1), + ParticleProp(Sym("ID"), 1)}; + + auto A = + std::make_shared(domain, particle_spec, sycl_target); + + NektarCartesianPeriodic pbc(sycl_target, graph, A->position_dat); + auto cell_id_translation = + std::make_shared(sycl_target, A->cell_id_dat, mesh); + const int rank = sycl_target->comm_pair.rank_parent; + const int size = sycl_target->comm_pair.size_parent; + + std::mt19937 rng_pos(52234234 + samplex + rank); + int rstart, rend; + get_decomp_1d(size, N_total, rank, &rstart, &rend); + const int N = rend - rstart; + int N_check = -1; + MPICHK(MPI_Allreduce(&N, &N_check, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD)); + NESOASSERT(N_check == N_total, "Error creating particles"); + const int cell_count = domain->mesh->get_cell_count(); + if (N > 0) { + auto positions = + uniform_within_extents(N_total, ndim, pbc.global_extent, rng_pos); + ParticleSet initial_distribution(N, A->get_particle_spec()); + for (int px = 0; px < N; px++) { + for (int dimx = 0; dimx < ndim; dimx++) { + const double pos_orig = positions[dimx][px] + pbc.global_origin[dimx]; + initial_distribution[Sym("P")][px][dimx] = pos_orig; + } + initial_distribution[Sym("CELL_ID")][px][0] = 0; + initial_distribution[Sym("ID")][px][0] = px; + } + A->add_particles_local(initial_distribution); + } + reset_mpi_ranks((*A)[Sym("NESO_MPI_RANK")]); + + pbc.execute(); + A->hybrid_move(); + cell_id_translation->execute(); + A->cell_move(); + + const auto k_P = (*A)[Sym("P")]->cell_dat.device_ptr(); + auto k_Q = (*A)[Sym("Q")]->cell_dat.device_ptr(); + + const auto pl_iter_range = A->mpi_rank_dat->get_particle_loop_iter_range(); + const auto pl_stride = A->mpi_rank_dat->get_particle_loop_cell_stride(); + const auto pl_npart_cell = A->mpi_rank_dat->get_particle_loop_npart_cell(); + const REAL reweight = pbc.global_extent[0] * pbc.global_extent[1] * + pbc.global_extent[2] / ((REAL)N_total); + + sycl_target->queue + .submit([&](sycl::handler &cgh) { + cgh.parallel_for<>(sycl::range<1>(pl_iter_range), + [=](sycl::id<1> idx) { + NESO_PARTICLES_KERNEL_START + const INT cellx = NESO_PARTICLES_KERNEL_CELL; + const INT layerx = NESO_PARTICLES_KERNEL_LAYER; + + const REAL x = k_P[cellx][0][layerx]; + const REAL y = k_P[cellx][1][layerx]; + const REAL z = k_P[cellx][2][layerx]; + + const REAL eval0 = func(x, y, z); + const REAL eval = reweight * eval0; + k_Q[cellx][0][layerx] = eval; + NESO_PARTICLES_KERNEL_END + }); + }) + .wait_and_throw(); + + auto field = std::make_shared(session, graph, "u"); + std::vector> fields = {field}; + auto field_project = std::make_shared>( + fields, A, cell_id_translation); + + field_project->testing_enable(); + std::vector> project_syms = {Sym("Q")}; + std::vector project_components = {0}; + + field_project->project(project_syms, project_components); + + const int tot_quad_points = field->GetTotPoints(); + Array phys_correct(tot_quad_points); + Array phys_projected_u(tot_quad_points); + phys_projected_u = field->GetPhys(); + interpolate_onto_nektar_field_3d(func, field); + phys_correct = field->GetPhys(); + + const double err = field->L2(phys_projected_u, phys_correct); + + A->free(); + mesh->free(); + + delete[] argv[0]; + delete[] argv[1]; + delete[] argv[2]; + return err; + }; + + const int Nsample = 4; + + std::vector Nparticles; + Nparticles.push_back(400000); + Nparticles.push_back(1600000); + + std::map> R_errors; + + for (auto N_total : Nparticles) { + for (int samplex = 0; samplex < Nsample; samplex++) { + const double err = project_run(N_total, samplex); + R_errors[N_total].push_back(err); + } + } + + auto lambda_average = [=](std::vector errors) { + double avg = 0.0; + for (auto &err : errors) { + avg += err; + } + avg /= errors.size(); + return avg; + }; + + const double err_0 = lambda_average(R_errors[Nparticles[0]]); + const double err_1 = lambda_average(R_errors[Nparticles[1]]); + + const double order_abs = ABS((err_0 / err_1)); + ASSERT_NEAR(order_abs, 2.0, 0.075); +} + +TEST(ParticleFunctionProjectionOrder3D, DisContFieldHex) { + projection_wrapper_order_3d( + "reference_hex_cube/conditions_nummodes_2.xml", + "reference_hex_cube/hex_cube_0.3_perturbed.xml"); +} +TEST(ParticleFunctionProjectionOrder3D, DisContFieldPrismTet) { + projection_wrapper_order_3d( + "reference_prism_tet_cube/conditions_nummodes_4.xml", + "reference_prism_tet_cube/prism_tet_cube_0.5_perturbed.xml"); +} +TEST(ParticleFunctionProjectionOrder3D, ContFieldHex) { + projection_wrapper_order_3d( + "reference_hex_cube/conditions_cg_nummodes_2.xml", + "reference_hex_cube/hex_cube_0.3_perturbed.xml"); +} +TEST(ParticleFunctionProjectionOrder3D, ContFieldPrismTet) { + projection_wrapper_order_3d( + "reference_prism_tet_cube/conditions_cg_nummodes_4.xml", + "reference_prism_tet_cube/prism_tet_cube_0.5_perturbed.xml"); +} diff --git a/test/test_resources/reference_all_types_cube/conditions.xml b/test/test_resources/reference_all_types_cube/conditions.xml index f9f79b23..5f8aa4b0 100644 --- a/test/test_resources/reference_all_types_cube/conditions.xml +++ b/test/test_resources/reference_all_types_cube/conditions.xml @@ -3,23 +3,24 @@ xsi:noNamespaceSchemaLocation="http://www.nektar.info/schema/nektar.xsd"> - - - - + + + + - + + - - + + diff --git a/test/test_resources/reference_all_types_cube/conditions_cg.xml b/test/test_resources/reference_all_types_cube/conditions_cg.xml new file mode 100644 index 00000000..b19e54d6 --- /dev/null +++ b/test/test_resources/reference_all_types_cube/conditions_cg.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +

Lambda = 0.0

+

epsilon = -1.0

+

num_particles_total = 600000

+

num_particles_per_cell = -1

+

particle_time_step = 0.001

+

particle_num_time_steps = 100

+

particle_num_write_particle_steps = 0

+

particle_num_write_field_energy_steps = 0

+

particle_num_write_field_steps = 0

+

particle_num_print_steps = 10

+

particle_distribution_position = 2

+

particle_initial_velocity = 1.0

+

particle_charge_density = 105.27578027828649

+

particle_number_density = 105.27578027828649

+
+ + + u + + + + C[100] + C[200] + C[300] + C[400] + C[500] + C[600] + + + + + + + + + + + + + + + + + + + + + + + +
+ +
diff --git a/test/test_resources/reference_hex_cube/conditions.xml b/test/test_resources/reference_hex_cube/conditions.xml new file mode 100644 index 00000000..50a46db1 --- /dev/null +++ b/test/test_resources/reference_hex_cube/conditions.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + u + + + + C[100-105] + + + + + + + + + + + diff --git a/test/test_resources/reference_hex_cube/conditions_cg.xml b/test/test_resources/reference_hex_cube/conditions_cg.xml new file mode 100644 index 00000000..630d699c --- /dev/null +++ b/test/test_resources/reference_hex_cube/conditions_cg.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + u + + + + C[100-105] + + + + + + + + + + + diff --git a/test/test_resources/reference_hex_cube/conditions_cg_nummodes_2.xml b/test/test_resources/reference_hex_cube/conditions_cg_nummodes_2.xml new file mode 100644 index 00000000..4fe9c5a0 --- /dev/null +++ b/test/test_resources/reference_hex_cube/conditions_cg_nummodes_2.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + u + + + + C[100-105] + + + + + + + + + + + diff --git a/test/test_resources/reference_hex_cube/conditions_nummodes_2.xml b/test/test_resources/reference_hex_cube/conditions_nummodes_2.xml new file mode 100644 index 00000000..f1c55df6 --- /dev/null +++ b/test/test_resources/reference_hex_cube/conditions_nummodes_2.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + u + + + + C[100-105] + + + + + + + + + + + diff --git a/test/test_resources/reference_hex_cube/hex_cube_0.3_perturbed.xml b/test/test_resources/reference_hex_cube/hex_cube_0.3_perturbed.xml new file mode 100644 index 00000000..5cb91558 --- /dev/null +++ b/test/test_resources/reference_hex_cube/hex_cube_0.3_perturbed.xml @@ -0,0 +1,4949 @@ + + + + + -1.00000000e+00 -1.00000000e+00 -1.00000000e+00 + -7.50000000e-01 -1.00000000e+00 -1.00000000e+00 + -7.50000000e-01 -7.50000000e-01 -1.00000000e+00 + -1.00000000e+00 -7.50000000e-01 -1.00000000e+00 + -1.00000000e+00 -1.00000000e+00 -7.50000000e-01 + -7.50000000e-01 -1.00000000e+00 -7.50000000e-01 + -7.09026325e-01 -7.97093832e-01 -7.75892556e-01 + -1.00000000e+00 -7.50000000e-01 -7.50000000e-01 + -1.00000000e+00 -1.00000000e+00 -5.00000000e-01 + -7.50000000e-01 -1.00000000e+00 -5.00000000e-01 + -7.74264671e-01 -7.04185999e-01 -5.23231598e-01 + -1.00000000e+00 -7.50000000e-01 -5.00000000e-01 + -1.00000000e+00 -1.00000000e+00 -2.50000000e-01 + -7.50000000e-01 -1.00000000e+00 -2.50000000e-01 + -7.30867249e-01 -7.65343280e-01 -2.03079194e-01 + -1.00000000e+00 -7.50000000e-01 -2.50000000e-01 + -1.00000000e+00 -1.00000000e+00 -2.75701684e-12 + -7.50000000e-01 -1.00000000e+00 -2.75696133e-12 + -7.00889124e-01 -7.57898988e-01 8.16357359e-03 + -1.00000000e+00 -7.50000000e-01 -2.75700296e-12 + -1.00000000e+00 -1.00000000e+00 2.50000000e-01 + -7.50000000e-01 -1.00000000e+00 2.50000000e-01 + -7.40603393e-01 -7.86491172e-01 2.57216717e-01 + -1.00000000e+00 -7.50000000e-01 2.50000000e-01 + -1.00000000e+00 -1.00000000e+00 5.00000000e-01 + -7.50000000e-01 -1.00000000e+00 5.00000000e-01 + -7.34880932e-01 -7.78017250e-01 4.62942460e-01 + -1.00000000e+00 -7.50000000e-01 5.00000000e-01 + -1.00000000e+00 -1.00000000e+00 7.50000000e-01 + -7.50000000e-01 -1.00000000e+00 7.50000000e-01 + -7.23609650e-01 -7.92981630e-01 7.09965864e-01 + -1.00000000e+00 -7.50000000e-01 7.50000000e-01 + -1.00000000e+00 -1.00000000e+00 1.00000000e+00 + -7.50000000e-01 -1.00000000e+00 1.00000000e+00 + -7.50000000e-01 -7.50000000e-01 1.00000000e+00 + -1.00000000e+00 -7.50000000e-01 1.00000000e+00 + -7.50000000e-01 -5.00000000e-01 -1.00000000e+00 + -1.00000000e+00 -5.00000000e-01 -1.00000000e+00 + -7.96680642e-01 -5.05092151e-01 -7.96783090e-01 + -1.00000000e+00 -5.00000000e-01 -7.50000000e-01 + -7.50869387e-01 -4.82878972e-01 -5.25308924e-01 + -1.00000000e+00 -5.00000000e-01 -5.00000000e-01 + -7.28986869e-01 -4.63337450e-01 -2.50894076e-01 + -1.00000000e+00 -5.00000000e-01 -2.50000000e-01 + -7.04310558e-01 -4.51785874e-01 -1.49281520e-03 + -1.00000000e+00 -5.00000000e-01 -2.75698908e-12 + -7.24388643e-01 -5.43707210e-01 2.25076778e-01 + -1.00000000e+00 -5.00000000e-01 2.50000000e-01 + -7.61147547e-01 -5.43677355e-01 5.23444701e-01 + -1.00000000e+00 -5.00000000e-01 5.00000000e-01 + -7.09504163e-01 -4.78616852e-01 7.88044652e-01 + -1.00000000e+00 -5.00000000e-01 7.50000000e-01 + -7.50000000e-01 -5.00000000e-01 1.00000000e+00 + -1.00000000e+00 -5.00000000e-01 1.00000000e+00 + -7.50000000e-01 -2.50000000e-01 -1.00000000e+00 + -1.00000000e+00 -2.50000000e-01 -1.00000000e+00 + -7.35085945e-01 -2.30571294e-01 -7.67234370e-01 + -1.00000000e+00 -2.50000000e-01 -7.50000000e-01 + -7.08643357e-01 -2.01766042e-01 -4.93730440e-01 + -1.00000000e+00 -2.50000000e-01 -5.00000000e-01 + -7.66664638e-01 -2.37681640e-01 -2.25879832e-01 + -1.00000000e+00 -2.50000000e-01 -2.50000000e-01 + -7.17393704e-01 -2.62015452e-01 3.63910662e-02 + -1.00000000e+00 -2.50000000e-01 -2.75698908e-12 + -7.47974221e-01 -2.54903794e-01 2.17625899e-01 + -1.00000000e+00 -2.50000000e-01 2.50000000e-01 + -7.13261251e-01 -2.16102583e-01 5.24260397e-01 + -1.00000000e+00 -2.50000000e-01 5.00000000e-01 + -7.43876805e-01 -2.86561066e-01 7.58178743e-01 + -1.00000000e+00 -2.50000000e-01 7.50000000e-01 + -7.50000000e-01 -2.50000000e-01 1.00000000e+00 + -1.00000000e+00 -2.50000000e-01 1.00000000e+00 + -7.50000000e-01 2.06773487e-12 -1.00000000e+00 + -1.00000000e+00 2.75701684e-12 -1.00000000e+00 + -7.44799514e-01 -2.55639241e-02 -7.40776226e-01 + -1.00000000e+00 2.75700990e-12 -7.50000000e-01 + -7.47522745e-01 -2.69910663e-02 -5.41807558e-01 + -1.00000000e+00 2.75700296e-12 -5.00000000e-01 + -7.10256239e-01 1.19024264e-02 -2.29946673e-01 + -1.00000000e+00 2.75698908e-12 -2.50000000e-01 + -7.02074152e-01 -1.67178092e-02 -1.77615608e-02 + -1.00000000e+00 2.75701684e-12 -2.75698908e-12 + -7.65088440e-01 7.70149677e-03 2.76527691e-01 + -1.00000000e+00 2.75701684e-12 2.50000000e-01 + -7.67076513e-01 -3.93165226e-02 5.01532655e-01 + -1.00000000e+00 2.75696133e-12 5.00000000e-01 + -7.02184280e-01 2.38876054e-02 7.06143017e-01 + -1.00000000e+00 2.75701684e-12 7.50000000e-01 + -7.50000000e-01 2.06773487e-12 1.00000000e+00 + -1.00000000e+00 2.75701684e-12 1.00000000e+00 + -7.50000000e-01 2.50000000e-01 -1.00000000e+00 + -1.00000000e+00 2.50000000e-01 -1.00000000e+00 + -7.43054929e-01 2.42257081e-01 -7.33878559e-01 + -1.00000000e+00 2.50000000e-01 -7.50000000e-01 + -7.33910833e-01 2.58492698e-01 -5.29863875e-01 + -1.00000000e+00 2.50000000e-01 -5.00000000e-01 + -7.92673247e-01 2.14008757e-01 -2.65758145e-01 + -1.00000000e+00 2.50000000e-01 -2.50000000e-01 + -7.58687160e-01 2.52178785e-01 -1.85040584e-02 + -1.00000000e+00 2.50000000e-01 -2.75701684e-12 + -7.71416303e-01 2.47346050e-01 2.92879628e-01 + -1.00000000e+00 2.50000000e-01 2.50000000e-01 + -7.24427889e-01 2.28040270e-01 5.08939544e-01 + -1.00000000e+00 2.50000000e-01 5.00000000e-01 + -7.14331893e-01 2.97603437e-01 7.23684918e-01 + -1.00000000e+00 2.50000000e-01 7.50000000e-01 + -7.50000000e-01 2.50000000e-01 1.00000000e+00 + -1.00000000e+00 2.50000000e-01 1.00000000e+00 + -7.50000000e-01 5.00000000e-01 -1.00000000e+00 + -1.00000000e+00 5.00000000e-01 -1.00000000e+00 + -7.74711747e-01 5.33661095e-01 -7.80463458e-01 + -1.00000000e+00 5.00000000e-01 -7.50000000e-01 + -7.67215534e-01 5.42589437e-01 -5.04549603e-01 + -1.00000000e+00 5.00000000e-01 -5.00000000e-01 + -7.15810149e-01 5.26893539e-01 -2.42883325e-01 + -1.00000000e+00 5.00000000e-01 -2.50000000e-01 + -7.56401821e-01 5.02565626e-01 2.48326812e-02 + -1.00000000e+00 5.00000000e-01 -2.75701684e-12 + -7.83862557e-01 4.80699390e-01 2.79128169e-01 + -1.00000000e+00 5.00000000e-01 2.50000000e-01 + -7.54917756e-01 4.86550393e-01 5.05779683e-01 + -1.00000000e+00 5.00000000e-01 5.00000000e-01 + -7.56116181e-01 4.93561842e-01 7.24201581e-01 + -1.00000000e+00 5.00000000e-01 7.50000000e-01 + -7.50000000e-01 5.00000000e-01 1.00000000e+00 + -1.00000000e+00 5.00000000e-01 1.00000000e+00 + -7.50000000e-01 7.50000000e-01 -1.00000000e+00 + -1.00000000e+00 7.50000000e-01 -1.00000000e+00 + -7.24858605e-01 7.36268224e-01 -7.67047070e-01 + -1.00000000e+00 7.50000000e-01 -7.50000000e-01 + -7.10484097e-01 7.35338133e-01 -4.71307945e-01 + -1.00000000e+00 7.50000000e-01 -5.00000000e-01 + -7.80234406e-01 7.45157469e-01 -2.31786586e-01 + -1.00000000e+00 7.50000000e-01 -2.50000000e-01 + -7.67744832e-01 7.43808943e-01 3.26513941e-02 + -1.00000000e+00 7.50000000e-01 -2.75696133e-12 + -7.68777660e-01 7.88159735e-01 2.12820846e-01 + -1.00000000e+00 7.50000000e-01 2.50000000e-01 + -7.88561978e-01 7.94748850e-01 5.14205890e-01 + -1.00000000e+00 7.50000000e-01 5.00000000e-01 + -7.76417936e-01 7.11298435e-01 7.36857963e-01 + -1.00000000e+00 7.50000000e-01 7.50000000e-01 + -7.50000000e-01 7.50000000e-01 1.00000000e+00 + -1.00000000e+00 7.50000000e-01 1.00000000e+00 + -7.50000000e-01 1.00000000e+00 -1.00000000e+00 + -1.00000000e+00 1.00000000e+00 -1.00000000e+00 + -7.50000000e-01 1.00000000e+00 -7.50000000e-01 + -1.00000000e+00 1.00000000e+00 -7.50000000e-01 + -7.50000000e-01 1.00000000e+00 -5.00000000e-01 + -1.00000000e+00 1.00000000e+00 -5.00000000e-01 + -7.50000000e-01 1.00000000e+00 -2.50000000e-01 + -1.00000000e+00 1.00000000e+00 -2.50000000e-01 + -7.50000000e-01 1.00000000e+00 -2.75700296e-12 + -1.00000000e+00 1.00000000e+00 -2.75701684e-12 + -7.50000000e-01 1.00000000e+00 2.50000000e-01 + -1.00000000e+00 1.00000000e+00 2.50000000e-01 + -7.50000000e-01 1.00000000e+00 5.00000000e-01 + -1.00000000e+00 1.00000000e+00 5.00000000e-01 + -7.50000000e-01 1.00000000e+00 7.50000000e-01 + -1.00000000e+00 1.00000000e+00 7.50000000e-01 + -7.50000000e-01 1.00000000e+00 1.00000000e+00 + -1.00000000e+00 1.00000000e+00 1.00000000e+00 + -5.00000000e-01 -1.00000000e+00 -1.00000000e+00 + -5.00000000e-01 -7.50000000e-01 -1.00000000e+00 + -5.00000000e-01 -1.00000000e+00 -7.50000000e-01 + -5.46002219e-01 -7.34923704e-01 -7.89514908e-01 + -5.00000000e-01 -1.00000000e+00 -5.00000000e-01 + -4.86495368e-01 -7.68333451e-01 -5.14818934e-01 + -5.00000000e-01 -1.00000000e+00 -2.50000000e-01 + -4.65263292e-01 -7.07924696e-01 -2.07408589e-01 + -5.00000000e-01 -1.00000000e+00 -2.75701684e-12 + -4.57241909e-01 -7.31529229e-01 -1.28166815e-02 + -5.00000000e-01 -1.00000000e+00 2.50000000e-01 + -5.31779381e-01 -7.89016915e-01 2.40685954e-01 + -5.00000000e-01 -1.00000000e+00 5.00000000e-01 + -5.11354410e-01 -7.15118173e-01 4.75956008e-01 + -5.00000000e-01 -1.00000000e+00 7.50000000e-01 + -5.39164125e-01 -7.09421522e-01 7.09087030e-01 + -5.00000000e-01 -1.00000000e+00 1.00000000e+00 + -5.00000000e-01 -7.50000000e-01 1.00000000e+00 + -5.00000000e-01 -5.00000000e-01 -1.00000000e+00 + -5.31410973e-01 -5.34584980e-01 -7.75662594e-01 + -4.92038440e-01 -4.80697943e-01 -4.82952762e-01 + -4.92720923e-01 -5.37913375e-01 -2.56144442e-01 + -4.52568703e-01 -4.70229577e-01 -4.08219558e-02 + -5.04907211e-01 -4.80132133e-01 2.32261761e-01 + -5.10681713e-01 -4.96382915e-01 5.49398964e-01 + -5.14348163e-01 -5.46433301e-01 7.10931411e-01 + -5.00000000e-01 -5.00000000e-01 1.00000000e+00 + -5.00000000e-01 -2.50000000e-01 -1.00000000e+00 + -4.89251085e-01 -2.85066386e-01 -7.51165739e-01 + -4.92401478e-01 -2.78810030e-01 -4.90736193e-01 + -4.61936028e-01 -2.53216296e-01 -2.45087690e-01 + -5.39322368e-01 -2.37790512e-01 3.06373953e-02 + -4.63867321e-01 -2.46546552e-01 2.07891547e-01 + -5.04551888e-01 -2.90539802e-01 4.52271278e-01 + -5.22516315e-01 -2.55038700e-01 7.93277928e-01 + -5.00000000e-01 -2.50000000e-01 1.00000000e+00 + -5.00000000e-01 1.37850842e-12 -1.00000000e+00 + -5.23019564e-01 -8.55064653e-03 -7.30403312e-01 + -4.91964101e-01 -3.09701985e-02 -5.41793830e-01 + -4.80242415e-01 3.52058254e-02 -2.75865014e-01 + -5.09738148e-01 1.24009523e-02 -2.18121842e-02 + -5.15491173e-01 3.27525661e-02 2.69453769e-01 + -4.63229670e-01 -2.52670184e-02 5.33381004e-01 + -4.50321966e-01 -2.32789474e-02 7.34386851e-01 + -5.00000000e-01 1.37850842e-12 1.00000000e+00 + -5.00000000e-01 2.50000000e-01 -1.00000000e+00 + -4.97598815e-01 2.27948356e-01 -7.82731376e-01 + -5.14022054e-01 2.53000207e-01 -5.45933746e-01 + -5.15644769e-01 2.77240340e-01 -2.88537691e-01 + -4.50162792e-01 2.33194645e-01 1.39874786e-02 + -4.59651064e-01 2.95932477e-01 2.46654233e-01 + -5.03702464e-01 2.84983335e-01 4.75290052e-01 + -5.37531291e-01 2.40555507e-01 7.59064654e-01 + -5.00000000e-01 2.50000000e-01 1.00000000e+00 + -5.00000000e-01 5.00000000e-01 -1.00000000e+00 + -5.17497977e-01 4.58685276e-01 -7.90345989e-01 + -4.52583488e-01 4.94738260e-01 -4.77464933e-01 + -4.86976976e-01 5.27521070e-01 -2.79420163e-01 + -5.41308892e-01 4.95275488e-01 4.01612685e-02 + -4.63408638e-01 5.42661201e-01 2.83693533e-01 + -4.74773020e-01 4.93030603e-01 5.11008471e-01 + -5.29343590e-01 5.11309694e-01 7.79954473e-01 + -5.00000000e-01 5.00000000e-01 1.00000000e+00 + -5.00000000e-01 7.50000000e-01 -1.00000000e+00 + -4.76352524e-01 7.95286611e-01 -7.97493888e-01 + -5.04476713e-01 7.02918426e-01 -4.72901082e-01 + -4.61656970e-01 7.78170119e-01 -2.92630168e-01 + -5.20342050e-01 7.46331743e-01 1.12376338e-02 + -4.51436470e-01 7.61862672e-01 2.11630560e-01 + -5.05061793e-01 7.68022439e-01 4.95277418e-01 + -4.59800950e-01 7.31837445e-01 7.98859533e-01 + -5.00000000e-01 7.50000000e-01 1.00000000e+00 + -5.00000000e-01 1.00000000e+00 -1.00000000e+00 + -5.00000000e-01 1.00000000e+00 -7.50000000e-01 + -5.00000000e-01 1.00000000e+00 -5.00000000e-01 + -5.00000000e-01 1.00000000e+00 -2.50000000e-01 + -5.00000000e-01 1.00000000e+00 -2.75698908e-12 + -5.00000000e-01 1.00000000e+00 2.50000000e-01 + -5.00000000e-01 1.00000000e+00 5.00000000e-01 + -5.00000000e-01 1.00000000e+00 7.50000000e-01 + -5.00000000e-01 1.00000000e+00 1.00000000e+00 + -2.50000000e-01 -1.00000000e+00 -1.00000000e+00 + -2.50000000e-01 -7.50000000e-01 -1.00000000e+00 + -2.50000000e-01 -1.00000000e+00 -7.50000000e-01 + -2.85894190e-01 -7.60309174e-01 -7.83437280e-01 + -2.50000000e-01 -1.00000000e+00 -5.00000000e-01 + -2.50309633e-01 -7.18382469e-01 -4.72244014e-01 + -2.50000000e-01 -1.00000000e+00 -2.50000000e-01 + -2.90326575e-01 -7.68793840e-01 -2.27944734e-01 + -2.50000000e-01 -1.00000000e+00 -2.75701684e-12 + -2.78870904e-01 -7.40070306e-01 4.43185002e-03 + -2.50000000e-01 -1.00000000e+00 2.50000000e-01 + -2.20691488e-01 -7.33773900e-01 2.30949340e-01 + -2.50000000e-01 -1.00000000e+00 5.00000000e-01 + -2.81613647e-01 -7.74619659e-01 5.46888415e-01 + -2.50000000e-01 -1.00000000e+00 7.50000000e-01 + -2.46249940e-01 -7.22189875e-01 7.94119453e-01 + -2.50000000e-01 -1.00000000e+00 1.00000000e+00 + -2.50000000e-01 -7.50000000e-01 1.00000000e+00 + -2.50000000e-01 -5.00000000e-01 -1.00000000e+00 + -2.03201309e-01 -5.33681800e-01 -7.39664004e-01 + -2.59808949e-01 -4.65269306e-01 -4.87902664e-01 + -2.85814814e-01 -5.17652390e-01 -2.34784004e-01 + -2.26422658e-01 -4.82459209e-01 -3.42825085e-02 + -2.77772215e-01 -4.70104860e-01 2.37878590e-01 + -2.56305109e-01 -4.50071228e-01 4.66111060e-01 + -2.49931708e-01 -5.31274979e-01 7.92822953e-01 + -2.50000000e-01 -5.00000000e-01 1.00000000e+00 + -2.50000000e-01 -2.50000000e-01 -1.00000000e+00 + -2.05913651e-01 -2.64014583e-01 -7.70462901e-01 + -2.51736803e-01 -2.69310009e-01 -4.54836681e-01 + -2.32364781e-01 -2.20763782e-01 -2.80585315e-01 + -2.73946856e-01 -2.47043784e-01 -6.62900921e-03 + -2.77237391e-01 -2.91473005e-01 2.18957045e-01 + -2.07945190e-01 -2.19345166e-01 4.97866668e-01 + -2.35923652e-01 -2.51635255e-01 7.05711076e-01 + -2.50000000e-01 -2.50000000e-01 1.00000000e+00 + -2.50000000e-01 6.89226454e-13 -1.00000000e+00 + -2.44056677e-01 3.74320192e-02 -7.56167953e-01 + -2.04861804e-01 -5.69342435e-03 -5.04195632e-01 + -2.54840849e-01 -3.59149192e-02 -2.47487347e-01 + -2.04530951e-01 -7.50423267e-03 -4.05133288e-02 + -2.87667008e-01 4.14772444e-02 2.32198562e-01 + -2.34053672e-01 -3.46352841e-02 5.30647957e-01 + -2.64676903e-01 -1.77235176e-02 7.54920166e-01 + -2.50000000e-01 6.89226454e-13 1.00000000e+00 + -2.50000000e-01 2.50000000e-01 -1.00000000e+00 + -2.71945304e-01 2.82091399e-01 -7.80775921e-01 + -2.78076078e-01 2.37407324e-01 -5.14190285e-01 + -2.96501691e-01 2.45891691e-01 -2.02443552e-01 + -2.65606200e-01 2.03900289e-01 -2.29257623e-02 + -2.53889132e-01 2.72293842e-01 2.45334627e-01 + -2.76175127e-01 2.82202021e-01 5.38124604e-01 + -2.30657907e-01 2.89330688e-01 7.09787715e-01 + -2.50000000e-01 2.50000000e-01 1.00000000e+00 + -2.50000000e-01 5.00000000e-01 -1.00000000e+00 + -2.42609373e-01 4.69499223e-01 -7.97281877e-01 + -2.38086175e-01 5.30102618e-01 -5.12288025e-01 + -2.80627274e-01 4.81673289e-01 -2.15202901e-01 + -2.62099961e-01 4.75078086e-01 -1.89726592e-02 + -2.91353795e-01 4.54905896e-01 2.15986549e-01 + -2.01879465e-01 4.90219339e-01 5.18765187e-01 + -2.07526394e-01 5.37277849e-01 7.34585197e-01 + -2.50000000e-01 5.00000000e-01 1.00000000e+00 + -2.50000000e-01 7.50000000e-01 -1.00000000e+00 + -2.26389670e-01 7.64966166e-01 -7.65874356e-01 + -2.10498445e-01 7.37545349e-01 -4.94720911e-01 + -2.28287369e-01 7.01626999e-01 -2.82634415e-01 + -2.46361175e-01 7.75471129e-01 -3.50445218e-02 + -2.87240575e-01 7.75290929e-01 2.39731476e-01 + -2.98710408e-01 7.78095954e-01 4.76702843e-01 + -2.67905043e-01 7.46990552e-01 7.66863940e-01 + -2.50000000e-01 7.50000000e-01 1.00000000e+00 + -2.50000000e-01 1.00000000e+00 -1.00000000e+00 + -2.50000000e-01 1.00000000e+00 -7.50000000e-01 + -2.50000000e-01 1.00000000e+00 -5.00000000e-01 + -2.50000000e-01 1.00000000e+00 -2.50000000e-01 + -2.50000000e-01 1.00000000e+00 -2.75698908e-12 + -2.50000000e-01 1.00000000e+00 2.50000000e-01 + -2.50000000e-01 1.00000000e+00 5.00000000e-01 + -2.50000000e-01 1.00000000e+00 7.50000000e-01 + -2.50000000e-01 1.00000000e+00 1.00000000e+00 + -2.75701684e-12 -1.00000000e+00 -1.00000000e+00 + -2.06776263e-12 -7.50000000e-01 -1.00000000e+00 + -2.75700990e-12 -1.00000000e+00 -7.50000000e-01 + 3.34568271e-02 -7.40044296e-01 -7.21769034e-01 + -2.75700296e-12 -1.00000000e+00 -5.00000000e-01 + -4.92429953e-02 -7.18235102e-01 -4.65574401e-01 + -2.75698908e-12 -1.00000000e+00 -2.50000000e-01 + -1.24869294e-02 -7.18455598e-01 -2.63826454e-01 + -2.75701684e-12 -1.00000000e+00 -2.75698908e-12 + 3.24388470e-02 -7.00590122e-01 2.48206776e-02 + -2.75701684e-12 -1.00000000e+00 2.50000000e-01 + 2.09290277e-02 -7.62922334e-01 2.35364285e-01 + -2.75696133e-12 -1.00000000e+00 5.00000000e-01 + 4.15200748e-02 -7.51723905e-01 4.76671856e-01 + -2.75701684e-12 -1.00000000e+00 7.50000000e-01 + -2.18222109e-02 -7.78675809e-01 7.04782653e-01 + -2.75701684e-12 -1.00000000e+00 1.00000000e+00 + -2.06776263e-12 -7.50000000e-01 1.00000000e+00 + -1.37850842e-12 -5.00000000e-01 -1.00000000e+00 + 3.58170738e-02 -4.54195372e-01 -7.38474923e-01 + -3.56631628e-02 -5.47156584e-01 -4.97544434e-01 + 1.71950773e-02 -5.07172514e-01 -2.69783591e-01 + 3.54649860e-03 -4.60405317e-01 -1.77221272e-02 + 3.94364036e-02 -5.12455877e-01 2.94157004e-01 + 2.38082085e-02 -5.43937423e-01 5.04974235e-01 + -1.49885894e-02 -5.05290340e-01 7.66214433e-01 + -1.37850842e-12 -5.00000000e-01 1.00000000e+00 + -6.89254209e-13 -2.50000000e-01 -1.00000000e+00 + -4.52091335e-02 -2.53711683e-01 -7.70536992e-01 + -1.73037321e-02 -2.17877200e-01 -4.66301833e-01 + -1.72967874e-02 -2.94464516e-01 -2.66967974e-01 + 8.72959949e-03 -2.19362728e-01 1.15966008e-02 + -4.92892671e-02 -2.35283393e-01 2.39768243e-01 + -4.16098433e-02 -2.15390091e-01 5.06146744e-01 + -4.73761565e-02 -2.19202055e-01 7.76218435e-01 + -6.89254209e-13 -2.50000000e-01 1.00000000e+00 + 0.00000000e+00 0.00000000e+00 -1.00000000e+00 + 8.69268678e-03 -1.43350950e-02 -7.59155796e-01 + -2.71706977e-02 4.89802370e-02 -4.71320579e-01 + 2.24519919e-02 4.62777025e-03 -2.42540841e-01 + -4.64232702e-02 3.42833959e-02 -3.07561994e-02 + -1.39859901e-02 -3.10148856e-02 2.97335468e-01 + -5.20846485e-03 -1.32092065e-02 5.10395768e-01 + -1.01602093e-02 -5.12227453e-03 7.60054165e-01 + 0.00000000e+00 0.00000000e+00 1.00000000e+00 + 6.89254209e-13 2.50000000e-01 -1.00000000e+00 + -1.97158332e-02 2.03831009e-01 -7.90277152e-01 + 1.06752857e-02 2.05990459e-01 -4.84810566e-01 + -2.17682932e-02 2.12565193e-01 -2.68782990e-01 + 3.36426602e-02 2.28633609e-01 3.65290402e-02 + 2.88763248e-02 2.07402671e-01 2.32349251e-01 + -3.14209320e-03 2.61290908e-01 5.44324077e-01 + 3.20477836e-02 2.31783290e-01 7.09978077e-01 + 6.89254209e-13 2.50000000e-01 1.00000000e+00 + 1.37850842e-12 5.00000000e-01 -1.00000000e+00 + 1.07380306e-02 4.97393558e-01 -7.19105502e-01 + -2.51045076e-02 4.51493733e-01 -5.49082580e-01 + -3.43689688e-02 5.34261873e-01 -2.66226094e-01 + -1.94749700e-02 5.24974229e-01 -3.54711672e-02 + -1.03644166e-02 5.05990462e-01 2.13336115e-01 + 3.10907395e-02 4.65840793e-01 5.31303175e-01 + 4.70954910e-02 4.74317636e-01 7.01900038e-01 + 1.37850842e-12 5.00000000e-01 1.00000000e+00 + 2.06776263e-12 7.50000000e-01 -1.00000000e+00 + -7.87310318e-03 7.51043314e-01 -7.52166514e-01 + 4.51905598e-02 7.10004161e-01 -5.28293327e-01 + -8.60367639e-03 7.39631384e-01 -2.54203607e-01 + 4.12597554e-02 7.40710353e-01 1.73066624e-02 + -2.81401250e-04 7.63126345e-01 2.58299128e-01 + -2.59909989e-02 7.75380807e-01 5.33523405e-01 + -3.10961077e-02 7.81936271e-01 7.18136574e-01 + 2.06776263e-12 7.50000000e-01 1.00000000e+00 + 2.75701684e-12 1.00000000e+00 -1.00000000e+00 + 2.75700990e-12 1.00000000e+00 -7.50000000e-01 + 2.75700296e-12 1.00000000e+00 -5.00000000e-01 + 2.75698908e-12 1.00000000e+00 -2.50000000e-01 + 2.75701684e-12 1.00000000e+00 -2.75698908e-12 + 2.75701684e-12 1.00000000e+00 2.50000000e-01 + 2.75696133e-12 1.00000000e+00 5.00000000e-01 + 2.75701684e-12 1.00000000e+00 7.50000000e-01 + 2.75701684e-12 1.00000000e+00 1.00000000e+00 + 2.50000000e-01 -1.00000000e+00 -1.00000000e+00 + 2.50000000e-01 -7.50000000e-01 -1.00000000e+00 + 2.50000000e-01 -1.00000000e+00 -7.50000000e-01 + 2.74183172e-01 -7.54512483e-01 -7.36455931e-01 + 2.50000000e-01 -1.00000000e+00 -5.00000000e-01 + 2.35247421e-01 -7.53897071e-01 -4.76332378e-01 + 2.50000000e-01 -1.00000000e+00 -2.50000000e-01 + 2.14370438e-01 -7.70105550e-01 -2.59288110e-01 + 2.50000000e-01 -1.00000000e+00 -2.75698908e-12 + 2.59012596e-01 -7.07737524e-01 2.49924104e-02 + 2.50000000e-01 -1.00000000e+00 2.50000000e-01 + 2.59378499e-01 -7.57043395e-01 2.66537809e-01 + 2.50000000e-01 -1.00000000e+00 5.00000000e-01 + 2.34714304e-01 -7.42407840e-01 5.39312856e-01 + 2.50000000e-01 -1.00000000e+00 7.50000000e-01 + 2.60297779e-01 -7.46475431e-01 7.93783675e-01 + 2.50000000e-01 -1.00000000e+00 1.00000000e+00 + 2.50000000e-01 -7.50000000e-01 1.00000000e+00 + 2.50000000e-01 -5.00000000e-01 -1.00000000e+00 + 2.02545425e-01 -4.63827302e-01 -7.34114822e-01 + 2.09430099e-01 -4.75966899e-01 -5.12155323e-01 + 2.14549652e-01 -4.57951880e-01 -2.56869209e-01 + 2.54910707e-01 -5.08937143e-01 1.23055504e-02 + 2.93932342e-01 -4.51174064e-01 2.10699201e-01 + 2.14033214e-01 -4.93680429e-01 5.34085720e-01 + 2.65265732e-01 -4.81626013e-01 7.33092193e-01 + 2.50000000e-01 -5.00000000e-01 1.00000000e+00 + 2.50000000e-01 -2.50000000e-01 -1.00000000e+00 + 2.94245852e-01 -2.54314464e-01 -7.29478634e-01 + 2.47059487e-01 -2.41939308e-01 -5.04911922e-01 + 2.72496891e-01 -2.61057667e-01 -2.90329494e-01 + 2.58566596e-01 -2.76477336e-01 4.63021818e-02 + 2.61530277e-01 -2.97653095e-01 2.52654653e-01 + 2.84033317e-01 -2.48305698e-01 4.99366617e-01 + 2.60143155e-01 -2.26286153e-01 7.24600407e-01 + 2.50000000e-01 -2.50000000e-01 1.00000000e+00 + 2.50000000e-01 -6.89226454e-13 -1.00000000e+00 + 2.70301861e-01 1.34330219e-02 -7.63147083e-01 + 2.93875921e-01 -4.89497564e-02 -4.96836731e-01 + 2.18823886e-01 2.65682814e-02 -2.48362106e-01 + 2.78675933e-01 -5.79754157e-03 -4.49082349e-02 + 2.13369580e-01 -3.08417471e-02 2.03516131e-01 + 2.08855586e-01 -6.77743068e-03 4.64733410e-01 + 2.22922302e-01 1.22113526e-02 7.59399186e-01 + 2.50000000e-01 -6.89226454e-13 1.00000000e+00 + 2.50000000e-01 2.50000000e-01 -1.00000000e+00 + 2.57274318e-01 2.77450050e-01 -7.66786665e-01 + 2.86300441e-01 2.50833174e-01 -5.02504990e-01 + 2.15617675e-01 2.92802719e-01 -2.44082191e-01 + 2.64048877e-01 2.87239022e-01 -3.40770583e-02 + 2.77136735e-01 2.25414932e-01 2.84522460e-01 + 2.62669341e-01 2.12847464e-01 4.57542763e-01 + 2.34832631e-01 2.72811345e-01 7.78460973e-01 + 2.50000000e-01 2.50000000e-01 1.00000000e+00 + 2.50000000e-01 5.00000000e-01 -1.00000000e+00 + 2.41539679e-01 4.56684082e-01 -7.29530213e-01 + 2.43878990e-01 5.03040230e-01 -4.77367769e-01 + 2.66018833e-01 5.07245305e-01 -2.03100747e-01 + 2.20796757e-01 5.34185671e-01 1.18893933e-02 + 2.08014824e-01 4.75253881e-01 2.42838570e-01 + 2.32823225e-01 5.49837512e-01 5.11752754e-01 + 2.75137840e-01 4.79540445e-01 7.72573233e-01 + 2.50000000e-01 5.00000000e-01 1.00000000e+00 + 2.50000000e-01 7.50000000e-01 -1.00000000e+00 + 2.91011822e-01 7.65593863e-01 -7.74685175e-01 + 2.15875281e-01 7.58499352e-01 -4.51410060e-01 + 2.58941015e-01 7.23875424e-01 -2.05734918e-01 + 2.51274404e-01 7.61015400e-01 -4.62783250e-02 + 2.58677116e-01 7.63606655e-01 2.61035671e-01 + 2.92999909e-01 7.60687319e-01 4.93306385e-01 + 2.96840207e-01 7.87402040e-01 7.14028274e-01 + 2.50000000e-01 7.50000000e-01 1.00000000e+00 + 2.50000000e-01 1.00000000e+00 -1.00000000e+00 + 2.50000000e-01 1.00000000e+00 -7.50000000e-01 + 2.50000000e-01 1.00000000e+00 -5.00000000e-01 + 2.50000000e-01 1.00000000e+00 -2.50000000e-01 + 2.50000000e-01 1.00000000e+00 -2.75701684e-12 + 2.50000000e-01 1.00000000e+00 2.50000000e-01 + 2.50000000e-01 1.00000000e+00 5.00000000e-01 + 2.50000000e-01 1.00000000e+00 7.50000000e-01 + 2.50000000e-01 1.00000000e+00 1.00000000e+00 + 5.00000000e-01 -1.00000000e+00 -1.00000000e+00 + 5.00000000e-01 -7.50000000e-01 -1.00000000e+00 + 5.00000000e-01 -1.00000000e+00 -7.50000000e-01 + 4.86301258e-01 -7.44480912e-01 -7.29365734e-01 + 5.00000000e-01 -1.00000000e+00 -5.00000000e-01 + 4.78606390e-01 -7.13108485e-01 -4.89859694e-01 + 5.00000000e-01 -1.00000000e+00 -2.50000000e-01 + 5.01660507e-01 -7.26762025e-01 -2.20790402e-01 + 5.00000000e-01 -1.00000000e+00 -2.75698908e-12 + 4.67101964e-01 -7.96856613e-01 3.81294266e-02 + 5.00000000e-01 -1.00000000e+00 2.50000000e-01 + 4.72309668e-01 -7.14843185e-01 2.06640304e-01 + 5.00000000e-01 -1.00000000e+00 5.00000000e-01 + 4.74600480e-01 -7.17771831e-01 4.64516924e-01 + 5.00000000e-01 -1.00000000e+00 7.50000000e-01 + 5.43831288e-01 -7.19318472e-01 7.23932511e-01 + 5.00000000e-01 -1.00000000e+00 1.00000000e+00 + 5.00000000e-01 -7.50000000e-01 1.00000000e+00 + 5.00000000e-01 -5.00000000e-01 -1.00000000e+00 + 5.19907855e-01 -5.27957252e-01 -7.89977077e-01 + 5.38067847e-01 -4.92649323e-01 -4.62759494e-01 + 5.05263086e-01 -4.52155425e-01 -2.68023239e-01 + 5.07332200e-01 -5.38678957e-01 2.07653492e-02 + 5.43458530e-01 -5.16722709e-01 2.93442762e-01 + 4.76825548e-01 -5.19427336e-01 4.65467020e-01 + 5.43748530e-01 -5.40480932e-01 7.31318027e-01 + 5.00000000e-01 -5.00000000e-01 1.00000000e+00 + 5.00000000e-01 -2.50000000e-01 -1.00000000e+00 + 5.06894014e-01 -2.33125875e-01 -7.07487996e-01 + 5.49113885e-01 -2.26413892e-01 -5.01612866e-01 + 4.63419032e-01 -2.19495205e-01 -2.90495725e-01 + 5.14227512e-01 -2.59753280e-01 3.65331995e-03 + 4.55009562e-01 -2.58945851e-01 2.90117945e-01 + 4.68771102e-01 -2.49311950e-01 5.01486332e-01 + 5.04205114e-01 -2.68885886e-01 7.91509640e-01 + 5.00000000e-01 -2.50000000e-01 1.00000000e+00 + 5.00000000e-01 -1.37845291e-12 -1.00000000e+00 + 4.90670848e-01 -4.77899429e-02 -7.36919908e-01 + 4.73296458e-01 3.86327056e-03 -4.99631995e-01 + 5.04209106e-01 4.64615808e-02 -2.06092710e-01 + 5.17106907e-01 3.18568282e-02 1.00469264e-02 + 4.56201401e-01 2.03086796e-03 2.63641668e-01 + 4.50337235e-01 -6.54374056e-03 4.58095086e-01 + 5.34187475e-01 -3.76989480e-02 7.67099741e-01 + 5.00000000e-01 -1.37845291e-12 1.00000000e+00 + 5.00000000e-01 2.50000000e-01 -1.00000000e+00 + 4.87413204e-01 2.33027530e-01 -7.75119908e-01 + 5.06430376e-01 2.35983848e-01 -4.65790917e-01 + 4.81305441e-01 2.25444901e-01 -2.08755574e-01 + 5.38191249e-01 2.86943843e-01 1.07273369e-02 + 4.94225764e-01 2.60310362e-01 2.91689245e-01 + 4.63054269e-01 2.40751296e-01 5.37189883e-01 + 5.17101531e-01 2.00532239e-01 7.40300412e-01 + 5.00000000e-01 2.50000000e-01 1.00000000e+00 + 5.00000000e-01 5.00000000e-01 -1.00000000e+00 + 5.07518078e-01 5.16813026e-01 -7.60535657e-01 + 5.21042510e-01 5.30479834e-01 -5.42667738e-01 + 5.32080427e-01 4.76181244e-01 -2.96282320e-01 + 5.02830507e-01 5.08134499e-01 -4.81644257e-02 + 5.12620840e-01 5.29442721e-01 2.58479969e-01 + 5.21715336e-01 5.03661604e-01 4.72259099e-01 + 5.20206843e-01 4.98629629e-01 7.14222146e-01 + 5.00000000e-01 5.00000000e-01 1.00000000e+00 + 5.00000000e-01 7.50000000e-01 -1.00000000e+00 + 4.54374717e-01 7.50542802e-01 -7.05232559e-01 + 4.78192451e-01 7.28222057e-01 -4.77859179e-01 + 4.79659748e-01 7.97572358e-01 -2.12393570e-01 + 4.67673723e-01 7.69533544e-01 2.03259741e-02 + 4.78902084e-01 7.59768589e-01 2.62394804e-01 + 4.88484671e-01 7.11810259e-01 4.89955210e-01 + 4.82458091e-01 7.27029915e-01 7.04334654e-01 + 5.00000000e-01 7.50000000e-01 1.00000000e+00 + 5.00000000e-01 1.00000000e+00 -1.00000000e+00 + 5.00000000e-01 1.00000000e+00 -7.50000000e-01 + 5.00000000e-01 1.00000000e+00 -5.00000000e-01 + 5.00000000e-01 1.00000000e+00 -2.50000000e-01 + 5.00000000e-01 1.00000000e+00 -2.75701684e-12 + 5.00000000e-01 1.00000000e+00 2.50000000e-01 + 5.00000000e-01 1.00000000e+00 5.00000000e-01 + 5.00000000e-01 1.00000000e+00 7.50000000e-01 + 5.00000000e-01 1.00000000e+00 1.00000000e+00 + 7.50000000e-01 -1.00000000e+00 -1.00000000e+00 + 7.50000000e-01 -7.50000000e-01 -1.00000000e+00 + 7.50000000e-01 -1.00000000e+00 -7.50000000e-01 + 7.91945497e-01 -7.38510470e-01 -7.74951866e-01 + 7.50000000e-01 -1.00000000e+00 -5.00000000e-01 + 7.26020174e-01 -7.16198076e-01 -5.41117397e-01 + 7.50000000e-01 -1.00000000e+00 -2.50000000e-01 + 7.99856017e-01 -7.39644875e-01 -2.53114642e-01 + 7.50000000e-01 -1.00000000e+00 -2.75700296e-12 + 7.92196727e-01 -7.52967515e-01 2.66992626e-02 + 7.50000000e-01 -1.00000000e+00 2.50000000e-01 + 7.93975525e-01 -7.68828964e-01 2.22876969e-01 + 7.50000000e-01 -1.00000000e+00 5.00000000e-01 + 7.02150219e-01 -7.68266805e-01 4.91552730e-01 + 7.50000000e-01 -1.00000000e+00 7.50000000e-01 + 7.94643488e-01 -7.83172213e-01 7.97553024e-01 + 7.50000000e-01 -1.00000000e+00 1.00000000e+00 + 7.50000000e-01 -7.50000000e-01 1.00000000e+00 + 7.50000000e-01 -5.00000000e-01 -1.00000000e+00 + 7.22851191e-01 -4.57046092e-01 -7.66504887e-01 + 7.76120918e-01 -5.43084273e-01 -4.91001954e-01 + 7.70526106e-01 -4.61342002e-01 -2.75022766e-01 + 7.83531986e-01 -4.66970832e-01 4.72390139e-02 + 7.24206641e-01 -4.99582470e-01 2.53094663e-01 + 7.65656355e-01 -4.86252460e-01 5.33089944e-01 + 7.37123603e-01 -4.75295222e-01 7.55914786e-01 + 7.50000000e-01 -5.00000000e-01 1.00000000e+00 + 7.50000000e-01 -2.50000000e-01 -1.00000000e+00 + 7.36509422e-01 -2.74242552e-01 -7.85833132e-01 + 7.90655368e-01 -2.29852411e-01 -5.35567166e-01 + 7.74238290e-01 -2.27287443e-01 -2.43030064e-01 + 7.38738235e-01 -2.04633501e-01 2.85581325e-02 + 7.83748446e-01 -2.12934705e-01 2.47640336e-01 + 7.98001597e-01 -2.32207961e-01 5.19409501e-01 + 7.15149149e-01 -2.60531871e-01 7.81696644e-01 + 7.50000000e-01 -2.50000000e-01 1.00000000e+00 + 7.50000000e-01 -2.06779038e-12 -1.00000000e+00 + 7.68634705e-01 8.26038532e-03 -7.95751124e-01 + 7.06737201e-01 -4.60191893e-02 -5.17545654e-01 + 7.19407240e-01 4.70491616e-02 -2.37338067e-01 + 7.11502410e-01 -4.37838565e-03 -3.30627842e-02 + 7.18626038e-01 2.27107379e-02 2.29396987e-01 + 7.83260135e-01 -1.15134929e-02 4.77448736e-01 + 7.61629578e-01 -2.95808147e-02 7.36735103e-01 + 7.50000000e-01 -2.06779038e-12 1.00000000e+00 + 7.50000000e-01 2.50000000e-01 -1.00000000e+00 + 7.30789612e-01 2.91607607e-01 -7.79233873e-01 + 7.36607837e-01 2.04682639e-01 -4.56319603e-01 + 7.98682297e-01 2.71359570e-01 -2.22764756e-01 + 7.46464203e-01 2.01143942e-01 5.29345018e-03 + 7.24092364e-01 2.00295197e-01 2.30041701e-01 + 7.46175477e-01 2.86515301e-01 5.31100395e-01 + 7.88105663e-01 2.64178049e-01 7.92670951e-01 + 7.50000000e-01 2.50000000e-01 1.00000000e+00 + 7.50000000e-01 5.00000000e-01 -1.00000000e+00 + 7.10283466e-01 4.68646549e-01 -7.69518312e-01 + 7.51882038e-01 5.42162271e-01 -5.13927865e-01 + 7.59220200e-01 5.29324538e-01 -2.20082408e-01 + 7.65714941e-01 4.71400838e-01 3.43472104e-02 + 7.15132062e-01 4.93473339e-01 2.24456161e-01 + 7.43640800e-01 5.35303502e-01 5.11382296e-01 + 7.76954961e-01 5.07145887e-01 7.52404784e-01 + 7.50000000e-01 5.00000000e-01 1.00000000e+00 + 7.50000000e-01 7.50000000e-01 -1.00000000e+00 + 7.21016174e-01 7.23883155e-01 -7.58041758e-01 + 7.58459105e-01 7.35934345e-01 -4.86287723e-01 + 7.80918794e-01 7.93875298e-01 -2.11099129e-01 + 7.35915091e-01 7.50805127e-01 2.71345260e-02 + 7.58917410e-01 7.00601832e-01 2.75229903e-01 + 7.53589758e-01 7.48692575e-01 4.55123021e-01 + 7.29128194e-01 7.58741450e-01 7.12247141e-01 + 7.50000000e-01 7.50000000e-01 1.00000000e+00 + 7.50000000e-01 1.00000000e+00 -1.00000000e+00 + 7.50000000e-01 1.00000000e+00 -7.50000000e-01 + 7.50000000e-01 1.00000000e+00 -5.00000000e-01 + 7.50000000e-01 1.00000000e+00 -2.50000000e-01 + 7.50000000e-01 1.00000000e+00 -2.75696133e-12 + 7.50000000e-01 1.00000000e+00 2.50000000e-01 + 7.50000000e-01 1.00000000e+00 5.00000000e-01 + 7.50000000e-01 1.00000000e+00 7.50000000e-01 + 7.50000000e-01 1.00000000e+00 1.00000000e+00 + 1.00000000e+00 -1.00000000e+00 -1.00000000e+00 + 1.00000000e+00 -7.50000000e-01 -1.00000000e+00 + 1.00000000e+00 -1.00000000e+00 -7.50000000e-01 + 1.00000000e+00 -7.50000000e-01 -7.50000000e-01 + 1.00000000e+00 -1.00000000e+00 -5.00000000e-01 + 1.00000000e+00 -7.50000000e-01 -5.00000000e-01 + 1.00000000e+00 -1.00000000e+00 -2.50000000e-01 + 1.00000000e+00 -7.50000000e-01 -2.50000000e-01 + 1.00000000e+00 -1.00000000e+00 -2.75701684e-12 + 1.00000000e+00 -7.50000000e-01 -2.75696133e-12 + 1.00000000e+00 -1.00000000e+00 2.50000000e-01 + 1.00000000e+00 -7.50000000e-01 2.50000000e-01 + 1.00000000e+00 -1.00000000e+00 5.00000000e-01 + 1.00000000e+00 -7.50000000e-01 5.00000000e-01 + 1.00000000e+00 -1.00000000e+00 7.50000000e-01 + 1.00000000e+00 -7.50000000e-01 7.50000000e-01 + 1.00000000e+00 -1.00000000e+00 1.00000000e+00 + 1.00000000e+00 -7.50000000e-01 1.00000000e+00 + 1.00000000e+00 -5.00000000e-01 -1.00000000e+00 + 1.00000000e+00 -5.00000000e-01 -7.50000000e-01 + 1.00000000e+00 -5.00000000e-01 -5.00000000e-01 + 1.00000000e+00 -5.00000000e-01 -2.50000000e-01 + 1.00000000e+00 -5.00000000e-01 -2.75701684e-12 + 1.00000000e+00 -5.00000000e-01 2.50000000e-01 + 1.00000000e+00 -5.00000000e-01 5.00000000e-01 + 1.00000000e+00 -5.00000000e-01 7.50000000e-01 + 1.00000000e+00 -5.00000000e-01 1.00000000e+00 + 1.00000000e+00 -2.50000000e-01 -1.00000000e+00 + 1.00000000e+00 -2.50000000e-01 -7.50000000e-01 + 1.00000000e+00 -2.50000000e-01 -5.00000000e-01 + 1.00000000e+00 -2.50000000e-01 -2.50000000e-01 + 1.00000000e+00 -2.50000000e-01 -2.75701684e-12 + 1.00000000e+00 -2.50000000e-01 2.50000000e-01 + 1.00000000e+00 -2.50000000e-01 5.00000000e-01 + 1.00000000e+00 -2.50000000e-01 7.50000000e-01 + 1.00000000e+00 -2.50000000e-01 1.00000000e+00 + 1.00000000e+00 -2.75701684e-12 -1.00000000e+00 + 1.00000000e+00 -2.75700990e-12 -7.50000000e-01 + 1.00000000e+00 -2.75700296e-12 -5.00000000e-01 + 1.00000000e+00 -2.75698908e-12 -2.50000000e-01 + 1.00000000e+00 -2.75701684e-12 -2.75698908e-12 + 1.00000000e+00 -2.75701684e-12 2.50000000e-01 + 1.00000000e+00 -2.75696133e-12 5.00000000e-01 + 1.00000000e+00 -2.75701684e-12 7.50000000e-01 + 1.00000000e+00 -2.75701684e-12 1.00000000e+00 + 1.00000000e+00 2.50000000e-01 -1.00000000e+00 + 1.00000000e+00 2.50000000e-01 -7.50000000e-01 + 1.00000000e+00 2.50000000e-01 -5.00000000e-01 + 1.00000000e+00 2.50000000e-01 -2.50000000e-01 + 1.00000000e+00 2.50000000e-01 -2.75698908e-12 + 1.00000000e+00 2.50000000e-01 2.50000000e-01 + 1.00000000e+00 2.50000000e-01 5.00000000e-01 + 1.00000000e+00 2.50000000e-01 7.50000000e-01 + 1.00000000e+00 2.50000000e-01 1.00000000e+00 + 1.00000000e+00 5.00000000e-01 -1.00000000e+00 + 1.00000000e+00 5.00000000e-01 -7.50000000e-01 + 1.00000000e+00 5.00000000e-01 -5.00000000e-01 + 1.00000000e+00 5.00000000e-01 -2.50000000e-01 + 1.00000000e+00 5.00000000e-01 -2.75698908e-12 + 1.00000000e+00 5.00000000e-01 2.50000000e-01 + 1.00000000e+00 5.00000000e-01 5.00000000e-01 + 1.00000000e+00 5.00000000e-01 7.50000000e-01 + 1.00000000e+00 5.00000000e-01 1.00000000e+00 + 1.00000000e+00 7.50000000e-01 -1.00000000e+00 + 1.00000000e+00 7.50000000e-01 -7.50000000e-01 + 1.00000000e+00 7.50000000e-01 -5.00000000e-01 + 1.00000000e+00 7.50000000e-01 -2.50000000e-01 + 1.00000000e+00 7.50000000e-01 -2.75700296e-12 + 1.00000000e+00 7.50000000e-01 2.50000000e-01 + 1.00000000e+00 7.50000000e-01 5.00000000e-01 + 1.00000000e+00 7.50000000e-01 7.50000000e-01 + 1.00000000e+00 7.50000000e-01 1.00000000e+00 + 1.00000000e+00 1.00000000e+00 -1.00000000e+00 + 1.00000000e+00 1.00000000e+00 -7.50000000e-01 + 1.00000000e+00 1.00000000e+00 -5.00000000e-01 + 1.00000000e+00 1.00000000e+00 -2.50000000e-01 + 1.00000000e+00 1.00000000e+00 -2.75701684e-12 + 1.00000000e+00 1.00000000e+00 2.50000000e-01 + 1.00000000e+00 1.00000000e+00 5.00000000e-01 + 1.00000000e+00 1.00000000e+00 7.50000000e-01 + 1.00000000e+00 1.00000000e+00 1.00000000e+00 + + + 0 1 + 1 2 + 2 3 + 3 0 + 0 4 + 1 5 + 2 6 + 3 7 + 4 5 + 5 6 + 6 7 + 7 4 + 4 8 + 5 9 + 6 10 + 7 11 + 8 9 + 9 10 + 10 11 + 11 8 + 8 12 + 9 13 + 10 14 + 11 15 + 12 13 + 13 14 + 14 15 + 15 12 + 12 16 + 13 17 + 14 18 + 15 19 + 16 17 + 17 18 + 18 19 + 19 16 + 16 20 + 17 21 + 18 22 + 19 23 + 20 21 + 21 22 + 22 23 + 23 20 + 20 24 + 21 25 + 22 26 + 23 27 + 24 25 + 25 26 + 26 27 + 27 24 + 24 28 + 25 29 + 26 30 + 27 31 + 28 29 + 29 30 + 30 31 + 31 28 + 28 32 + 29 33 + 30 34 + 31 35 + 32 33 + 33 34 + 34 35 + 35 32 + 2 36 + 36 37 + 37 3 + 36 38 + 37 39 + 6 38 + 38 39 + 39 7 + 38 40 + 39 41 + 10 40 + 40 41 + 41 11 + 40 42 + 41 43 + 14 42 + 42 43 + 43 15 + 42 44 + 43 45 + 18 44 + 44 45 + 45 19 + 44 46 + 45 47 + 22 46 + 46 47 + 47 23 + 46 48 + 47 49 + 26 48 + 48 49 + 49 27 + 48 50 + 49 51 + 30 50 + 50 51 + 51 31 + 50 52 + 51 53 + 34 52 + 52 53 + 53 35 + 36 54 + 54 55 + 55 37 + 54 56 + 55 57 + 38 56 + 56 57 + 57 39 + 56 58 + 57 59 + 40 58 + 58 59 + 59 41 + 58 60 + 59 61 + 42 60 + 60 61 + 61 43 + 60 62 + 61 63 + 44 62 + 62 63 + 63 45 + 62 64 + 63 65 + 46 64 + 64 65 + 65 47 + 64 66 + 65 67 + 48 66 + 66 67 + 67 49 + 66 68 + 67 69 + 50 68 + 68 69 + 69 51 + 68 70 + 69 71 + 52 70 + 70 71 + 71 53 + 54 72 + 72 73 + 73 55 + 72 74 + 73 75 + 56 74 + 74 75 + 75 57 + 74 76 + 75 77 + 58 76 + 76 77 + 77 59 + 76 78 + 77 79 + 60 78 + 78 79 + 79 61 + 78 80 + 79 81 + 62 80 + 80 81 + 81 63 + 80 82 + 81 83 + 64 82 + 82 83 + 83 65 + 82 84 + 83 85 + 66 84 + 84 85 + 85 67 + 84 86 + 85 87 + 68 86 + 86 87 + 87 69 + 86 88 + 87 89 + 70 88 + 88 89 + 89 71 + 72 90 + 90 91 + 91 73 + 90 92 + 91 93 + 74 92 + 92 93 + 93 75 + 92 94 + 93 95 + 76 94 + 94 95 + 95 77 + 94 96 + 95 97 + 78 96 + 96 97 + 97 79 + 96 98 + 97 99 + 80 98 + 98 99 + 99 81 + 98 100 + 99 101 + 82 100 + 100 101 + 101 83 + 100 102 + 101 103 + 84 102 + 102 103 + 103 85 + 102 104 + 103 105 + 86 104 + 104 105 + 105 87 + 104 106 + 105 107 + 88 106 + 106 107 + 107 89 + 90 108 + 108 109 + 109 91 + 108 110 + 109 111 + 92 110 + 110 111 + 111 93 + 110 112 + 111 113 + 94 112 + 112 113 + 113 95 + 112 114 + 113 115 + 96 114 + 114 115 + 115 97 + 114 116 + 115 117 + 98 116 + 116 117 + 117 99 + 116 118 + 117 119 + 100 118 + 118 119 + 119 101 + 118 120 + 119 121 + 102 120 + 120 121 + 121 103 + 120 122 + 121 123 + 104 122 + 122 123 + 123 105 + 122 124 + 123 125 + 106 124 + 124 125 + 125 107 + 108 126 + 126 127 + 127 109 + 126 128 + 127 129 + 110 128 + 128 129 + 129 111 + 128 130 + 129 131 + 112 130 + 130 131 + 131 113 + 130 132 + 131 133 + 114 132 + 132 133 + 133 115 + 132 134 + 133 135 + 116 134 + 134 135 + 135 117 + 134 136 + 135 137 + 118 136 + 136 137 + 137 119 + 136 138 + 137 139 + 120 138 + 138 139 + 139 121 + 138 140 + 139 141 + 122 140 + 140 141 + 141 123 + 140 142 + 141 143 + 124 142 + 142 143 + 143 125 + 126 144 + 144 145 + 145 127 + 144 146 + 145 147 + 128 146 + 146 147 + 147 129 + 146 148 + 147 149 + 130 148 + 148 149 + 149 131 + 148 150 + 149 151 + 132 150 + 150 151 + 151 133 + 150 152 + 151 153 + 134 152 + 152 153 + 153 135 + 152 154 + 153 155 + 136 154 + 154 155 + 155 137 + 154 156 + 155 157 + 138 156 + 156 157 + 157 139 + 156 158 + 157 159 + 140 158 + 158 159 + 159 141 + 158 160 + 159 161 + 142 160 + 160 161 + 161 143 + 1 162 + 162 163 + 163 2 + 162 164 + 163 165 + 5 164 + 164 165 + 165 6 + 164 166 + 165 167 + 9 166 + 166 167 + 167 10 + 166 168 + 167 169 + 13 168 + 168 169 + 169 14 + 168 170 + 169 171 + 17 170 + 170 171 + 171 18 + 170 172 + 171 173 + 21 172 + 172 173 + 173 22 + 172 174 + 173 175 + 25 174 + 174 175 + 175 26 + 174 176 + 175 177 + 29 176 + 176 177 + 177 30 + 176 178 + 177 179 + 33 178 + 178 179 + 179 34 + 163 180 + 180 36 + 180 181 + 165 181 + 181 38 + 181 182 + 167 182 + 182 40 + 182 183 + 169 183 + 183 42 + 183 184 + 171 184 + 184 44 + 184 185 + 173 185 + 185 46 + 185 186 + 175 186 + 186 48 + 186 187 + 177 187 + 187 50 + 187 188 + 179 188 + 188 52 + 180 189 + 189 54 + 189 190 + 181 190 + 190 56 + 190 191 + 182 191 + 191 58 + 191 192 + 183 192 + 192 60 + 192 193 + 184 193 + 193 62 + 193 194 + 185 194 + 194 64 + 194 195 + 186 195 + 195 66 + 195 196 + 187 196 + 196 68 + 196 197 + 188 197 + 197 70 + 189 198 + 198 72 + 198 199 + 190 199 + 199 74 + 199 200 + 191 200 + 200 76 + 200 201 + 192 201 + 201 78 + 201 202 + 193 202 + 202 80 + 202 203 + 194 203 + 203 82 + 203 204 + 195 204 + 204 84 + 204 205 + 196 205 + 205 86 + 205 206 + 197 206 + 206 88 + 198 207 + 207 90 + 207 208 + 199 208 + 208 92 + 208 209 + 200 209 + 209 94 + 209 210 + 201 210 + 210 96 + 210 211 + 202 211 + 211 98 + 211 212 + 203 212 + 212 100 + 212 213 + 204 213 + 213 102 + 213 214 + 205 214 + 214 104 + 214 215 + 206 215 + 215 106 + 207 216 + 216 108 + 216 217 + 208 217 + 217 110 + 217 218 + 209 218 + 218 112 + 218 219 + 210 219 + 219 114 + 219 220 + 211 220 + 220 116 + 220 221 + 212 221 + 221 118 + 221 222 + 213 222 + 222 120 + 222 223 + 214 223 + 223 122 + 223 224 + 215 224 + 224 124 + 216 225 + 225 126 + 225 226 + 217 226 + 226 128 + 226 227 + 218 227 + 227 130 + 227 228 + 219 228 + 228 132 + 228 229 + 220 229 + 229 134 + 229 230 + 221 230 + 230 136 + 230 231 + 222 231 + 231 138 + 231 232 + 223 232 + 232 140 + 232 233 + 224 233 + 233 142 + 225 234 + 234 144 + 234 235 + 226 235 + 235 146 + 235 236 + 227 236 + 236 148 + 236 237 + 228 237 + 237 150 + 237 238 + 229 238 + 238 152 + 238 239 + 230 239 + 239 154 + 239 240 + 231 240 + 240 156 + 240 241 + 232 241 + 241 158 + 241 242 + 233 242 + 242 160 + 162 243 + 243 244 + 244 163 + 243 245 + 244 246 + 164 245 + 245 246 + 246 165 + 245 247 + 246 248 + 166 247 + 247 248 + 248 167 + 247 249 + 248 250 + 168 249 + 249 250 + 250 169 + 249 251 + 250 252 + 170 251 + 251 252 + 252 171 + 251 253 + 252 254 + 172 253 + 253 254 + 254 173 + 253 255 + 254 256 + 174 255 + 255 256 + 256 175 + 255 257 + 256 258 + 176 257 + 257 258 + 258 177 + 257 259 + 258 260 + 178 259 + 259 260 + 260 179 + 244 261 + 261 180 + 261 262 + 246 262 + 262 181 + 262 263 + 248 263 + 263 182 + 263 264 + 250 264 + 264 183 + 264 265 + 252 265 + 265 184 + 265 266 + 254 266 + 266 185 + 266 267 + 256 267 + 267 186 + 267 268 + 258 268 + 268 187 + 268 269 + 260 269 + 269 188 + 261 270 + 270 189 + 270 271 + 262 271 + 271 190 + 271 272 + 263 272 + 272 191 + 272 273 + 264 273 + 273 192 + 273 274 + 265 274 + 274 193 + 274 275 + 266 275 + 275 194 + 275 276 + 267 276 + 276 195 + 276 277 + 268 277 + 277 196 + 277 278 + 269 278 + 278 197 + 270 279 + 279 198 + 279 280 + 271 280 + 280 199 + 280 281 + 272 281 + 281 200 + 281 282 + 273 282 + 282 201 + 282 283 + 274 283 + 283 202 + 283 284 + 275 284 + 284 203 + 284 285 + 276 285 + 285 204 + 285 286 + 277 286 + 286 205 + 286 287 + 278 287 + 287 206 + 279 288 + 288 207 + 288 289 + 280 289 + 289 208 + 289 290 + 281 290 + 290 209 + 290 291 + 282 291 + 291 210 + 291 292 + 283 292 + 292 211 + 292 293 + 284 293 + 293 212 + 293 294 + 285 294 + 294 213 + 294 295 + 286 295 + 295 214 + 295 296 + 287 296 + 296 215 + 288 297 + 297 216 + 297 298 + 289 298 + 298 217 + 298 299 + 290 299 + 299 218 + 299 300 + 291 300 + 300 219 + 300 301 + 292 301 + 301 220 + 301 302 + 293 302 + 302 221 + 302 303 + 294 303 + 303 222 + 303 304 + 295 304 + 304 223 + 304 305 + 296 305 + 305 224 + 297 306 + 306 225 + 306 307 + 298 307 + 307 226 + 307 308 + 299 308 + 308 227 + 308 309 + 300 309 + 309 228 + 309 310 + 301 310 + 310 229 + 310 311 + 302 311 + 311 230 + 311 312 + 303 312 + 312 231 + 312 313 + 304 313 + 313 232 + 313 314 + 305 314 + 314 233 + 306 315 + 315 234 + 315 316 + 307 316 + 316 235 + 316 317 + 308 317 + 317 236 + 317 318 + 309 318 + 318 237 + 318 319 + 310 319 + 319 238 + 319 320 + 311 320 + 320 239 + 320 321 + 312 321 + 321 240 + 321 322 + 313 322 + 322 241 + 322 323 + 314 323 + 323 242 + 243 324 + 324 325 + 325 244 + 324 326 + 325 327 + 245 326 + 326 327 + 327 246 + 326 328 + 327 329 + 247 328 + 328 329 + 329 248 + 328 330 + 329 331 + 249 330 + 330 331 + 331 250 + 330 332 + 331 333 + 251 332 + 332 333 + 333 252 + 332 334 + 333 335 + 253 334 + 334 335 + 335 254 + 334 336 + 335 337 + 255 336 + 336 337 + 337 256 + 336 338 + 337 339 + 257 338 + 338 339 + 339 258 + 338 340 + 339 341 + 259 340 + 340 341 + 341 260 + 325 342 + 342 261 + 342 343 + 327 343 + 343 262 + 343 344 + 329 344 + 344 263 + 344 345 + 331 345 + 345 264 + 345 346 + 333 346 + 346 265 + 346 347 + 335 347 + 347 266 + 347 348 + 337 348 + 348 267 + 348 349 + 339 349 + 349 268 + 349 350 + 341 350 + 350 269 + 342 351 + 351 270 + 351 352 + 343 352 + 352 271 + 352 353 + 344 353 + 353 272 + 353 354 + 345 354 + 354 273 + 354 355 + 346 355 + 355 274 + 355 356 + 347 356 + 356 275 + 356 357 + 348 357 + 357 276 + 357 358 + 349 358 + 358 277 + 358 359 + 350 359 + 359 278 + 351 360 + 360 279 + 360 361 + 352 361 + 361 280 + 361 362 + 353 362 + 362 281 + 362 363 + 354 363 + 363 282 + 363 364 + 355 364 + 364 283 + 364 365 + 356 365 + 365 284 + 365 366 + 357 366 + 366 285 + 366 367 + 358 367 + 367 286 + 367 368 + 359 368 + 368 287 + 360 369 + 369 288 + 369 370 + 361 370 + 370 289 + 370 371 + 362 371 + 371 290 + 371 372 + 363 372 + 372 291 + 372 373 + 364 373 + 373 292 + 373 374 + 365 374 + 374 293 + 374 375 + 366 375 + 375 294 + 375 376 + 367 376 + 376 295 + 376 377 + 368 377 + 377 296 + 369 378 + 378 297 + 378 379 + 370 379 + 379 298 + 379 380 + 371 380 + 380 299 + 380 381 + 372 381 + 381 300 + 381 382 + 373 382 + 382 301 + 382 383 + 374 383 + 383 302 + 383 384 + 375 384 + 384 303 + 384 385 + 376 385 + 385 304 + 385 386 + 377 386 + 386 305 + 378 387 + 387 306 + 387 388 + 379 388 + 388 307 + 388 389 + 380 389 + 389 308 + 389 390 + 381 390 + 390 309 + 390 391 + 382 391 + 391 310 + 391 392 + 383 392 + 392 311 + 392 393 + 384 393 + 393 312 + 393 394 + 385 394 + 394 313 + 394 395 + 386 395 + 395 314 + 387 396 + 396 315 + 396 397 + 388 397 + 397 316 + 397 398 + 389 398 + 398 317 + 398 399 + 390 399 + 399 318 + 399 400 + 391 400 + 400 319 + 400 401 + 392 401 + 401 320 + 401 402 + 393 402 + 402 321 + 402 403 + 394 403 + 403 322 + 403 404 + 395 404 + 404 323 + 324 405 + 405 406 + 406 325 + 405 407 + 406 408 + 326 407 + 407 408 + 408 327 + 407 409 + 408 410 + 328 409 + 409 410 + 410 329 + 409 411 + 410 412 + 330 411 + 411 412 + 412 331 + 411 413 + 412 414 + 332 413 + 413 414 + 414 333 + 413 415 + 414 416 + 334 415 + 415 416 + 416 335 + 415 417 + 416 418 + 336 417 + 417 418 + 418 337 + 417 419 + 418 420 + 338 419 + 419 420 + 420 339 + 419 421 + 420 422 + 340 421 + 421 422 + 422 341 + 406 423 + 423 342 + 423 424 + 408 424 + 424 343 + 424 425 + 410 425 + 425 344 + 425 426 + 412 426 + 426 345 + 426 427 + 414 427 + 427 346 + 427 428 + 416 428 + 428 347 + 428 429 + 418 429 + 429 348 + 429 430 + 420 430 + 430 349 + 430 431 + 422 431 + 431 350 + 423 432 + 432 351 + 432 433 + 424 433 + 433 352 + 433 434 + 425 434 + 434 353 + 434 435 + 426 435 + 435 354 + 435 436 + 427 436 + 436 355 + 436 437 + 428 437 + 437 356 + 437 438 + 429 438 + 438 357 + 438 439 + 430 439 + 439 358 + 439 440 + 431 440 + 440 359 + 432 441 + 441 360 + 441 442 + 433 442 + 442 361 + 442 443 + 434 443 + 443 362 + 443 444 + 435 444 + 444 363 + 444 445 + 436 445 + 445 364 + 445 446 + 437 446 + 446 365 + 446 447 + 438 447 + 447 366 + 447 448 + 439 448 + 448 367 + 448 449 + 440 449 + 449 368 + 441 450 + 450 369 + 450 451 + 442 451 + 451 370 + 451 452 + 443 452 + 452 371 + 452 453 + 444 453 + 453 372 + 453 454 + 445 454 + 454 373 + 454 455 + 446 455 + 455 374 + 455 456 + 447 456 + 456 375 + 456 457 + 448 457 + 457 376 + 457 458 + 449 458 + 458 377 + 450 459 + 459 378 + 459 460 + 451 460 + 460 379 + 460 461 + 452 461 + 461 380 + 461 462 + 453 462 + 462 381 + 462 463 + 454 463 + 463 382 + 463 464 + 455 464 + 464 383 + 464 465 + 456 465 + 465 384 + 465 466 + 457 466 + 466 385 + 466 467 + 458 467 + 467 386 + 459 468 + 468 387 + 468 469 + 460 469 + 469 388 + 469 470 + 461 470 + 470 389 + 470 471 + 462 471 + 471 390 + 471 472 + 463 472 + 472 391 + 472 473 + 464 473 + 473 392 + 473 474 + 465 474 + 474 393 + 474 475 + 466 475 + 475 394 + 475 476 + 467 476 + 476 395 + 468 477 + 477 396 + 477 478 + 469 478 + 478 397 + 478 479 + 470 479 + 479 398 + 479 480 + 471 480 + 480 399 + 480 481 + 472 481 + 481 400 + 481 482 + 473 482 + 482 401 + 482 483 + 474 483 + 483 402 + 483 484 + 475 484 + 484 403 + 484 485 + 476 485 + 485 404 + 405 486 + 486 487 + 487 406 + 486 488 + 487 489 + 407 488 + 488 489 + 489 408 + 488 490 + 489 491 + 409 490 + 490 491 + 491 410 + 490 492 + 491 493 + 411 492 + 492 493 + 493 412 + 492 494 + 493 495 + 413 494 + 494 495 + 495 414 + 494 496 + 495 497 + 415 496 + 496 497 + 497 416 + 496 498 + 497 499 + 417 498 + 498 499 + 499 418 + 498 500 + 499 501 + 419 500 + 500 501 + 501 420 + 500 502 + 501 503 + 421 502 + 502 503 + 503 422 + 487 504 + 504 423 + 504 505 + 489 505 + 505 424 + 505 506 + 491 506 + 506 425 + 506 507 + 493 507 + 507 426 + 507 508 + 495 508 + 508 427 + 508 509 + 497 509 + 509 428 + 509 510 + 499 510 + 510 429 + 510 511 + 501 511 + 511 430 + 511 512 + 503 512 + 512 431 + 504 513 + 513 432 + 513 514 + 505 514 + 514 433 + 514 515 + 506 515 + 515 434 + 515 516 + 507 516 + 516 435 + 516 517 + 508 517 + 517 436 + 517 518 + 509 518 + 518 437 + 518 519 + 510 519 + 519 438 + 519 520 + 511 520 + 520 439 + 520 521 + 512 521 + 521 440 + 513 522 + 522 441 + 522 523 + 514 523 + 523 442 + 523 524 + 515 524 + 524 443 + 524 525 + 516 525 + 525 444 + 525 526 + 517 526 + 526 445 + 526 527 + 518 527 + 527 446 + 527 528 + 519 528 + 528 447 + 528 529 + 520 529 + 529 448 + 529 530 + 521 530 + 530 449 + 522 531 + 531 450 + 531 532 + 523 532 + 532 451 + 532 533 + 524 533 + 533 452 + 533 534 + 525 534 + 534 453 + 534 535 + 526 535 + 535 454 + 535 536 + 527 536 + 536 455 + 536 537 + 528 537 + 537 456 + 537 538 + 529 538 + 538 457 + 538 539 + 530 539 + 539 458 + 531 540 + 540 459 + 540 541 + 532 541 + 541 460 + 541 542 + 533 542 + 542 461 + 542 543 + 534 543 + 543 462 + 543 544 + 535 544 + 544 463 + 544 545 + 536 545 + 545 464 + 545 546 + 537 546 + 546 465 + 546 547 + 538 547 + 547 466 + 547 548 + 539 548 + 548 467 + 540 549 + 549 468 + 549 550 + 541 550 + 550 469 + 550 551 + 542 551 + 551 470 + 551 552 + 543 552 + 552 471 + 552 553 + 544 553 + 553 472 + 553 554 + 545 554 + 554 473 + 554 555 + 546 555 + 555 474 + 555 556 + 547 556 + 556 475 + 556 557 + 548 557 + 557 476 + 549 558 + 558 477 + 558 559 + 550 559 + 559 478 + 559 560 + 551 560 + 560 479 + 560 561 + 552 561 + 561 480 + 561 562 + 553 562 + 562 481 + 562 563 + 554 563 + 563 482 + 563 564 + 555 564 + 564 483 + 564 565 + 556 565 + 565 484 + 565 566 + 557 566 + 566 485 + 486 567 + 567 568 + 568 487 + 567 569 + 568 570 + 488 569 + 569 570 + 570 489 + 569 571 + 570 572 + 490 571 + 571 572 + 572 491 + 571 573 + 572 574 + 492 573 + 573 574 + 574 493 + 573 575 + 574 576 + 494 575 + 575 576 + 576 495 + 575 577 + 576 578 + 496 577 + 577 578 + 578 497 + 577 579 + 578 580 + 498 579 + 579 580 + 580 499 + 579 581 + 580 582 + 500 581 + 581 582 + 582 501 + 581 583 + 582 584 + 502 583 + 583 584 + 584 503 + 568 585 + 585 504 + 585 586 + 570 586 + 586 505 + 586 587 + 572 587 + 587 506 + 587 588 + 574 588 + 588 507 + 588 589 + 576 589 + 589 508 + 589 590 + 578 590 + 590 509 + 590 591 + 580 591 + 591 510 + 591 592 + 582 592 + 592 511 + 592 593 + 584 593 + 593 512 + 585 594 + 594 513 + 594 595 + 586 595 + 595 514 + 595 596 + 587 596 + 596 515 + 596 597 + 588 597 + 597 516 + 597 598 + 589 598 + 598 517 + 598 599 + 590 599 + 599 518 + 599 600 + 591 600 + 600 519 + 600 601 + 592 601 + 601 520 + 601 602 + 593 602 + 602 521 + 594 603 + 603 522 + 603 604 + 595 604 + 604 523 + 604 605 + 596 605 + 605 524 + 605 606 + 597 606 + 606 525 + 606 607 + 598 607 + 607 526 + 607 608 + 599 608 + 608 527 + 608 609 + 600 609 + 609 528 + 609 610 + 601 610 + 610 529 + 610 611 + 602 611 + 611 530 + 603 612 + 612 531 + 612 613 + 604 613 + 613 532 + 613 614 + 605 614 + 614 533 + 614 615 + 606 615 + 615 534 + 615 616 + 607 616 + 616 535 + 616 617 + 608 617 + 617 536 + 617 618 + 609 618 + 618 537 + 618 619 + 610 619 + 619 538 + 619 620 + 611 620 + 620 539 + 612 621 + 621 540 + 621 622 + 613 622 + 622 541 + 622 623 + 614 623 + 623 542 + 623 624 + 615 624 + 624 543 + 624 625 + 616 625 + 625 544 + 625 626 + 617 626 + 626 545 + 626 627 + 618 627 + 627 546 + 627 628 + 619 628 + 628 547 + 628 629 + 620 629 + 629 548 + 621 630 + 630 549 + 630 631 + 622 631 + 631 550 + 631 632 + 623 632 + 632 551 + 632 633 + 624 633 + 633 552 + 633 634 + 625 634 + 634 553 + 634 635 + 626 635 + 635 554 + 635 636 + 627 636 + 636 555 + 636 637 + 628 637 + 637 556 + 637 638 + 629 638 + 638 557 + 630 639 + 639 558 + 639 640 + 631 640 + 640 559 + 640 641 + 632 641 + 641 560 + 641 642 + 633 642 + 642 561 + 642 643 + 634 643 + 643 562 + 643 644 + 635 644 + 644 563 + 644 645 + 636 645 + 645 564 + 645 646 + 637 646 + 646 565 + 646 647 + 638 647 + 647 566 + 567 648 + 648 649 + 649 568 + 648 650 + 649 651 + 569 650 + 650 651 + 651 570 + 650 652 + 651 653 + 571 652 + 652 653 + 653 572 + 652 654 + 653 655 + 573 654 + 654 655 + 655 574 + 654 656 + 655 657 + 575 656 + 656 657 + 657 576 + 656 658 + 657 659 + 577 658 + 658 659 + 659 578 + 658 660 + 659 661 + 579 660 + 660 661 + 661 580 + 660 662 + 661 663 + 581 662 + 662 663 + 663 582 + 662 664 + 663 665 + 583 664 + 664 665 + 665 584 + 649 666 + 666 585 + 666 667 + 651 667 + 667 586 + 667 668 + 653 668 + 668 587 + 668 669 + 655 669 + 669 588 + 669 670 + 657 670 + 670 589 + 670 671 + 659 671 + 671 590 + 671 672 + 661 672 + 672 591 + 672 673 + 663 673 + 673 592 + 673 674 + 665 674 + 674 593 + 666 675 + 675 594 + 675 676 + 667 676 + 676 595 + 676 677 + 668 677 + 677 596 + 677 678 + 669 678 + 678 597 + 678 679 + 670 679 + 679 598 + 679 680 + 671 680 + 680 599 + 680 681 + 672 681 + 681 600 + 681 682 + 673 682 + 682 601 + 682 683 + 674 683 + 683 602 + 675 684 + 684 603 + 684 685 + 676 685 + 685 604 + 685 686 + 677 686 + 686 605 + 686 687 + 678 687 + 687 606 + 687 688 + 679 688 + 688 607 + 688 689 + 680 689 + 689 608 + 689 690 + 681 690 + 690 609 + 690 691 + 682 691 + 691 610 + 691 692 + 683 692 + 692 611 + 684 693 + 693 612 + 693 694 + 685 694 + 694 613 + 694 695 + 686 695 + 695 614 + 695 696 + 687 696 + 696 615 + 696 697 + 688 697 + 697 616 + 697 698 + 689 698 + 698 617 + 698 699 + 690 699 + 699 618 + 699 700 + 691 700 + 700 619 + 700 701 + 692 701 + 701 620 + 693 702 + 702 621 + 702 703 + 694 703 + 703 622 + 703 704 + 695 704 + 704 623 + 704 705 + 696 705 + 705 624 + 705 706 + 697 706 + 706 625 + 706 707 + 698 707 + 707 626 + 707 708 + 699 708 + 708 627 + 708 709 + 700 709 + 709 628 + 709 710 + 701 710 + 710 629 + 702 711 + 711 630 + 711 712 + 703 712 + 712 631 + 712 713 + 704 713 + 713 632 + 713 714 + 705 714 + 714 633 + 714 715 + 706 715 + 715 634 + 715 716 + 707 716 + 716 635 + 716 717 + 708 717 + 717 636 + 717 718 + 709 718 + 718 637 + 718 719 + 710 719 + 719 638 + 711 720 + 720 639 + 720 721 + 712 721 + 721 640 + 721 722 + 713 722 + 722 641 + 722 723 + 714 723 + 723 642 + 723 724 + 715 724 + 724 643 + 724 725 + 716 725 + 725 644 + 725 726 + 717 726 + 726 645 + 726 727 + 718 727 + 727 646 + 727 728 + 719 728 + 728 647 + + + 0 1 2 3 + 0 5 8 4 + 1 6 9 5 + 2 6 10 7 + 3 7 11 4 + 8 9 10 11 + 8 13 16 12 + 9 14 17 13 + 10 14 18 15 + 11 15 19 12 + 16 17 18 19 + 16 21 24 20 + 17 22 25 21 + 18 22 26 23 + 19 23 27 20 + 24 25 26 27 + 24 29 32 28 + 25 30 33 29 + 26 30 34 31 + 27 31 35 28 + 32 33 34 35 + 32 37 40 36 + 33 38 41 37 + 34 38 42 39 + 35 39 43 36 + 40 41 42 43 + 40 45 48 44 + 41 46 49 45 + 42 46 50 47 + 43 47 51 44 + 48 49 50 51 + 48 53 56 52 + 49 54 57 53 + 50 54 58 55 + 51 55 59 52 + 56 57 58 59 + 56 61 64 60 + 57 62 65 61 + 58 62 66 63 + 59 63 67 60 + 64 65 66 67 + 2 68 69 70 + 68 71 73 6 + 69 71 74 72 + 70 72 75 7 + 10 73 74 75 + 73 76 78 14 + 74 76 79 77 + 75 77 80 15 + 18 78 79 80 + 78 81 83 22 + 79 81 84 82 + 80 82 85 23 + 26 83 84 85 + 83 86 88 30 + 84 86 89 87 + 85 87 90 31 + 34 88 89 90 + 88 91 93 38 + 89 91 94 92 + 90 92 95 39 + 42 93 94 95 + 93 96 98 46 + 94 96 99 97 + 95 97 100 47 + 50 98 99 100 + 98 101 103 54 + 99 101 104 102 + 100 102 105 55 + 58 103 104 105 + 103 106 108 62 + 104 106 109 107 + 105 107 110 63 + 66 108 109 110 + 69 111 112 113 + 111 114 116 71 + 112 114 117 115 + 113 115 118 72 + 74 116 117 118 + 116 119 121 76 + 117 119 122 120 + 118 120 123 77 + 79 121 122 123 + 121 124 126 81 + 122 124 127 125 + 123 125 128 82 + 84 126 127 128 + 126 129 131 86 + 127 129 132 130 + 128 130 133 87 + 89 131 132 133 + 131 134 136 91 + 132 134 137 135 + 133 135 138 92 + 94 136 137 138 + 136 139 141 96 + 137 139 142 140 + 138 140 143 97 + 99 141 142 143 + 141 144 146 101 + 142 144 147 145 + 143 145 148 102 + 104 146 147 148 + 146 149 151 106 + 147 149 152 150 + 148 150 153 107 + 109 151 152 153 + 112 154 155 156 + 154 157 159 114 + 155 157 160 158 + 156 158 161 115 + 117 159 160 161 + 159 162 164 119 + 160 162 165 163 + 161 163 166 120 + 122 164 165 166 + 164 167 169 124 + 165 167 170 168 + 166 168 171 125 + 127 169 170 171 + 169 172 174 129 + 170 172 175 173 + 171 173 176 130 + 132 174 175 176 + 174 177 179 134 + 175 177 180 178 + 176 178 181 135 + 137 179 180 181 + 179 182 184 139 + 180 182 185 183 + 181 183 186 140 + 142 184 185 186 + 184 187 189 144 + 185 187 190 188 + 186 188 191 145 + 147 189 190 191 + 189 192 194 149 + 190 192 195 193 + 191 193 196 150 + 152 194 195 196 + 155 197 198 199 + 197 200 202 157 + 198 200 203 201 + 199 201 204 158 + 160 202 203 204 + 202 205 207 162 + 203 205 208 206 + 204 206 209 163 + 165 207 208 209 + 207 210 212 167 + 208 210 213 211 + 209 211 214 168 + 170 212 213 214 + 212 215 217 172 + 213 215 218 216 + 214 216 219 173 + 175 217 218 219 + 217 220 222 177 + 218 220 223 221 + 219 221 224 178 + 180 222 223 224 + 222 225 227 182 + 223 225 228 226 + 224 226 229 183 + 185 227 228 229 + 227 230 232 187 + 228 230 233 231 + 229 231 234 188 + 190 232 233 234 + 232 235 237 192 + 233 235 238 236 + 234 236 239 193 + 195 237 238 239 + 198 240 241 242 + 240 243 245 200 + 241 243 246 244 + 242 244 247 201 + 203 245 246 247 + 245 248 250 205 + 246 248 251 249 + 247 249 252 206 + 208 250 251 252 + 250 253 255 210 + 251 253 256 254 + 252 254 257 211 + 213 255 256 257 + 255 258 260 215 + 256 258 261 259 + 257 259 262 216 + 218 260 261 262 + 260 263 265 220 + 261 263 266 264 + 262 264 267 221 + 223 265 266 267 + 265 268 270 225 + 266 268 271 269 + 267 269 272 226 + 228 270 271 272 + 270 273 275 230 + 271 273 276 274 + 272 274 277 231 + 233 275 276 277 + 275 278 280 235 + 276 278 281 279 + 277 279 282 236 + 238 280 281 282 + 241 283 284 285 + 283 286 288 243 + 284 286 289 287 + 285 287 290 244 + 246 288 289 290 + 288 291 293 248 + 289 291 294 292 + 290 292 295 249 + 251 293 294 295 + 293 296 298 253 + 294 296 299 297 + 295 297 300 254 + 256 298 299 300 + 298 301 303 258 + 299 301 304 302 + 300 302 305 259 + 261 303 304 305 + 303 306 308 263 + 304 306 309 307 + 305 307 310 264 + 266 308 309 310 + 308 311 313 268 + 309 311 314 312 + 310 312 315 269 + 271 313 314 315 + 313 316 318 273 + 314 316 319 317 + 315 317 320 274 + 276 318 319 320 + 318 321 323 278 + 319 321 324 322 + 320 322 325 279 + 281 323 324 325 + 284 326 327 328 + 326 329 331 286 + 327 329 332 330 + 328 330 333 287 + 289 331 332 333 + 331 334 336 291 + 332 334 337 335 + 333 335 338 292 + 294 336 337 338 + 336 339 341 296 + 337 339 342 340 + 338 340 343 297 + 299 341 342 343 + 341 344 346 301 + 342 344 347 345 + 343 345 348 302 + 304 346 347 348 + 346 349 351 306 + 347 349 352 350 + 348 350 353 307 + 309 351 352 353 + 351 354 356 311 + 352 354 357 355 + 353 355 358 312 + 314 356 357 358 + 356 359 361 316 + 357 359 362 360 + 358 360 363 317 + 319 361 362 363 + 361 364 366 321 + 362 364 367 365 + 363 365 368 322 + 324 366 367 368 + 369 370 371 1 + 369 372 374 5 + 370 373 375 372 + 371 373 376 6 + 374 375 376 9 + 374 377 379 13 + 375 378 380 377 + 376 378 381 14 + 379 380 381 17 + 379 382 384 21 + 380 383 385 382 + 381 383 386 22 + 384 385 386 25 + 384 387 389 29 + 385 388 390 387 + 386 388 391 30 + 389 390 391 33 + 389 392 394 37 + 390 393 395 392 + 391 393 396 38 + 394 395 396 41 + 394 397 399 45 + 395 398 400 397 + 396 398 401 46 + 399 400 401 49 + 399 402 404 53 + 400 403 405 402 + 401 403 406 54 + 404 405 406 57 + 404 407 409 61 + 405 408 410 407 + 406 408 411 62 + 409 410 411 65 + 371 412 413 68 + 412 414 415 373 + 413 414 416 71 + 376 415 416 73 + 415 417 418 378 + 416 417 419 76 + 381 418 419 78 + 418 420 421 383 + 419 420 422 81 + 386 421 422 83 + 421 423 424 388 + 422 423 425 86 + 391 424 425 88 + 424 426 427 393 + 425 426 428 91 + 396 427 428 93 + 427 429 430 398 + 428 429 431 96 + 401 430 431 98 + 430 432 433 403 + 431 432 434 101 + 406 433 434 103 + 433 435 436 408 + 434 435 437 106 + 411 436 437 108 + 413 438 439 111 + 438 440 441 414 + 439 440 442 114 + 416 441 442 116 + 441 443 444 417 + 442 443 445 119 + 419 444 445 121 + 444 446 447 420 + 445 446 448 124 + 422 447 448 126 + 447 449 450 423 + 448 449 451 129 + 425 450 451 131 + 450 452 453 426 + 451 452 454 134 + 428 453 454 136 + 453 455 456 429 + 454 455 457 139 + 431 456 457 141 + 456 458 459 432 + 457 458 460 144 + 434 459 460 146 + 459 461 462 435 + 460 461 463 149 + 437 462 463 151 + 439 464 465 154 + 464 466 467 440 + 465 466 468 157 + 442 467 468 159 + 467 469 470 443 + 468 469 471 162 + 445 470 471 164 + 470 472 473 446 + 471 472 474 167 + 448 473 474 169 + 473 475 476 449 + 474 475 477 172 + 451 476 477 174 + 476 478 479 452 + 477 478 480 177 + 454 479 480 179 + 479 481 482 455 + 480 481 483 182 + 457 482 483 184 + 482 484 485 458 + 483 484 486 187 + 460 485 486 189 + 485 487 488 461 + 486 487 489 192 + 463 488 489 194 + 465 490 491 197 + 490 492 493 466 + 491 492 494 200 + 468 493 494 202 + 493 495 496 469 + 494 495 497 205 + 471 496 497 207 + 496 498 499 472 + 497 498 500 210 + 474 499 500 212 + 499 501 502 475 + 500 501 503 215 + 477 502 503 217 + 502 504 505 478 + 503 504 506 220 + 480 505 506 222 + 505 507 508 481 + 506 507 509 225 + 483 508 509 227 + 508 510 511 484 + 509 510 512 230 + 486 511 512 232 + 511 513 514 487 + 512 513 515 235 + 489 514 515 237 + 491 516 517 240 + 516 518 519 492 + 517 518 520 243 + 494 519 520 245 + 519 521 522 495 + 520 521 523 248 + 497 522 523 250 + 522 524 525 498 + 523 524 526 253 + 500 525 526 255 + 525 527 528 501 + 526 527 529 258 + 503 528 529 260 + 528 530 531 504 + 529 530 532 263 + 506 531 532 265 + 531 533 534 507 + 532 533 535 268 + 509 534 535 270 + 534 536 537 510 + 535 536 538 273 + 512 537 538 275 + 537 539 540 513 + 538 539 541 278 + 515 540 541 280 + 517 542 543 283 + 542 544 545 518 + 543 544 546 286 + 520 545 546 288 + 545 547 548 521 + 546 547 549 291 + 523 548 549 293 + 548 550 551 524 + 549 550 552 296 + 526 551 552 298 + 551 553 554 527 + 552 553 555 301 + 529 554 555 303 + 554 556 557 530 + 555 556 558 306 + 532 557 558 308 + 557 559 560 533 + 558 559 561 311 + 535 560 561 313 + 560 562 563 536 + 561 562 564 316 + 538 563 564 318 + 563 565 566 539 + 564 565 567 321 + 541 566 567 323 + 543 568 569 326 + 568 570 571 544 + 569 570 572 329 + 546 571 572 331 + 571 573 574 547 + 572 573 575 334 + 549 574 575 336 + 574 576 577 550 + 575 576 578 339 + 552 577 578 341 + 577 579 580 553 + 578 579 581 344 + 555 580 581 346 + 580 582 583 556 + 581 582 584 349 + 558 583 584 351 + 583 585 586 559 + 584 585 587 354 + 561 586 587 356 + 586 588 589 562 + 587 588 590 359 + 564 589 590 361 + 589 591 592 565 + 590 591 593 364 + 567 592 593 366 + 594 595 596 370 + 594 597 599 372 + 595 598 600 597 + 596 598 601 373 + 599 600 601 375 + 599 602 604 377 + 600 603 605 602 + 601 603 606 378 + 604 605 606 380 + 604 607 609 382 + 605 608 610 607 + 606 608 611 383 + 609 610 611 385 + 609 612 614 387 + 610 613 615 612 + 611 613 616 388 + 614 615 616 390 + 614 617 619 392 + 615 618 620 617 + 616 618 621 393 + 619 620 621 395 + 619 622 624 397 + 620 623 625 622 + 621 623 626 398 + 624 625 626 400 + 624 627 629 402 + 625 628 630 627 + 626 628 631 403 + 629 630 631 405 + 629 632 634 407 + 630 633 635 632 + 631 633 636 408 + 634 635 636 410 + 596 637 638 412 + 637 639 640 598 + 638 639 641 414 + 601 640 641 415 + 640 642 643 603 + 641 642 644 417 + 606 643 644 418 + 643 645 646 608 + 644 645 647 420 + 611 646 647 421 + 646 648 649 613 + 647 648 650 423 + 616 649 650 424 + 649 651 652 618 + 650 651 653 426 + 621 652 653 427 + 652 654 655 623 + 653 654 656 429 + 626 655 656 430 + 655 657 658 628 + 656 657 659 432 + 631 658 659 433 + 658 660 661 633 + 659 660 662 435 + 636 661 662 436 + 638 663 664 438 + 663 665 666 639 + 664 665 667 440 + 641 666 667 441 + 666 668 669 642 + 667 668 670 443 + 644 669 670 444 + 669 671 672 645 + 670 671 673 446 + 647 672 673 447 + 672 674 675 648 + 673 674 676 449 + 650 675 676 450 + 675 677 678 651 + 676 677 679 452 + 653 678 679 453 + 678 680 681 654 + 679 680 682 455 + 656 681 682 456 + 681 683 684 657 + 682 683 685 458 + 659 684 685 459 + 684 686 687 660 + 685 686 688 461 + 662 687 688 462 + 664 689 690 464 + 689 691 692 665 + 690 691 693 466 + 667 692 693 467 + 692 694 695 668 + 693 694 696 469 + 670 695 696 470 + 695 697 698 671 + 696 697 699 472 + 673 698 699 473 + 698 700 701 674 + 699 700 702 475 + 676 701 702 476 + 701 703 704 677 + 702 703 705 478 + 679 704 705 479 + 704 706 707 680 + 705 706 708 481 + 682 707 708 482 + 707 709 710 683 + 708 709 711 484 + 685 710 711 485 + 710 712 713 686 + 711 712 714 487 + 688 713 714 488 + 690 715 716 490 + 715 717 718 691 + 716 717 719 492 + 693 718 719 493 + 718 720 721 694 + 719 720 722 495 + 696 721 722 496 + 721 723 724 697 + 722 723 725 498 + 699 724 725 499 + 724 726 727 700 + 725 726 728 501 + 702 727 728 502 + 727 729 730 703 + 728 729 731 504 + 705 730 731 505 + 730 732 733 706 + 731 732 734 507 + 708 733 734 508 + 733 735 736 709 + 734 735 737 510 + 711 736 737 511 + 736 738 739 712 + 737 738 740 513 + 714 739 740 514 + 716 741 742 516 + 741 743 744 717 + 742 743 745 518 + 719 744 745 519 + 744 746 747 720 + 745 746 748 521 + 722 747 748 522 + 747 749 750 723 + 748 749 751 524 + 725 750 751 525 + 750 752 753 726 + 751 752 754 527 + 728 753 754 528 + 753 755 756 729 + 754 755 757 530 + 731 756 757 531 + 756 758 759 732 + 757 758 760 533 + 734 759 760 534 + 759 761 762 735 + 760 761 763 536 + 737 762 763 537 + 762 764 765 738 + 763 764 766 539 + 740 765 766 540 + 742 767 768 542 + 767 769 770 743 + 768 769 771 544 + 745 770 771 545 + 770 772 773 746 + 771 772 774 547 + 748 773 774 548 + 773 775 776 749 + 774 775 777 550 + 751 776 777 551 + 776 778 779 752 + 777 778 780 553 + 754 779 780 554 + 779 781 782 755 + 780 781 783 556 + 757 782 783 557 + 782 784 785 758 + 783 784 786 559 + 760 785 786 560 + 785 787 788 761 + 786 787 789 562 + 763 788 789 563 + 788 790 791 764 + 789 790 792 565 + 766 791 792 566 + 768 793 794 568 + 793 795 796 769 + 794 795 797 570 + 771 796 797 571 + 796 798 799 772 + 797 798 800 573 + 774 799 800 574 + 799 801 802 775 + 800 801 803 576 + 777 802 803 577 + 802 804 805 778 + 803 804 806 579 + 780 805 806 580 + 805 807 808 781 + 806 807 809 582 + 783 808 809 583 + 808 810 811 784 + 809 810 812 585 + 786 811 812 586 + 811 813 814 787 + 812 813 815 588 + 789 814 815 589 + 814 816 817 790 + 815 816 818 591 + 792 817 818 592 + 819 820 821 595 + 819 822 824 597 + 820 823 825 822 + 821 823 826 598 + 824 825 826 600 + 824 827 829 602 + 825 828 830 827 + 826 828 831 603 + 829 830 831 605 + 829 832 834 607 + 830 833 835 832 + 831 833 836 608 + 834 835 836 610 + 834 837 839 612 + 835 838 840 837 + 836 838 841 613 + 839 840 841 615 + 839 842 844 617 + 840 843 845 842 + 841 843 846 618 + 844 845 846 620 + 844 847 849 622 + 845 848 850 847 + 846 848 851 623 + 849 850 851 625 + 849 852 854 627 + 850 853 855 852 + 851 853 856 628 + 854 855 856 630 + 854 857 859 632 + 855 858 860 857 + 856 858 861 633 + 859 860 861 635 + 821 862 863 637 + 862 864 865 823 + 863 864 866 639 + 826 865 866 640 + 865 867 868 828 + 866 867 869 642 + 831 868 869 643 + 868 870 871 833 + 869 870 872 645 + 836 871 872 646 + 871 873 874 838 + 872 873 875 648 + 841 874 875 649 + 874 876 877 843 + 875 876 878 651 + 846 877 878 652 + 877 879 880 848 + 878 879 881 654 + 851 880 881 655 + 880 882 883 853 + 881 882 884 657 + 856 883 884 658 + 883 885 886 858 + 884 885 887 660 + 861 886 887 661 + 863 888 889 663 + 888 890 891 864 + 889 890 892 665 + 866 891 892 666 + 891 893 894 867 + 892 893 895 668 + 869 894 895 669 + 894 896 897 870 + 895 896 898 671 + 872 897 898 672 + 897 899 900 873 + 898 899 901 674 + 875 900 901 675 + 900 902 903 876 + 901 902 904 677 + 878 903 904 678 + 903 905 906 879 + 904 905 907 680 + 881 906 907 681 + 906 908 909 882 + 907 908 910 683 + 884 909 910 684 + 909 911 912 885 + 910 911 913 686 + 887 912 913 687 + 889 914 915 689 + 914 916 917 890 + 915 916 918 691 + 892 917 918 692 + 917 919 920 893 + 918 919 921 694 + 895 920 921 695 + 920 922 923 896 + 921 922 924 697 + 898 923 924 698 + 923 925 926 899 + 924 925 927 700 + 901 926 927 701 + 926 928 929 902 + 927 928 930 703 + 904 929 930 704 + 929 931 932 905 + 930 931 933 706 + 907 932 933 707 + 932 934 935 908 + 933 934 936 709 + 910 935 936 710 + 935 937 938 911 + 936 937 939 712 + 913 938 939 713 + 915 940 941 715 + 940 942 943 916 + 941 942 944 717 + 918 943 944 718 + 943 945 946 919 + 944 945 947 720 + 921 946 947 721 + 946 948 949 922 + 947 948 950 723 + 924 949 950 724 + 949 951 952 925 + 950 951 953 726 + 927 952 953 727 + 952 954 955 928 + 953 954 956 729 + 930 955 956 730 + 955 957 958 931 + 956 957 959 732 + 933 958 959 733 + 958 960 961 934 + 959 960 962 735 + 936 961 962 736 + 961 963 964 937 + 962 963 965 738 + 939 964 965 739 + 941 966 967 741 + 966 968 969 942 + 967 968 970 743 + 944 969 970 744 + 969 971 972 945 + 970 971 973 746 + 947 972 973 747 + 972 974 975 948 + 973 974 976 749 + 950 975 976 750 + 975 977 978 951 + 976 977 979 752 + 953 978 979 753 + 978 980 981 954 + 979 980 982 755 + 956 981 982 756 + 981 983 984 957 + 982 983 985 758 + 959 984 985 759 + 984 986 987 960 + 985 986 988 761 + 962 987 988 762 + 987 989 990 963 + 988 989 991 764 + 965 990 991 765 + 967 992 993 767 + 992 994 995 968 + 993 994 996 769 + 970 995 996 770 + 995 997 998 971 + 996 997 999 772 + 973 998 999 773 + 998 1000 1001 974 + 999 1000 1002 775 + 976 1001 1002 776 + 1001 1003 1004 977 + 1002 1003 1005 778 + 979 1004 1005 779 + 1004 1006 1007 980 + 1005 1006 1008 781 + 982 1007 1008 782 + 1007 1009 1010 983 + 1008 1009 1011 784 + 985 1010 1011 785 + 1010 1012 1013 986 + 1011 1012 1014 787 + 988 1013 1014 788 + 1013 1015 1016 989 + 1014 1015 1017 790 + 991 1016 1017 791 + 993 1018 1019 793 + 1018 1020 1021 994 + 1019 1020 1022 795 + 996 1021 1022 796 + 1021 1023 1024 997 + 1022 1023 1025 798 + 999 1024 1025 799 + 1024 1026 1027 1000 + 1025 1026 1028 801 + 1002 1027 1028 802 + 1027 1029 1030 1003 + 1028 1029 1031 804 + 1005 1030 1031 805 + 1030 1032 1033 1006 + 1031 1032 1034 807 + 1008 1033 1034 808 + 1033 1035 1036 1009 + 1034 1035 1037 810 + 1011 1036 1037 811 + 1036 1038 1039 1012 + 1037 1038 1040 813 + 1014 1039 1040 814 + 1039 1041 1042 1015 + 1040 1041 1043 816 + 1017 1042 1043 817 + 1044 1045 1046 820 + 1044 1047 1049 822 + 1045 1048 1050 1047 + 1046 1048 1051 823 + 1049 1050 1051 825 + 1049 1052 1054 827 + 1050 1053 1055 1052 + 1051 1053 1056 828 + 1054 1055 1056 830 + 1054 1057 1059 832 + 1055 1058 1060 1057 + 1056 1058 1061 833 + 1059 1060 1061 835 + 1059 1062 1064 837 + 1060 1063 1065 1062 + 1061 1063 1066 838 + 1064 1065 1066 840 + 1064 1067 1069 842 + 1065 1068 1070 1067 + 1066 1068 1071 843 + 1069 1070 1071 845 + 1069 1072 1074 847 + 1070 1073 1075 1072 + 1071 1073 1076 848 + 1074 1075 1076 850 + 1074 1077 1079 852 + 1075 1078 1080 1077 + 1076 1078 1081 853 + 1079 1080 1081 855 + 1079 1082 1084 857 + 1080 1083 1085 1082 + 1081 1083 1086 858 + 1084 1085 1086 860 + 1046 1087 1088 862 + 1087 1089 1090 1048 + 1088 1089 1091 864 + 1051 1090 1091 865 + 1090 1092 1093 1053 + 1091 1092 1094 867 + 1056 1093 1094 868 + 1093 1095 1096 1058 + 1094 1095 1097 870 + 1061 1096 1097 871 + 1096 1098 1099 1063 + 1097 1098 1100 873 + 1066 1099 1100 874 + 1099 1101 1102 1068 + 1100 1101 1103 876 + 1071 1102 1103 877 + 1102 1104 1105 1073 + 1103 1104 1106 879 + 1076 1105 1106 880 + 1105 1107 1108 1078 + 1106 1107 1109 882 + 1081 1108 1109 883 + 1108 1110 1111 1083 + 1109 1110 1112 885 + 1086 1111 1112 886 + 1088 1113 1114 888 + 1113 1115 1116 1089 + 1114 1115 1117 890 + 1091 1116 1117 891 + 1116 1118 1119 1092 + 1117 1118 1120 893 + 1094 1119 1120 894 + 1119 1121 1122 1095 + 1120 1121 1123 896 + 1097 1122 1123 897 + 1122 1124 1125 1098 + 1123 1124 1126 899 + 1100 1125 1126 900 + 1125 1127 1128 1101 + 1126 1127 1129 902 + 1103 1128 1129 903 + 1128 1130 1131 1104 + 1129 1130 1132 905 + 1106 1131 1132 906 + 1131 1133 1134 1107 + 1132 1133 1135 908 + 1109 1134 1135 909 + 1134 1136 1137 1110 + 1135 1136 1138 911 + 1112 1137 1138 912 + 1114 1139 1140 914 + 1139 1141 1142 1115 + 1140 1141 1143 916 + 1117 1142 1143 917 + 1142 1144 1145 1118 + 1143 1144 1146 919 + 1120 1145 1146 920 + 1145 1147 1148 1121 + 1146 1147 1149 922 + 1123 1148 1149 923 + 1148 1150 1151 1124 + 1149 1150 1152 925 + 1126 1151 1152 926 + 1151 1153 1154 1127 + 1152 1153 1155 928 + 1129 1154 1155 929 + 1154 1156 1157 1130 + 1155 1156 1158 931 + 1132 1157 1158 932 + 1157 1159 1160 1133 + 1158 1159 1161 934 + 1135 1160 1161 935 + 1160 1162 1163 1136 + 1161 1162 1164 937 + 1138 1163 1164 938 + 1140 1165 1166 940 + 1165 1167 1168 1141 + 1166 1167 1169 942 + 1143 1168 1169 943 + 1168 1170 1171 1144 + 1169 1170 1172 945 + 1146 1171 1172 946 + 1171 1173 1174 1147 + 1172 1173 1175 948 + 1149 1174 1175 949 + 1174 1176 1177 1150 + 1175 1176 1178 951 + 1152 1177 1178 952 + 1177 1179 1180 1153 + 1178 1179 1181 954 + 1155 1180 1181 955 + 1180 1182 1183 1156 + 1181 1182 1184 957 + 1158 1183 1184 958 + 1183 1185 1186 1159 + 1184 1185 1187 960 + 1161 1186 1187 961 + 1186 1188 1189 1162 + 1187 1188 1190 963 + 1164 1189 1190 964 + 1166 1191 1192 966 + 1191 1193 1194 1167 + 1192 1193 1195 968 + 1169 1194 1195 969 + 1194 1196 1197 1170 + 1195 1196 1198 971 + 1172 1197 1198 972 + 1197 1199 1200 1173 + 1198 1199 1201 974 + 1175 1200 1201 975 + 1200 1202 1203 1176 + 1201 1202 1204 977 + 1178 1203 1204 978 + 1203 1205 1206 1179 + 1204 1205 1207 980 + 1181 1206 1207 981 + 1206 1208 1209 1182 + 1207 1208 1210 983 + 1184 1209 1210 984 + 1209 1211 1212 1185 + 1210 1211 1213 986 + 1187 1212 1213 987 + 1212 1214 1215 1188 + 1213 1214 1216 989 + 1190 1215 1216 990 + 1192 1217 1218 992 + 1217 1219 1220 1193 + 1218 1219 1221 994 + 1195 1220 1221 995 + 1220 1222 1223 1196 + 1221 1222 1224 997 + 1198 1223 1224 998 + 1223 1225 1226 1199 + 1224 1225 1227 1000 + 1201 1226 1227 1001 + 1226 1228 1229 1202 + 1227 1228 1230 1003 + 1204 1229 1230 1004 + 1229 1231 1232 1205 + 1230 1231 1233 1006 + 1207 1232 1233 1007 + 1232 1234 1235 1208 + 1233 1234 1236 1009 + 1210 1235 1236 1010 + 1235 1237 1238 1211 + 1236 1237 1239 1012 + 1213 1238 1239 1013 + 1238 1240 1241 1214 + 1239 1240 1242 1015 + 1216 1241 1242 1016 + 1218 1243 1244 1018 + 1243 1245 1246 1219 + 1244 1245 1247 1020 + 1221 1246 1247 1021 + 1246 1248 1249 1222 + 1247 1248 1250 1023 + 1224 1249 1250 1024 + 1249 1251 1252 1225 + 1250 1251 1253 1026 + 1227 1252 1253 1027 + 1252 1254 1255 1228 + 1253 1254 1256 1029 + 1230 1255 1256 1030 + 1255 1257 1258 1231 + 1256 1257 1259 1032 + 1233 1258 1259 1033 + 1258 1260 1261 1234 + 1259 1260 1262 1035 + 1236 1261 1262 1036 + 1261 1263 1264 1237 + 1262 1263 1265 1038 + 1239 1264 1265 1039 + 1264 1266 1267 1240 + 1265 1266 1268 1041 + 1242 1267 1268 1042 + 1269 1270 1271 1045 + 1269 1272 1274 1047 + 1270 1273 1275 1272 + 1271 1273 1276 1048 + 1274 1275 1276 1050 + 1274 1277 1279 1052 + 1275 1278 1280 1277 + 1276 1278 1281 1053 + 1279 1280 1281 1055 + 1279 1282 1284 1057 + 1280 1283 1285 1282 + 1281 1283 1286 1058 + 1284 1285 1286 1060 + 1284 1287 1289 1062 + 1285 1288 1290 1287 + 1286 1288 1291 1063 + 1289 1290 1291 1065 + 1289 1292 1294 1067 + 1290 1293 1295 1292 + 1291 1293 1296 1068 + 1294 1295 1296 1070 + 1294 1297 1299 1072 + 1295 1298 1300 1297 + 1296 1298 1301 1073 + 1299 1300 1301 1075 + 1299 1302 1304 1077 + 1300 1303 1305 1302 + 1301 1303 1306 1078 + 1304 1305 1306 1080 + 1304 1307 1309 1082 + 1305 1308 1310 1307 + 1306 1308 1311 1083 + 1309 1310 1311 1085 + 1271 1312 1313 1087 + 1312 1314 1315 1273 + 1313 1314 1316 1089 + 1276 1315 1316 1090 + 1315 1317 1318 1278 + 1316 1317 1319 1092 + 1281 1318 1319 1093 + 1318 1320 1321 1283 + 1319 1320 1322 1095 + 1286 1321 1322 1096 + 1321 1323 1324 1288 + 1322 1323 1325 1098 + 1291 1324 1325 1099 + 1324 1326 1327 1293 + 1325 1326 1328 1101 + 1296 1327 1328 1102 + 1327 1329 1330 1298 + 1328 1329 1331 1104 + 1301 1330 1331 1105 + 1330 1332 1333 1303 + 1331 1332 1334 1107 + 1306 1333 1334 1108 + 1333 1335 1336 1308 + 1334 1335 1337 1110 + 1311 1336 1337 1111 + 1313 1338 1339 1113 + 1338 1340 1341 1314 + 1339 1340 1342 1115 + 1316 1341 1342 1116 + 1341 1343 1344 1317 + 1342 1343 1345 1118 + 1319 1344 1345 1119 + 1344 1346 1347 1320 + 1345 1346 1348 1121 + 1322 1347 1348 1122 + 1347 1349 1350 1323 + 1348 1349 1351 1124 + 1325 1350 1351 1125 + 1350 1352 1353 1326 + 1351 1352 1354 1127 + 1328 1353 1354 1128 + 1353 1355 1356 1329 + 1354 1355 1357 1130 + 1331 1356 1357 1131 + 1356 1358 1359 1332 + 1357 1358 1360 1133 + 1334 1359 1360 1134 + 1359 1361 1362 1335 + 1360 1361 1363 1136 + 1337 1362 1363 1137 + 1339 1364 1365 1139 + 1364 1366 1367 1340 + 1365 1366 1368 1141 + 1342 1367 1368 1142 + 1367 1369 1370 1343 + 1368 1369 1371 1144 + 1345 1370 1371 1145 + 1370 1372 1373 1346 + 1371 1372 1374 1147 + 1348 1373 1374 1148 + 1373 1375 1376 1349 + 1374 1375 1377 1150 + 1351 1376 1377 1151 + 1376 1378 1379 1352 + 1377 1378 1380 1153 + 1354 1379 1380 1154 + 1379 1381 1382 1355 + 1380 1381 1383 1156 + 1357 1382 1383 1157 + 1382 1384 1385 1358 + 1383 1384 1386 1159 + 1360 1385 1386 1160 + 1385 1387 1388 1361 + 1386 1387 1389 1162 + 1363 1388 1389 1163 + 1365 1390 1391 1165 + 1390 1392 1393 1366 + 1391 1392 1394 1167 + 1368 1393 1394 1168 + 1393 1395 1396 1369 + 1394 1395 1397 1170 + 1371 1396 1397 1171 + 1396 1398 1399 1372 + 1397 1398 1400 1173 + 1374 1399 1400 1174 + 1399 1401 1402 1375 + 1400 1401 1403 1176 + 1377 1402 1403 1177 + 1402 1404 1405 1378 + 1403 1404 1406 1179 + 1380 1405 1406 1180 + 1405 1407 1408 1381 + 1406 1407 1409 1182 + 1383 1408 1409 1183 + 1408 1410 1411 1384 + 1409 1410 1412 1185 + 1386 1411 1412 1186 + 1411 1413 1414 1387 + 1412 1413 1415 1188 + 1389 1414 1415 1189 + 1391 1416 1417 1191 + 1416 1418 1419 1392 + 1417 1418 1420 1193 + 1394 1419 1420 1194 + 1419 1421 1422 1395 + 1420 1421 1423 1196 + 1397 1422 1423 1197 + 1422 1424 1425 1398 + 1423 1424 1426 1199 + 1400 1425 1426 1200 + 1425 1427 1428 1401 + 1426 1427 1429 1202 + 1403 1428 1429 1203 + 1428 1430 1431 1404 + 1429 1430 1432 1205 + 1406 1431 1432 1206 + 1431 1433 1434 1407 + 1432 1433 1435 1208 + 1409 1434 1435 1209 + 1434 1436 1437 1410 + 1435 1436 1438 1211 + 1412 1437 1438 1212 + 1437 1439 1440 1413 + 1438 1439 1441 1214 + 1415 1440 1441 1215 + 1417 1442 1443 1217 + 1442 1444 1445 1418 + 1443 1444 1446 1219 + 1420 1445 1446 1220 + 1445 1447 1448 1421 + 1446 1447 1449 1222 + 1423 1448 1449 1223 + 1448 1450 1451 1424 + 1449 1450 1452 1225 + 1426 1451 1452 1226 + 1451 1453 1454 1427 + 1452 1453 1455 1228 + 1429 1454 1455 1229 + 1454 1456 1457 1430 + 1455 1456 1458 1231 + 1432 1457 1458 1232 + 1457 1459 1460 1433 + 1458 1459 1461 1234 + 1435 1460 1461 1235 + 1460 1462 1463 1436 + 1461 1462 1464 1237 + 1438 1463 1464 1238 + 1463 1465 1466 1439 + 1464 1465 1467 1240 + 1441 1466 1467 1241 + 1443 1468 1469 1243 + 1468 1470 1471 1444 + 1469 1470 1472 1245 + 1446 1471 1472 1246 + 1471 1473 1474 1447 + 1472 1473 1475 1248 + 1449 1474 1475 1249 + 1474 1476 1477 1450 + 1475 1476 1478 1251 + 1452 1477 1478 1252 + 1477 1479 1480 1453 + 1478 1479 1481 1254 + 1455 1480 1481 1255 + 1480 1482 1483 1456 + 1481 1482 1484 1257 + 1458 1483 1484 1258 + 1483 1485 1486 1459 + 1484 1485 1487 1260 + 1461 1486 1487 1261 + 1486 1488 1489 1462 + 1487 1488 1490 1263 + 1464 1489 1490 1264 + 1489 1491 1492 1465 + 1490 1491 1493 1266 + 1467 1492 1493 1267 + 1494 1495 1496 1270 + 1494 1497 1499 1272 + 1495 1498 1500 1497 + 1496 1498 1501 1273 + 1499 1500 1501 1275 + 1499 1502 1504 1277 + 1500 1503 1505 1502 + 1501 1503 1506 1278 + 1504 1505 1506 1280 + 1504 1507 1509 1282 + 1505 1508 1510 1507 + 1506 1508 1511 1283 + 1509 1510 1511 1285 + 1509 1512 1514 1287 + 1510 1513 1515 1512 + 1511 1513 1516 1288 + 1514 1515 1516 1290 + 1514 1517 1519 1292 + 1515 1518 1520 1517 + 1516 1518 1521 1293 + 1519 1520 1521 1295 + 1519 1522 1524 1297 + 1520 1523 1525 1522 + 1521 1523 1526 1298 + 1524 1525 1526 1300 + 1524 1527 1529 1302 + 1525 1528 1530 1527 + 1526 1528 1531 1303 + 1529 1530 1531 1305 + 1529 1532 1534 1307 + 1530 1533 1535 1532 + 1531 1533 1536 1308 + 1534 1535 1536 1310 + 1496 1537 1538 1312 + 1537 1539 1540 1498 + 1538 1539 1541 1314 + 1501 1540 1541 1315 + 1540 1542 1543 1503 + 1541 1542 1544 1317 + 1506 1543 1544 1318 + 1543 1545 1546 1508 + 1544 1545 1547 1320 + 1511 1546 1547 1321 + 1546 1548 1549 1513 + 1547 1548 1550 1323 + 1516 1549 1550 1324 + 1549 1551 1552 1518 + 1550 1551 1553 1326 + 1521 1552 1553 1327 + 1552 1554 1555 1523 + 1553 1554 1556 1329 + 1526 1555 1556 1330 + 1555 1557 1558 1528 + 1556 1557 1559 1332 + 1531 1558 1559 1333 + 1558 1560 1561 1533 + 1559 1560 1562 1335 + 1536 1561 1562 1336 + 1538 1563 1564 1338 + 1563 1565 1566 1539 + 1564 1565 1567 1340 + 1541 1566 1567 1341 + 1566 1568 1569 1542 + 1567 1568 1570 1343 + 1544 1569 1570 1344 + 1569 1571 1572 1545 + 1570 1571 1573 1346 + 1547 1572 1573 1347 + 1572 1574 1575 1548 + 1573 1574 1576 1349 + 1550 1575 1576 1350 + 1575 1577 1578 1551 + 1576 1577 1579 1352 + 1553 1578 1579 1353 + 1578 1580 1581 1554 + 1579 1580 1582 1355 + 1556 1581 1582 1356 + 1581 1583 1584 1557 + 1582 1583 1585 1358 + 1559 1584 1585 1359 + 1584 1586 1587 1560 + 1585 1586 1588 1361 + 1562 1587 1588 1362 + 1564 1589 1590 1364 + 1589 1591 1592 1565 + 1590 1591 1593 1366 + 1567 1592 1593 1367 + 1592 1594 1595 1568 + 1593 1594 1596 1369 + 1570 1595 1596 1370 + 1595 1597 1598 1571 + 1596 1597 1599 1372 + 1573 1598 1599 1373 + 1598 1600 1601 1574 + 1599 1600 1602 1375 + 1576 1601 1602 1376 + 1601 1603 1604 1577 + 1602 1603 1605 1378 + 1579 1604 1605 1379 + 1604 1606 1607 1580 + 1605 1606 1608 1381 + 1582 1607 1608 1382 + 1607 1609 1610 1583 + 1608 1609 1611 1384 + 1585 1610 1611 1385 + 1610 1612 1613 1586 + 1611 1612 1614 1387 + 1588 1613 1614 1388 + 1590 1615 1616 1390 + 1615 1617 1618 1591 + 1616 1617 1619 1392 + 1593 1618 1619 1393 + 1618 1620 1621 1594 + 1619 1620 1622 1395 + 1596 1621 1622 1396 + 1621 1623 1624 1597 + 1622 1623 1625 1398 + 1599 1624 1625 1399 + 1624 1626 1627 1600 + 1625 1626 1628 1401 + 1602 1627 1628 1402 + 1627 1629 1630 1603 + 1628 1629 1631 1404 + 1605 1630 1631 1405 + 1630 1632 1633 1606 + 1631 1632 1634 1407 + 1608 1633 1634 1408 + 1633 1635 1636 1609 + 1634 1635 1637 1410 + 1611 1636 1637 1411 + 1636 1638 1639 1612 + 1637 1638 1640 1413 + 1614 1639 1640 1414 + 1616 1641 1642 1416 + 1641 1643 1644 1617 + 1642 1643 1645 1418 + 1619 1644 1645 1419 + 1644 1646 1647 1620 + 1645 1646 1648 1421 + 1622 1647 1648 1422 + 1647 1649 1650 1623 + 1648 1649 1651 1424 + 1625 1650 1651 1425 + 1650 1652 1653 1626 + 1651 1652 1654 1427 + 1628 1653 1654 1428 + 1653 1655 1656 1629 + 1654 1655 1657 1430 + 1631 1656 1657 1431 + 1656 1658 1659 1632 + 1657 1658 1660 1433 + 1634 1659 1660 1434 + 1659 1661 1662 1635 + 1660 1661 1663 1436 + 1637 1662 1663 1437 + 1662 1664 1665 1638 + 1663 1664 1666 1439 + 1640 1665 1666 1440 + 1642 1667 1668 1442 + 1667 1669 1670 1643 + 1668 1669 1671 1444 + 1645 1670 1671 1445 + 1670 1672 1673 1646 + 1671 1672 1674 1447 + 1648 1673 1674 1448 + 1673 1675 1676 1649 + 1674 1675 1677 1450 + 1651 1676 1677 1451 + 1676 1678 1679 1652 + 1677 1678 1680 1453 + 1654 1679 1680 1454 + 1679 1681 1682 1655 + 1680 1681 1683 1456 + 1657 1682 1683 1457 + 1682 1684 1685 1658 + 1683 1684 1686 1459 + 1660 1685 1686 1460 + 1685 1687 1688 1661 + 1686 1687 1689 1462 + 1663 1688 1689 1463 + 1688 1690 1691 1664 + 1689 1690 1692 1465 + 1666 1691 1692 1466 + 1668 1693 1694 1468 + 1693 1695 1696 1669 + 1694 1695 1697 1470 + 1671 1696 1697 1471 + 1696 1698 1699 1672 + 1697 1698 1700 1473 + 1674 1699 1700 1474 + 1699 1701 1702 1675 + 1700 1701 1703 1476 + 1677 1702 1703 1477 + 1702 1704 1705 1678 + 1703 1704 1706 1479 + 1680 1705 1706 1480 + 1705 1707 1708 1681 + 1706 1707 1709 1482 + 1683 1708 1709 1483 + 1708 1710 1711 1684 + 1709 1710 1712 1485 + 1686 1711 1712 1486 + 1711 1713 1714 1687 + 1712 1713 1715 1488 + 1689 1714 1715 1489 + 1714 1716 1717 1690 + 1715 1716 1718 1491 + 1692 1717 1718 1492 + 1719 1720 1721 1495 + 1719 1722 1724 1497 + 1720 1723 1725 1722 + 1721 1723 1726 1498 + 1724 1725 1726 1500 + 1724 1727 1729 1502 + 1725 1728 1730 1727 + 1726 1728 1731 1503 + 1729 1730 1731 1505 + 1729 1732 1734 1507 + 1730 1733 1735 1732 + 1731 1733 1736 1508 + 1734 1735 1736 1510 + 1734 1737 1739 1512 + 1735 1738 1740 1737 + 1736 1738 1741 1513 + 1739 1740 1741 1515 + 1739 1742 1744 1517 + 1740 1743 1745 1742 + 1741 1743 1746 1518 + 1744 1745 1746 1520 + 1744 1747 1749 1522 + 1745 1748 1750 1747 + 1746 1748 1751 1523 + 1749 1750 1751 1525 + 1749 1752 1754 1527 + 1750 1753 1755 1752 + 1751 1753 1756 1528 + 1754 1755 1756 1530 + 1754 1757 1759 1532 + 1755 1758 1760 1757 + 1756 1758 1761 1533 + 1759 1760 1761 1535 + 1721 1762 1763 1537 + 1762 1764 1765 1723 + 1763 1764 1766 1539 + 1726 1765 1766 1540 + 1765 1767 1768 1728 + 1766 1767 1769 1542 + 1731 1768 1769 1543 + 1768 1770 1771 1733 + 1769 1770 1772 1545 + 1736 1771 1772 1546 + 1771 1773 1774 1738 + 1772 1773 1775 1548 + 1741 1774 1775 1549 + 1774 1776 1777 1743 + 1775 1776 1778 1551 + 1746 1777 1778 1552 + 1777 1779 1780 1748 + 1778 1779 1781 1554 + 1751 1780 1781 1555 + 1780 1782 1783 1753 + 1781 1782 1784 1557 + 1756 1783 1784 1558 + 1783 1785 1786 1758 + 1784 1785 1787 1560 + 1761 1786 1787 1561 + 1763 1788 1789 1563 + 1788 1790 1791 1764 + 1789 1790 1792 1565 + 1766 1791 1792 1566 + 1791 1793 1794 1767 + 1792 1793 1795 1568 + 1769 1794 1795 1569 + 1794 1796 1797 1770 + 1795 1796 1798 1571 + 1772 1797 1798 1572 + 1797 1799 1800 1773 + 1798 1799 1801 1574 + 1775 1800 1801 1575 + 1800 1802 1803 1776 + 1801 1802 1804 1577 + 1778 1803 1804 1578 + 1803 1805 1806 1779 + 1804 1805 1807 1580 + 1781 1806 1807 1581 + 1806 1808 1809 1782 + 1807 1808 1810 1583 + 1784 1809 1810 1584 + 1809 1811 1812 1785 + 1810 1811 1813 1586 + 1787 1812 1813 1587 + 1789 1814 1815 1589 + 1814 1816 1817 1790 + 1815 1816 1818 1591 + 1792 1817 1818 1592 + 1817 1819 1820 1793 + 1818 1819 1821 1594 + 1795 1820 1821 1595 + 1820 1822 1823 1796 + 1821 1822 1824 1597 + 1798 1823 1824 1598 + 1823 1825 1826 1799 + 1824 1825 1827 1600 + 1801 1826 1827 1601 + 1826 1828 1829 1802 + 1827 1828 1830 1603 + 1804 1829 1830 1604 + 1829 1831 1832 1805 + 1830 1831 1833 1606 + 1807 1832 1833 1607 + 1832 1834 1835 1808 + 1833 1834 1836 1609 + 1810 1835 1836 1610 + 1835 1837 1838 1811 + 1836 1837 1839 1612 + 1813 1838 1839 1613 + 1815 1840 1841 1615 + 1840 1842 1843 1816 + 1841 1842 1844 1617 + 1818 1843 1844 1618 + 1843 1845 1846 1819 + 1844 1845 1847 1620 + 1821 1846 1847 1621 + 1846 1848 1849 1822 + 1847 1848 1850 1623 + 1824 1849 1850 1624 + 1849 1851 1852 1825 + 1850 1851 1853 1626 + 1827 1852 1853 1627 + 1852 1854 1855 1828 + 1853 1854 1856 1629 + 1830 1855 1856 1630 + 1855 1857 1858 1831 + 1856 1857 1859 1632 + 1833 1858 1859 1633 + 1858 1860 1861 1834 + 1859 1860 1862 1635 + 1836 1861 1862 1636 + 1861 1863 1864 1837 + 1862 1863 1865 1638 + 1839 1864 1865 1639 + 1841 1866 1867 1641 + 1866 1868 1869 1842 + 1867 1868 1870 1643 + 1844 1869 1870 1644 + 1869 1871 1872 1845 + 1870 1871 1873 1646 + 1847 1872 1873 1647 + 1872 1874 1875 1848 + 1873 1874 1876 1649 + 1850 1875 1876 1650 + 1875 1877 1878 1851 + 1876 1877 1879 1652 + 1853 1878 1879 1653 + 1878 1880 1881 1854 + 1879 1880 1882 1655 + 1856 1881 1882 1656 + 1881 1883 1884 1857 + 1882 1883 1885 1658 + 1859 1884 1885 1659 + 1884 1886 1887 1860 + 1885 1886 1888 1661 + 1862 1887 1888 1662 + 1887 1889 1890 1863 + 1888 1889 1891 1664 + 1865 1890 1891 1665 + 1867 1892 1893 1667 + 1892 1894 1895 1868 + 1893 1894 1896 1669 + 1870 1895 1896 1670 + 1895 1897 1898 1871 + 1896 1897 1899 1672 + 1873 1898 1899 1673 + 1898 1900 1901 1874 + 1899 1900 1902 1675 + 1876 1901 1902 1676 + 1901 1903 1904 1877 + 1902 1903 1905 1678 + 1879 1904 1905 1679 + 1904 1906 1907 1880 + 1905 1906 1908 1681 + 1882 1907 1908 1682 + 1907 1909 1910 1883 + 1908 1909 1911 1684 + 1885 1910 1911 1685 + 1910 1912 1913 1886 + 1911 1912 1914 1687 + 1888 1913 1914 1688 + 1913 1915 1916 1889 + 1914 1915 1917 1690 + 1891 1916 1917 1691 + 1893 1918 1919 1693 + 1918 1920 1921 1894 + 1919 1920 1922 1695 + 1896 1921 1922 1696 + 1921 1923 1924 1897 + 1922 1923 1925 1698 + 1899 1924 1925 1699 + 1924 1926 1927 1900 + 1925 1926 1928 1701 + 1902 1927 1928 1702 + 1927 1929 1930 1903 + 1928 1929 1931 1704 + 1905 1930 1931 1705 + 1930 1932 1933 1906 + 1931 1932 1934 1707 + 1908 1933 1934 1708 + 1933 1935 1936 1909 + 1934 1935 1937 1710 + 1911 1936 1937 1711 + 1936 1938 1939 1912 + 1937 1938 1940 1713 + 1914 1939 1940 1714 + 1939 1941 1942 1915 + 1940 1941 1943 1716 + 1917 1942 1943 1717 + + + 0 1 2 3 4 5 + 5 6 7 8 9 10 + 10 11 12 13 14 15 + 15 16 17 18 19 20 + 20 21 22 23 24 25 + 25 26 27 28 29 30 + 30 31 32 33 34 35 + 35 36 37 38 39 40 + 41 3 42 43 44 45 + 45 8 46 47 48 49 + 49 13 50 51 52 53 + 53 18 54 55 56 57 + 57 23 58 59 60 61 + 61 28 62 63 64 65 + 65 33 66 67 68 69 + 69 38 70 71 72 73 + 74 43 75 76 77 78 + 78 47 79 80 81 82 + 82 51 83 84 85 86 + 86 55 87 88 89 90 + 90 59 91 92 93 94 + 94 63 95 96 97 98 + 98 67 99 100 101 102 + 102 71 103 104 105 106 + 107 76 108 109 110 111 + 111 80 112 113 114 115 + 115 84 116 117 118 119 + 119 88 120 121 122 123 + 123 92 124 125 126 127 + 127 96 128 129 130 131 + 131 100 132 133 134 135 + 135 104 136 137 138 139 + 140 109 141 142 143 144 + 144 113 145 146 147 148 + 148 117 149 150 151 152 + 152 121 153 154 155 156 + 156 125 157 158 159 160 + 160 129 161 162 163 164 + 164 133 165 166 167 168 + 168 137 169 170 171 172 + 173 142 174 175 176 177 + 177 146 178 179 180 181 + 181 150 182 183 184 185 + 185 154 186 187 188 189 + 189 158 190 191 192 193 + 193 162 194 195 196 197 + 197 166 198 199 200 201 + 201 170 202 203 204 205 + 206 175 207 208 209 210 + 210 179 211 212 213 214 + 214 183 215 216 217 218 + 218 187 219 220 221 222 + 222 191 223 224 225 226 + 226 195 227 228 229 230 + 230 199 231 232 233 234 + 234 203 235 236 237 238 + 239 208 240 241 242 243 + 243 212 244 245 246 247 + 247 216 248 249 250 251 + 251 220 252 253 254 255 + 255 224 256 257 258 259 + 259 228 260 261 262 263 + 263 232 264 265 266 267 + 267 236 268 269 270 271 + 272 273 274 275 2 276 + 276 277 278 279 7 280 + 280 281 282 283 12 284 + 284 285 286 287 17 288 + 288 289 290 291 22 292 + 292 293 294 295 27 296 + 296 297 298 299 32 300 + 300 301 302 303 37 304 + 305 275 306 307 42 308 + 308 279 309 310 46 311 + 311 283 312 313 50 314 + 314 287 315 316 54 317 + 317 291 318 319 58 320 + 320 295 321 322 62 323 + 323 299 324 325 66 326 + 326 303 327 328 70 329 + 330 307 331 332 75 333 + 333 310 334 335 79 336 + 336 313 337 338 83 339 + 339 316 340 341 87 342 + 342 319 343 344 91 345 + 345 322 346 347 95 348 + 348 325 349 350 99 351 + 351 328 352 353 103 354 + 355 332 356 357 108 358 + 358 335 359 360 112 361 + 361 338 362 363 116 364 + 364 341 365 366 120 367 + 367 344 368 369 124 370 + 370 347 371 372 128 373 + 373 350 374 375 132 376 + 376 353 377 378 136 379 + 380 357 381 382 141 383 + 383 360 384 385 145 386 + 386 363 387 388 149 389 + 389 366 390 391 153 392 + 392 369 393 394 157 395 + 395 372 396 397 161 398 + 398 375 399 400 165 401 + 401 378 402 403 169 404 + 405 382 406 407 174 408 + 408 385 409 410 178 411 + 411 388 412 413 182 414 + 414 391 415 416 186 417 + 417 394 418 419 190 420 + 420 397 421 422 194 423 + 423 400 424 425 198 426 + 426 403 427 428 202 429 + 430 407 431 432 207 433 + 433 410 434 435 211 436 + 436 413 437 438 215 439 + 439 416 440 441 219 442 + 442 419 443 444 223 445 + 445 422 446 447 227 448 + 448 425 449 450 231 451 + 451 428 452 453 235 454 + 455 432 456 457 240 458 + 458 435 459 460 244 461 + 461 438 462 463 248 464 + 464 441 465 466 252 467 + 467 444 468 469 256 470 + 470 447 471 472 260 473 + 473 450 474 475 264 476 + 476 453 477 478 268 479 + 480 481 482 483 274 484 + 484 485 486 487 278 488 + 488 489 490 491 282 492 + 492 493 494 495 286 496 + 496 497 498 499 290 500 + 500 501 502 503 294 504 + 504 505 506 507 298 508 + 508 509 510 511 302 512 + 513 483 514 515 306 516 + 516 487 517 518 309 519 + 519 491 520 521 312 522 + 522 495 523 524 315 525 + 525 499 526 527 318 528 + 528 503 529 530 321 531 + 531 507 532 533 324 534 + 534 511 535 536 327 537 + 538 515 539 540 331 541 + 541 518 542 543 334 544 + 544 521 545 546 337 547 + 547 524 548 549 340 550 + 550 527 551 552 343 553 + 553 530 554 555 346 556 + 556 533 557 558 349 559 + 559 536 560 561 352 562 + 563 540 564 565 356 566 + 566 543 567 568 359 569 + 569 546 570 571 362 572 + 572 549 573 574 365 575 + 575 552 576 577 368 578 + 578 555 579 580 371 581 + 581 558 582 583 374 584 + 584 561 585 586 377 587 + 588 565 589 590 381 591 + 591 568 592 593 384 594 + 594 571 595 596 387 597 + 597 574 598 599 390 600 + 600 577 601 602 393 603 + 603 580 604 605 396 606 + 606 583 607 608 399 609 + 609 586 610 611 402 612 + 613 590 614 615 406 616 + 616 593 617 618 409 619 + 619 596 620 621 412 622 + 622 599 623 624 415 625 + 625 602 626 627 418 628 + 628 605 629 630 421 631 + 631 608 632 633 424 634 + 634 611 635 636 427 637 + 638 615 639 640 431 641 + 641 618 642 643 434 644 + 644 621 645 646 437 647 + 647 624 648 649 440 650 + 650 627 651 652 443 653 + 653 630 654 655 446 656 + 656 633 657 658 449 659 + 659 636 660 661 452 662 + 663 640 664 665 456 666 + 666 643 667 668 459 669 + 669 646 670 671 462 672 + 672 649 673 674 465 675 + 675 652 676 677 468 678 + 678 655 679 680 471 681 + 681 658 682 683 474 684 + 684 661 685 686 477 687 + 688 689 690 691 482 692 + 692 693 694 695 486 696 + 696 697 698 699 490 700 + 700 701 702 703 494 704 + 704 705 706 707 498 708 + 708 709 710 711 502 712 + 712 713 714 715 506 716 + 716 717 718 719 510 720 + 721 691 722 723 514 724 + 724 695 725 726 517 727 + 727 699 728 729 520 730 + 730 703 731 732 523 733 + 733 707 734 735 526 736 + 736 711 737 738 529 739 + 739 715 740 741 532 742 + 742 719 743 744 535 745 + 746 723 747 748 539 749 + 749 726 750 751 542 752 + 752 729 753 754 545 755 + 755 732 756 757 548 758 + 758 735 759 760 551 761 + 761 738 762 763 554 764 + 764 741 765 766 557 767 + 767 744 768 769 560 770 + 771 748 772 773 564 774 + 774 751 775 776 567 777 + 777 754 778 779 570 780 + 780 757 781 782 573 783 + 783 760 784 785 576 786 + 786 763 787 788 579 789 + 789 766 790 791 582 792 + 792 769 793 794 585 795 + 796 773 797 798 589 799 + 799 776 800 801 592 802 + 802 779 803 804 595 805 + 805 782 806 807 598 808 + 808 785 809 810 601 811 + 811 788 812 813 604 814 + 814 791 815 816 607 817 + 817 794 818 819 610 820 + 821 798 822 823 614 824 + 824 801 825 826 617 827 + 827 804 828 829 620 830 + 830 807 831 832 623 833 + 833 810 834 835 626 836 + 836 813 837 838 629 839 + 839 816 840 841 632 842 + 842 819 843 844 635 845 + 846 823 847 848 639 849 + 849 826 850 851 642 852 + 852 829 853 854 645 855 + 855 832 856 857 648 858 + 858 835 859 860 651 861 + 861 838 862 863 654 864 + 864 841 865 866 657 867 + 867 844 868 869 660 870 + 871 848 872 873 664 874 + 874 851 875 876 667 877 + 877 854 878 879 670 880 + 880 857 881 882 673 883 + 883 860 884 885 676 886 + 886 863 887 888 679 889 + 889 866 890 891 682 892 + 892 869 893 894 685 895 + 896 897 898 899 690 900 + 900 901 902 903 694 904 + 904 905 906 907 698 908 + 908 909 910 911 702 912 + 912 913 914 915 706 916 + 916 917 918 919 710 920 + 920 921 922 923 714 924 + 924 925 926 927 718 928 + 929 899 930 931 722 932 + 932 903 933 934 725 935 + 935 907 936 937 728 938 + 938 911 939 940 731 941 + 941 915 942 943 734 944 + 944 919 945 946 737 947 + 947 923 948 949 740 950 + 950 927 951 952 743 953 + 954 931 955 956 747 957 + 957 934 958 959 750 960 + 960 937 961 962 753 963 + 963 940 964 965 756 966 + 966 943 967 968 759 969 + 969 946 970 971 762 972 + 972 949 973 974 765 975 + 975 952 976 977 768 978 + 979 956 980 981 772 982 + 982 959 983 984 775 985 + 985 962 986 987 778 988 + 988 965 989 990 781 991 + 991 968 992 993 784 994 + 994 971 995 996 787 997 + 997 974 998 999 790 1000 + 1000 977 1001 1002 793 1003 + 1004 981 1005 1006 797 1007 + 1007 984 1008 1009 800 1010 + 1010 987 1011 1012 803 1013 + 1013 990 1014 1015 806 1016 + 1016 993 1017 1018 809 1019 + 1019 996 1020 1021 812 1022 + 1022 999 1023 1024 815 1025 + 1025 1002 1026 1027 818 1028 + 1029 1006 1030 1031 822 1032 + 1032 1009 1033 1034 825 1035 + 1035 1012 1036 1037 828 1038 + 1038 1015 1039 1040 831 1041 + 1041 1018 1042 1043 834 1044 + 1044 1021 1045 1046 837 1047 + 1047 1024 1048 1049 840 1050 + 1050 1027 1051 1052 843 1053 + 1054 1031 1055 1056 847 1057 + 1057 1034 1058 1059 850 1060 + 1060 1037 1061 1062 853 1063 + 1063 1040 1064 1065 856 1066 + 1066 1043 1067 1068 859 1069 + 1069 1046 1070 1071 862 1072 + 1072 1049 1073 1074 865 1075 + 1075 1052 1076 1077 868 1078 + 1079 1056 1080 1081 872 1082 + 1082 1059 1083 1084 875 1085 + 1085 1062 1086 1087 878 1088 + 1088 1065 1089 1090 881 1091 + 1091 1068 1092 1093 884 1094 + 1094 1071 1095 1096 887 1097 + 1097 1074 1098 1099 890 1100 + 1100 1077 1101 1102 893 1103 + 1104 1105 1106 1107 898 1108 + 1108 1109 1110 1111 902 1112 + 1112 1113 1114 1115 906 1116 + 1116 1117 1118 1119 910 1120 + 1120 1121 1122 1123 914 1124 + 1124 1125 1126 1127 918 1128 + 1128 1129 1130 1131 922 1132 + 1132 1133 1134 1135 926 1136 + 1137 1107 1138 1139 930 1140 + 1140 1111 1141 1142 933 1143 + 1143 1115 1144 1145 936 1146 + 1146 1119 1147 1148 939 1149 + 1149 1123 1150 1151 942 1152 + 1152 1127 1153 1154 945 1155 + 1155 1131 1156 1157 948 1158 + 1158 1135 1159 1160 951 1161 + 1162 1139 1163 1164 955 1165 + 1165 1142 1166 1167 958 1168 + 1168 1145 1169 1170 961 1171 + 1171 1148 1172 1173 964 1174 + 1174 1151 1175 1176 967 1177 + 1177 1154 1178 1179 970 1180 + 1180 1157 1181 1182 973 1183 + 1183 1160 1184 1185 976 1186 + 1187 1164 1188 1189 980 1190 + 1190 1167 1191 1192 983 1193 + 1193 1170 1194 1195 986 1196 + 1196 1173 1197 1198 989 1199 + 1199 1176 1200 1201 992 1202 + 1202 1179 1203 1204 995 1205 + 1205 1182 1206 1207 998 1208 + 1208 1185 1209 1210 1001 1211 + 1212 1189 1213 1214 1005 1215 + 1215 1192 1216 1217 1008 1218 + 1218 1195 1219 1220 1011 1221 + 1221 1198 1222 1223 1014 1224 + 1224 1201 1225 1226 1017 1227 + 1227 1204 1228 1229 1020 1230 + 1230 1207 1231 1232 1023 1233 + 1233 1210 1234 1235 1026 1236 + 1237 1214 1238 1239 1030 1240 + 1240 1217 1241 1242 1033 1243 + 1243 1220 1244 1245 1036 1246 + 1246 1223 1247 1248 1039 1249 + 1249 1226 1250 1251 1042 1252 + 1252 1229 1253 1254 1045 1255 + 1255 1232 1256 1257 1048 1258 + 1258 1235 1259 1260 1051 1261 + 1262 1239 1263 1264 1055 1265 + 1265 1242 1266 1267 1058 1268 + 1268 1245 1269 1270 1061 1271 + 1271 1248 1272 1273 1064 1274 + 1274 1251 1275 1276 1067 1277 + 1277 1254 1278 1279 1070 1280 + 1280 1257 1281 1282 1073 1283 + 1283 1260 1284 1285 1076 1286 + 1287 1264 1288 1289 1080 1290 + 1290 1267 1291 1292 1083 1293 + 1293 1270 1294 1295 1086 1296 + 1296 1273 1297 1298 1089 1299 + 1299 1276 1300 1301 1092 1302 + 1302 1279 1303 1304 1095 1305 + 1305 1282 1306 1307 1098 1308 + 1308 1285 1309 1310 1101 1311 + 1312 1313 1314 1315 1106 1316 + 1316 1317 1318 1319 1110 1320 + 1320 1321 1322 1323 1114 1324 + 1324 1325 1326 1327 1118 1328 + 1328 1329 1330 1331 1122 1332 + 1332 1333 1334 1335 1126 1336 + 1336 1337 1338 1339 1130 1340 + 1340 1341 1342 1343 1134 1344 + 1345 1315 1346 1347 1138 1348 + 1348 1319 1349 1350 1141 1351 + 1351 1323 1352 1353 1144 1354 + 1354 1327 1355 1356 1147 1357 + 1357 1331 1358 1359 1150 1360 + 1360 1335 1361 1362 1153 1363 + 1363 1339 1364 1365 1156 1366 + 1366 1343 1367 1368 1159 1369 + 1370 1347 1371 1372 1163 1373 + 1373 1350 1374 1375 1166 1376 + 1376 1353 1377 1378 1169 1379 + 1379 1356 1380 1381 1172 1382 + 1382 1359 1383 1384 1175 1385 + 1385 1362 1386 1387 1178 1388 + 1388 1365 1389 1390 1181 1391 + 1391 1368 1392 1393 1184 1394 + 1395 1372 1396 1397 1188 1398 + 1398 1375 1399 1400 1191 1401 + 1401 1378 1402 1403 1194 1404 + 1404 1381 1405 1406 1197 1407 + 1407 1384 1408 1409 1200 1410 + 1410 1387 1411 1412 1203 1413 + 1413 1390 1414 1415 1206 1416 + 1416 1393 1417 1418 1209 1419 + 1420 1397 1421 1422 1213 1423 + 1423 1400 1424 1425 1216 1426 + 1426 1403 1427 1428 1219 1429 + 1429 1406 1430 1431 1222 1432 + 1432 1409 1433 1434 1225 1435 + 1435 1412 1436 1437 1228 1438 + 1438 1415 1439 1440 1231 1441 + 1441 1418 1442 1443 1234 1444 + 1445 1422 1446 1447 1238 1448 + 1448 1425 1449 1450 1241 1451 + 1451 1428 1452 1453 1244 1454 + 1454 1431 1455 1456 1247 1457 + 1457 1434 1458 1459 1250 1460 + 1460 1437 1461 1462 1253 1463 + 1463 1440 1464 1465 1256 1466 + 1466 1443 1467 1468 1259 1469 + 1470 1447 1471 1472 1263 1473 + 1473 1450 1474 1475 1266 1476 + 1476 1453 1477 1478 1269 1479 + 1479 1456 1480 1481 1272 1482 + 1482 1459 1483 1484 1275 1485 + 1485 1462 1486 1487 1278 1488 + 1488 1465 1489 1490 1281 1491 + 1491 1468 1492 1493 1284 1494 + 1495 1472 1496 1497 1288 1498 + 1498 1475 1499 1500 1291 1501 + 1501 1478 1502 1503 1294 1504 + 1504 1481 1505 1506 1297 1507 + 1507 1484 1508 1509 1300 1510 + 1510 1487 1511 1512 1303 1513 + 1513 1490 1514 1515 1306 1516 + 1516 1493 1517 1518 1309 1519 + 1520 1521 1522 1523 1314 1524 + 1524 1525 1526 1527 1318 1528 + 1528 1529 1530 1531 1322 1532 + 1532 1533 1534 1535 1326 1536 + 1536 1537 1538 1539 1330 1540 + 1540 1541 1542 1543 1334 1544 + 1544 1545 1546 1547 1338 1548 + 1548 1549 1550 1551 1342 1552 + 1553 1523 1554 1555 1346 1556 + 1556 1527 1557 1558 1349 1559 + 1559 1531 1560 1561 1352 1562 + 1562 1535 1563 1564 1355 1565 + 1565 1539 1566 1567 1358 1568 + 1568 1543 1569 1570 1361 1571 + 1571 1547 1572 1573 1364 1574 + 1574 1551 1575 1576 1367 1577 + 1578 1555 1579 1580 1371 1581 + 1581 1558 1582 1583 1374 1584 + 1584 1561 1585 1586 1377 1587 + 1587 1564 1588 1589 1380 1590 + 1590 1567 1591 1592 1383 1593 + 1593 1570 1594 1595 1386 1596 + 1596 1573 1597 1598 1389 1599 + 1599 1576 1600 1601 1392 1602 + 1603 1580 1604 1605 1396 1606 + 1606 1583 1607 1608 1399 1609 + 1609 1586 1610 1611 1402 1612 + 1612 1589 1613 1614 1405 1615 + 1615 1592 1616 1617 1408 1618 + 1618 1595 1619 1620 1411 1621 + 1621 1598 1622 1623 1414 1624 + 1624 1601 1625 1626 1417 1627 + 1628 1605 1629 1630 1421 1631 + 1631 1608 1632 1633 1424 1634 + 1634 1611 1635 1636 1427 1637 + 1637 1614 1638 1639 1430 1640 + 1640 1617 1641 1642 1433 1643 + 1643 1620 1644 1645 1436 1646 + 1646 1623 1647 1648 1439 1649 + 1649 1626 1650 1651 1442 1652 + 1653 1630 1654 1655 1446 1656 + 1656 1633 1657 1658 1449 1659 + 1659 1636 1660 1661 1452 1662 + 1662 1639 1663 1664 1455 1665 + 1665 1642 1666 1667 1458 1668 + 1668 1645 1669 1670 1461 1671 + 1671 1648 1672 1673 1464 1674 + 1674 1651 1675 1676 1467 1677 + 1678 1655 1679 1680 1471 1681 + 1681 1658 1682 1683 1474 1684 + 1684 1661 1685 1686 1477 1687 + 1687 1664 1688 1689 1480 1690 + 1690 1667 1691 1692 1483 1693 + 1693 1670 1694 1695 1486 1696 + 1696 1673 1697 1698 1489 1699 + 1699 1676 1700 1701 1492 1702 + 1703 1680 1704 1705 1496 1706 + 1706 1683 1707 1708 1499 1709 + 1709 1686 1710 1711 1502 1712 + 1712 1689 1713 1714 1505 1715 + 1715 1692 1716 1717 1508 1718 + 1718 1695 1719 1720 1511 1721 + 1721 1698 1722 1723 1514 1724 + 1724 1701 1725 1726 1517 1727 + + + + H[0-511] + F[1,6,11,16,21,26,31,36,273,277,281,285,289,293,297,301,481,485,489,493,497,501,505,509,689,693,697,701,705,709,713,717,897,901,905,909,913,917,921,925,1105,1109,1113,1117,1121,1125,1129,1133,1313,1317,1321,1325,1329,1333,1337,1341,1521,1525,1529,1533,1537,1541,1545,1549] + F[1705,1708,1711,1714,1717,1720,1723,1726,1497,1500,1503,1506,1509,1512,1515,1518,1289,1292,1295,1298,1301,1304,1307,1310,1081,1084,1087,1090,1093,1096,1099,1102,873,876,879,882,885,888,891,894,665,668,671,674,677,680,683,686,457,460,463,466,469,472,475,478,241,245,249,253,257,261,265,269] + F[1522,1526,1530,1534,1538,1542,1546,1550,1554,1557,1560,1563,1566,1569,1572,1575,1579,1582,1585,1588,1591,1594,1597,1600,1604,1607,1610,1613,1616,1619,1622,1625,1629,1632,1635,1638,1641,1644,1647,1650,1654,1657,1660,1663,1666,1669,1672,1675,1679,1682,1685,1688,1691,1694,1697,1700,1704,1707,1710,1713,1716,1719,1722,1725] + F[242,246,250,254,258,262,266,270,209,213,217,221,225,229,233,237,176,180,184,188,192,196,200,204,143,147,151,155,159,163,167,171,110,114,118,122,126,130,134,138,77,81,85,89,93,97,101,105,44,48,52,56,60,64,68,72,4,9,14,19,24,29,34,39] + F[0,41,74,107,140,173,206,239,272,305,330,355,380,405,430,455,480,513,538,563,588,613,638,663,688,721,746,771,796,821,846,871,896,929,954,979,1004,1029,1054,1079,1104,1137,1162,1187,1212,1237,1262,1287,1312,1345,1370,1395,1420,1445,1470,1495,1520,1553,1578,1603,1628,1653,1678,1703] + F[40,73,106,139,172,205,238,271,304,329,354,379,404,429,454,479,512,537,562,587,612,637,662,687,720,745,770,795,820,845,870,895,928,953,978,1003,1028,1053,1078,1103,1136,1161,1186,1211,1236,1261,1286,1311,1344,1369,1394,1419,1444,1469,1494,1519,1552,1577,1602,1627,1652,1677,1702,1727] + + + C[1] + + + + + + 2e0fb86da236e7e5a3590fcf5e0f608bd8490945 + L0211-XU + 5.3.0 + 21-Jul-2023 15:32:50 + + -v hex_cube_0.3_perturbed.msh hex_cube_0.3_perturbed.tmp.xml:xml:uncompress + + diff --git a/test/test_resources/reference_prism_tet_cube/conditions.xml b/test/test_resources/reference_prism_tet_cube/conditions.xml new file mode 100644 index 00000000..19146825 --- /dev/null +++ b/test/test_resources/reference_prism_tet_cube/conditions.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + u + + + + C[100-113] + + + + + + + + + + + diff --git a/test/test_resources/reference_prism_tet_cube/conditions_cg.xml b/test/test_resources/reference_prism_tet_cube/conditions_cg.xml new file mode 100644 index 00000000..ad939197 --- /dev/null +++ b/test/test_resources/reference_prism_tet_cube/conditions_cg.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + u + + + + C[100-113] + + + + + + + + + + + diff --git a/test/test_resources/reference_prism_tet_cube/conditions_cg_nummodes_4.xml b/test/test_resources/reference_prism_tet_cube/conditions_cg_nummodes_4.xml new file mode 100644 index 00000000..44cd542c --- /dev/null +++ b/test/test_resources/reference_prism_tet_cube/conditions_cg_nummodes_4.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + u + + + + C[100-113] + + + + + + + + + + + diff --git a/test/test_resources/reference_prism_tet_cube/conditions_nummodes_4.xml b/test/test_resources/reference_prism_tet_cube/conditions_nummodes_4.xml new file mode 100644 index 00000000..6591fdfd --- /dev/null +++ b/test/test_resources/reference_prism_tet_cube/conditions_nummodes_4.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + u + + + + C[100-113] + + + + + + + + + + + diff --git a/test/test_resources/reference_prism_tet_cube/prism_tet_cube_0.5_perturbed.xml b/test/test_resources/reference_prism_tet_cube/prism_tet_cube_0.5_perturbed.xml new file mode 100644 index 00000000..d619f926 --- /dev/null +++ b/test/test_resources/reference_prism_tet_cube/prism_tet_cube_0.5_perturbed.xml @@ -0,0 +1,2286 @@ + + + + + 1.38381937e-01 -1.13005454e-02 -1.00000000e+00 + 5.77915356e-01 2.49332517e-01 -1.00000000e+00 + 2.57407788e-01 5.66401986e-01 -1.00000000e+00 + 4.99999861e-01 4.99999700e-01 -3.33333143e-01 + -1.00000000e+00 2.75279799e-12 -1.00000000e+00 + -6.16101923e-01 -2.48737908e-01 -1.00000000e+00 + -6.22548313e-01 2.28912046e-01 -1.00000000e+00 + -1.00000000e+00 -2.50000000e-01 -6.66666667e-01 + 6.04157991e-01 -2.43134354e-01 -1.00000000e+00 + 1.00000000e+00 -2.75279799e-12 -1.00000000e+00 + 1.00000000e+00 2.50000000e-01 -6.66666667e-01 + 1.00000000e+00 -2.50000000e-01 -6.66666667e-01 + 2.71813574e-01 -5.73537566e-01 -1.00000000e+00 + 4.99999391e-01 -5.00000849e-01 -3.33333326e-01 + -1.00000000e+00 2.50000000e-01 -6.66666667e-01 + -5.00000015e-01 9.92427448e-07 -3.33333471e-01 + 2.75279799e-12 1.00000000e+00 -1.00000000e+00 + -2.50000000e-01 1.00000000e+00 -6.66666667e-01 + 2.50000000e-01 1.00000000e+00 -6.66666667e-01 + 5.00000674e-01 2.80717084e-07 -3.33332869e-01 + -2.27984803e-01 -4.95763005e-01 -1.00000000e+00 + -3.11490770e-01 -7.17268168e-03 -1.00000000e+00 + 1.91982143e-08 7.20111544e-07 -3.33332555e-01 + -6.70349812e-01 6.43987610e-01 -1.00000000e+00 + -5.00000415e-01 4.99999939e-01 -3.33333905e-01 + -6.64196631e-01 1.00000000e+00 -6.66666667e-01 + -1.53982730e-07 5.00000400e-01 -3.33334273e-01 + -7.35714666e-07 -4.99999357e-01 -3.33333356e-01 + 2.50000000e-01 -1.00000000e+00 -6.66666667e-01 + -2.50000000e-01 -1.00000000e+00 -6.66666667e-01 + -2.29200747e-01 4.91026004e-01 -1.00000000e+00 + -1.00000000e+00 6.64196631e-01 -6.66666667e-01 + -6.33974596e-01 -6.33974596e-01 -1.00000000e+00 + -5.00000535e-01 -4.99999402e-01 -3.33332903e-01 + 6.33974596e-01 -6.33974596e-01 -1.00000000e+00 + 6.64196631e-01 -1.00000000e+00 -6.66666667e-01 + -1.00000000e+00 -6.64196631e-01 -6.66666667e-01 + 6.33974596e-01 6.33974596e-01 -1.00000000e+00 + 1.00000000e+00 6.64196631e-01 -6.66666667e-01 + 1.00000000e+00 -6.64196631e-01 -6.66666667e-01 + 6.64196631e-01 1.00000000e+00 -6.66666667e-01 + -6.64196631e-01 -1.00000000e+00 -6.66666667e-01 + -1.00000000e+00 -1.00000000e+00 -3.33333333e-01 + -5.00000000e-01 -1.00000000e+00 -3.33333333e-01 + 5.00000000e-01 1.00000000e+00 -3.33333333e-01 + 1.00000000e+00 1.00000000e+00 -3.33333333e-01 + -5.00000000e-01 1.00000000e+00 -1.00000000e+00 + -1.00000000e+00 1.00000000e+00 -6.66666667e-01 + -1.00000000e+00 1.00000000e+00 -1.00000000e+00 + 1.00000000e+00 1.00000000e+00 -6.66666667e-01 + -1.00000000e+00 -1.00000000e+00 -6.66666667e-01 + 1.00000000e+00 5.00000000e-01 -1.00000000e+00 + 5.00000000e-01 -1.00000000e+00 -1.00000000e+00 + -1.00000000e+00 -5.00000000e-01 -1.00000000e+00 + 1.00000000e+00 -5.00000000e-01 -1.00000000e+00 + 5.00000000e-01 1.00000000e+00 -1.00000000e+00 + -1.00000000e+00 5.00000000e-01 -1.00000000e+00 + 1.00000000e+00 1.00000000e+00 -1.00000000e+00 + -1.00000000e+00 -1.00000000e+00 -1.00000000e+00 + 1.00000000e+00 -1.00000000e+00 -6.66666667e-01 + 1.00000000e+00 -1.00000000e+00 -1.00000000e+00 + -5.00000000e-01 -1.00000000e+00 -1.00000000e+00 + 5.00000000e-01 -1.00000000e+00 -3.33333333e-01 + 1.00000000e+00 -1.00000000e+00 -3.33333333e-01 + -1.00000000e+00 5.00000000e-01 -3.33333333e-01 + -1.00000000e+00 1.00000000e+00 -3.33333333e-01 + -1.00000000e+00 -5.00000000e-01 -3.33333333e-01 + 1.00000000e+00 5.00000000e-01 -3.33333333e-01 + 2.75279799e-12 1.00000000e+00 -3.33333333e-01 + -2.75279799e-12 -1.00000000e+00 -3.33333333e-01 + -1.00000000e+00 2.75279799e-12 -3.33333333e-01 + 1.00000000e+00 -2.75279799e-12 -3.33333333e-01 + -2.75279799e-12 -1.00000000e+00 -1.00000000e+00 + -5.00000000e-01 1.00000000e+00 -3.33333333e-01 + 1.00000000e+00 -5.00000000e-01 -3.33333333e-01 + -1.00000000e+00 -1.00000000e+00 -9.21984711e-13 + -5.00000000e-01 -1.00000000e+00 -9.21929200e-13 + -1.00000000e+00 -5.00000000e-01 -9.21984711e-13 + -4.99999045e-01 -5.00000822e-01 8.50768634e-07 + -1.00000000e+00 -1.00000000e+00 3.33333333e-01 + -5.00000000e-01 -1.00000000e+00 3.33333333e-01 + -1.00000000e+00 -5.00000000e-01 3.33333333e-01 + -5.00000557e-01 -5.00000067e-01 3.33332473e-01 + -1.00000000e+00 2.75277023e-12 -9.21984711e-13 + -4.99999722e-01 -4.97534342e-07 -2.52133842e-07 + -1.00000000e+00 2.75279799e-12 3.33333333e-01 + -5.00000820e-01 3.05193355e-07 3.33332505e-01 + -1.00000000e+00 5.00000000e-01 -9.21929200e-13 + -4.99999391e-01 4.99999171e-01 8.29253103e-07 + -1.00000000e+00 5.00000000e-01 3.33333333e-01 + -5.00000367e-01 4.99999432e-01 3.33334245e-01 + -1.00000000e+00 1.00000000e+00 -9.21984711e-13 + -5.00000000e-01 1.00000000e+00 -9.21984711e-13 + -1.00000000e+00 1.00000000e+00 3.33333333e-01 + -5.00000000e-01 1.00000000e+00 3.33333333e-01 + -2.75277023e-12 -1.00000000e+00 -9.21984711e-13 + -3.61281773e-07 -5.00000152e-01 -7.40621103e-07 + -2.75279799e-12 -1.00000000e+00 3.33333333e-01 + -1.39615591e-08 -5.00000723e-01 3.33333895e-01 + 5.25623824e-07 -3.32760277e-07 5.86675383e-07 + 4.23628128e-07 4.31445581e-07 3.33334293e-01 + -5.30654932e-07 4.99999340e-01 7.79310659e-07 + -5.25054783e-07 4.99999363e-01 3.33332860e-01 + 2.75277023e-12 1.00000000e+00 -9.21984711e-13 + 2.75279799e-12 1.00000000e+00 3.33333333e-01 + 5.00000000e-01 -1.00000000e+00 -9.21984711e-13 + 4.99999859e-01 -5.00000986e-01 -3.71829174e-07 + 5.00000000e-01 -1.00000000e+00 3.33333333e-01 + 5.00000872e-01 -5.00000199e-01 3.33333374e-01 + 4.99999335e-01 4.96798985e-07 8.77336198e-07 + 4.99999591e-01 -1.36040842e-07 3.33333483e-01 + 4.99999293e-01 5.00000207e-01 7.17475775e-07 + 5.00000863e-01 5.00000407e-01 3.33334243e-01 + 5.00000000e-01 1.00000000e+00 -9.21929200e-13 + 5.00000000e-01 1.00000000e+00 3.33333333e-01 + 1.00000000e+00 -1.00000000e+00 -9.21984711e-13 + 1.00000000e+00 -5.00000000e-01 -9.21929200e-13 + 1.00000000e+00 -1.00000000e+00 3.33333333e-01 + 1.00000000e+00 -5.00000000e-01 3.33333333e-01 + 1.00000000e+00 -2.75277023e-12 -9.21984711e-13 + 1.00000000e+00 -2.75279799e-12 3.33333333e-01 + 1.00000000e+00 5.00000000e-01 -9.21984711e-13 + 1.00000000e+00 5.00000000e-01 3.33333333e-01 + 1.00000000e+00 1.00000000e+00 -9.21984711e-13 + 1.00000000e+00 1.00000000e+00 3.33333333e-01 + 5.77915356e-01 2.49332517e-01 1.00000000e+00 + 1.38381937e-01 -1.13005454e-02 1.00000000e+00 + 2.57407788e-01 5.66401986e-01 1.00000000e+00 + -6.16101923e-01 -2.48737908e-01 1.00000000e+00 + -1.00000000e+00 2.75279799e-12 1.00000000e+00 + -6.22548313e-01 2.28912046e-01 1.00000000e+00 + -1.00000000e+00 -2.50000000e-01 6.66666667e-01 + -2.29200747e-01 4.91026004e-01 1.00000000e+00 + -3.11490770e-01 -7.17268168e-03 1.00000000e+00 + 1.00000000e+00 -2.75279799e-12 1.00000000e+00 + 6.04157991e-01 -2.43134354e-01 1.00000000e+00 + 1.00000000e+00 2.50000000e-01 6.66666667e-01 + 1.00000000e+00 -2.50000000e-01 6.66666667e-01 + 2.71813574e-01 -5.73537566e-01 1.00000000e+00 + -1.00000000e+00 2.50000000e-01 6.66666667e-01 + 2.75279799e-12 1.00000000e+00 1.00000000e+00 + -2.50000000e-01 1.00000000e+00 6.66666667e-01 + 2.50000000e-01 1.00000000e+00 6.66666667e-01 + -2.27984803e-01 -4.95763005e-01 1.00000000e+00 + -6.70349812e-01 6.43987610e-01 1.00000000e+00 + -6.64196631e-01 1.00000000e+00 6.66666667e-01 + 2.50000000e-01 -1.00000000e+00 6.66666667e-01 + -2.50000000e-01 -1.00000000e+00 6.66666667e-01 + -1.00000000e+00 6.64196631e-01 6.66666667e-01 + -6.33974596e-01 -6.33974596e-01 1.00000000e+00 + 6.33974596e-01 -6.33974596e-01 1.00000000e+00 + 6.64196631e-01 -1.00000000e+00 6.66666667e-01 + -1.00000000e+00 -6.64196631e-01 6.66666667e-01 + 6.33974596e-01 6.33974596e-01 1.00000000e+00 + 1.00000000e+00 6.64196631e-01 6.66666667e-01 + 1.00000000e+00 -6.64196631e-01 6.66666667e-01 + -6.64196631e-01 -1.00000000e+00 6.66666667e-01 + 6.64196631e-01 1.00000000e+00 6.66666667e-01 + -5.00000000e-01 1.00000000e+00 1.00000000e+00 + -1.00000000e+00 -1.00000000e+00 6.66666667e-01 + 1.00000000e+00 1.00000000e+00 6.66666667e-01 + -1.00000000e+00 1.00000000e+00 1.00000000e+00 + -1.00000000e+00 1.00000000e+00 6.66666667e-01 + 1.00000000e+00 5.00000000e-01 1.00000000e+00 + -1.00000000e+00 -5.00000000e-01 1.00000000e+00 + 5.00000000e-01 -1.00000000e+00 1.00000000e+00 + 5.00000000e-01 1.00000000e+00 1.00000000e+00 + 1.00000000e+00 -5.00000000e-01 1.00000000e+00 + -1.00000000e+00 5.00000000e-01 1.00000000e+00 + 1.00000000e+00 -1.00000000e+00 1.00000000e+00 + 1.00000000e+00 -1.00000000e+00 6.66666667e-01 + -1.00000000e+00 -1.00000000e+00 1.00000000e+00 + 1.00000000e+00 1.00000000e+00 1.00000000e+00 + -5.00000000e-01 -1.00000000e+00 1.00000000e+00 + -2.75279799e-12 -1.00000000e+00 1.00000000e+00 + + + 1 0 + 2 1 + 2 0 + 3 0 + 3 1 + 3 2 + 5 4 + 5 6 + 4 6 + 7 4 + 7 5 + 7 6 + 8 9 + 9 10 + 8 10 + 8 11 + 9 11 + 10 11 + 8 12 + 8 0 + 0 12 + 13 12 + 8 13 + 13 0 + 14 6 + 14 4 + 14 7 + 9 1 + 1 8 + 10 1 + 5 15 + 6 15 + 7 15 + 16 2 + 16 17 + 17 2 + 18 2 + 16 18 + 18 17 + 14 15 + 8 19 + 10 19 + 11 19 + 19 0 + 19 1 + 19 3 + 21 20 + 21 15 + 20 15 + 22 20 + 22 21 + 22 15 + 23 24 + 17 24 + 23 17 + 25 23 + 25 24 + 25 17 + 26 2 + 26 17 + 18 26 + 20 27 + 20 28 + 27 28 + 29 27 + 29 20 + 29 28 + 30 21 + 30 15 + 30 22 + 23 6 + 6 24 + 23 14 + 14 24 + 20 0 + 21 0 + 22 0 + 13 19 + 26 0 + 3 26 + 30 0 + 31 24 + 23 31 + 14 31 + 5 21 + 6 21 + 0 27 + 27 12 + 13 27 + 32 5 + 33 32 + 33 5 + 7 32 + 7 33 + 13 34 + 28 13 + 28 34 + 35 34 + 35 13 + 35 28 + 32 36 + 36 33 + 36 7 + 37 3 + 3 10 + 37 10 + 37 38 + 38 3 + 38 10 + 11 34 + 13 11 + 39 13 + 39 34 + 39 11 + 37 18 + 3 18 + 3 40 + 40 37 + 40 18 + 29 32 + 29 33 + 41 33 + 32 41 + 29 41 + 30 16 + 30 2 + 17 30 + 12 20 + 12 28 + 1 37 + 26 30 + 8 34 + 30 23 + 30 24 + 25 31 + 22 26 + 19 22 + 19 26 + 42 43 + 41 43 + 41 42 + 36 42 + 36 43 + 41 36 + 44 45 + 38 45 + 44 38 + 44 40 + 40 45 + 38 40 + 6 30 + 32 20 + 20 33 + 22 27 + 13 22 + 39 35 + 27 15 + 33 15 + 33 27 + 12 34 + 37 2 + 24 22 + 24 26 + 24 15 + 20 5 + 23 46 + 17 46 + 46 25 + 47 48 + 48 25 + 47 25 + 47 31 + 31 48 + 45 49 + 49 38 + 40 49 + 42 50 + 41 50 + 36 50 + 37 51 + 10 51 + 38 51 + 52 34 + 52 28 + 52 35 + 32 53 + 7 53 + 36 53 + 54 34 + 11 54 + 39 54 + 55 37 + 55 18 + 55 40 + 56 23 + 56 14 + 56 31 + 49 57 + 57 38 + 40 57 + 58 50 + 41 58 + 36 58 + 59 60 + 35 60 + 35 59 + 59 39 + 60 39 + 56 6 + 53 5 + 1 51 + 8 54 + 46 30 + 61 32 + 61 29 + 41 61 + 62 63 + 35 63 + 35 62 + 62 39 + 63 39 + 13 62 + 24 64 + 25 64 + 31 64 + 65 64 + 65 25 + 65 31 + 43 33 + 44 3 + 52 12 + 42 66 + 43 66 + 36 66 + 67 45 + 44 67 + 67 38 + 55 2 + 23 48 + 57 37 + 60 34 + 58 32 + 61 20 + 68 26 + 68 17 + 68 18 + 69 27 + 69 28 + 69 29 + 15 70 + 7 70 + 14 70 + 71 19 + 71 10 + 71 11 + 51 9 + 63 59 + 65 47 + 72 61 + 72 20 + 72 29 + 46 16 + 57 55 + 54 60 + 57 51 + 60 52 + 58 53 + 53 4 + 62 28 + 73 24 + 73 17 + 73 25 + 74 13 + 11 74 + 74 39 + 14 64 + 66 33 + 66 7 + 67 3 + 67 10 + 43 29 + 44 18 + 4 56 + 48 56 + 9 54 + 13 63 + 74 63 + 65 73 + 65 24 + 52 72 + 12 72 + 72 28 + 55 16 + 46 48 + 58 61 + 70 66 + 33 70 + 67 71 + 3 71 + 73 68 + 73 26 + 69 62 + 27 62 + 19 74 + 64 15 + 64 70 + 74 71 + 69 33 + 68 3 + 43 69 + 68 44 + 75 76 + 76 43 + 75 42 + 75 77 + 76 77 + 77 66 + 78 77 + 78 76 + 78 33 + 79 80 + 80 76 + 79 75 + 79 81 + 80 81 + 81 77 + 82 81 + 82 80 + 82 78 + 77 83 + 83 70 + 78 83 + 84 83 + 84 78 + 84 15 + 81 85 + 85 83 + 82 85 + 86 85 + 86 82 + 86 84 + 83 87 + 87 64 + 84 87 + 88 87 + 88 84 + 88 24 + 85 89 + 89 87 + 86 89 + 90 89 + 90 86 + 90 88 + 87 91 + 91 65 + 88 91 + 92 91 + 92 73 + 92 88 + 89 93 + 93 91 + 90 93 + 94 93 + 94 92 + 94 90 + 76 95 + 95 69 + 95 78 + 96 78 + 96 95 + 96 27 + 80 97 + 97 95 + 97 82 + 98 82 + 98 97 + 98 96 + 96 84 + 99 84 + 99 96 + 99 22 + 98 86 + 100 86 + 100 98 + 100 99 + 99 88 + 101 88 + 101 99 + 101 26 + 100 90 + 102 90 + 102 100 + 102 101 + 101 92 + 103 92 + 103 68 + 103 101 + 102 94 + 104 94 + 104 103 + 104 102 + 95 105 + 105 62 + 105 96 + 106 96 + 106 105 + 106 13 + 97 107 + 107 105 + 107 98 + 108 98 + 108 107 + 108 106 + 106 99 + 109 99 + 109 106 + 109 19 + 108 100 + 110 100 + 110 108 + 110 109 + 109 101 + 111 101 + 111 109 + 111 3 + 110 102 + 112 102 + 112 110 + 112 111 + 111 103 + 113 103 + 113 44 + 113 111 + 112 104 + 114 104 + 114 113 + 114 112 + 105 115 + 115 63 + 115 106 + 116 115 + 116 74 + 116 106 + 107 117 + 117 115 + 117 108 + 118 117 + 118 116 + 118 108 + 116 109 + 119 116 + 119 71 + 119 109 + 118 110 + 120 118 + 120 119 + 120 110 + 119 111 + 121 119 + 121 67 + 121 111 + 120 112 + 122 120 + 122 121 + 122 112 + 121 113 + 123 121 + 123 45 + 123 113 + 122 114 + 124 122 + 124 123 + 124 114 + 125 112 + 126 112 + 125 126 + 127 125 + 127 112 + 127 126 + 129 128 + 130 129 + 130 128 + 131 128 + 129 131 + 130 131 + 90 132 + 132 130 + 90 130 + 90 133 + 132 133 + 130 133 + 134 135 + 135 136 + 134 136 + 134 137 + 137 135 + 137 136 + 108 138 + 138 135 + 108 135 + 108 126 + 126 138 + 126 135 + 129 139 + 139 130 + 139 131 + 134 125 + 135 125 + 136 125 + 86 128 + 86 130 + 131 86 + 140 127 + 127 141 + 140 141 + 140 142 + 127 142 + 141 142 + 86 139 + 110 135 + 110 136 + 110 137 + 125 110 + 110 126 + 86 133 + 86 143 + 100 143 + 100 133 + 133 143 + 144 90 + 144 141 + 90 141 + 90 145 + 144 145 + 145 141 + 127 102 + 141 102 + 142 102 + 98 143 + 98 146 + 143 146 + 147 143 + 147 98 + 147 146 + 130 144 + 90 139 + 144 139 + 100 126 + 143 126 + 133 126 + 102 126 + 100 132 + 132 126 + 144 148 + 148 90 + 148 139 + 128 133 + 98 138 + 98 126 + 128 82 + 149 82 + 128 149 + 131 82 + 131 149 + 108 150 + 146 150 + 108 146 + 151 108 + 151 150 + 151 146 + 152 82 + 149 152 + 131 152 + 153 112 + 136 153 + 136 112 + 154 112 + 153 154 + 136 154 + 108 137 + 150 137 + 150 155 + 155 108 + 155 137 + 82 147 + 149 147 + 149 156 + 156 82 + 156 147 + 142 112 + 153 142 + 157 153 + 157 112 + 157 142 + 132 140 + 132 127 + 141 132 + 138 143 + 138 146 + 125 153 + 132 102 + 135 150 + 144 132 + 145 148 + 114 154 + 154 124 + 124 157 + 114 157 + 154 157 + 156 79 + 156 80 + 152 80 + 152 79 + 152 156 + 82 143 + 149 143 + 151 155 + 138 150 + 127 153 + 128 143 + 158 144 + 158 141 + 158 145 + 79 159 + 159 156 + 159 152 + 124 160 + 154 160 + 160 157 + 162 161 + 162 145 + 145 161 + 161 148 + 162 148 + 163 153 + 163 136 + 163 154 + 149 164 + 164 131 + 164 152 + 165 150 + 146 165 + 151 165 + 166 153 + 142 166 + 166 157 + 150 167 + 167 137 + 155 167 + 144 168 + 139 168 + 148 168 + 169 170 + 151 170 + 151 169 + 155 169 + 155 170 + 159 171 + 156 171 + 171 152 + 160 172 + 154 172 + 172 157 + 130 168 + 128 164 + 163 125 + 135 167 + 158 132 + 149 173 + 147 173 + 156 173 + 89 145 + 89 148 + 118 151 + 118 155 + 145 93 + 93 148 + 117 151 + 155 117 + 138 165 + 81 152 + 122 154 + 127 166 + 161 144 + 171 149 + 153 172 + 169 150 + 173 143 + 97 146 + 147 97 + 104 141 + 104 142 + 85 131 + 139 85 + 120 136 + 137 120 + 134 163 + 93 162 + 117 170 + 173 174 + 174 143 + 174 147 + 158 140 + 169 167 + 166 172 + 172 163 + 171 164 + 165 169 + 129 164 + 146 107 + 107 151 + 94 141 + 94 145 + 118 137 + 89 139 + 81 131 + 136 122 + 114 142 + 80 147 + 168 129 + 161 168 + 167 134 + 174 165 + 174 138 + 174 146 + 140 166 + 161 158 + 171 173 + + + 0 1 2 + 0 4 3 + 1 5 4 + 2 5 3 + 6 7 8 + 6 10 9 + 7 11 10 + 8 11 9 + 12 13 14 + 12 16 15 + 13 17 16 + 14 17 15 + 18 19 20 + 18 22 21 + 19 23 22 + 20 23 21 + 8 25 24 + 9 26 25 + 11 26 24 + 27 28 12 + 27 29 13 + 28 14 29 + 7 30 31 + 30 32 10 + 31 32 11 + 33 34 35 + 33 37 36 + 34 38 37 + 35 38 36 + 31 24 39 + 32 26 39 + 40 14 41 + 40 15 42 + 41 17 42 + 0 43 44 + 43 45 3 + 44 45 4 + 28 44 40 + 44 41 29 + 46 47 48 + 46 50 49 + 47 51 50 + 48 51 49 + 52 53 54 + 52 56 55 + 53 57 56 + 54 57 55 + 58 35 59 + 58 36 60 + 59 38 60 + 61 62 63 + 61 65 64 + 62 66 65 + 63 66 64 + 67 68 47 + 67 69 50 + 68 51 69 + 70 71 52 + 70 24 72 + 71 73 24 + 52 73 72 + 74 75 46 + 74 76 49 + 75 50 76 + 19 43 40 + 23 77 43 + 22 77 40 + 2 58 78 + 58 79 5 + 78 79 3 + 80 67 75 + 80 69 76 + 52 82 81 + 72 83 82 + 73 83 81 + 7 84 85 + 84 47 30 + 85 47 31 + 20 86 87 + 86 88 23 + 87 88 21 + 89 90 91 + 89 92 10 + 90 93 92 + 91 93 10 + 94 95 96 + 94 98 97 + 95 99 98 + 96 99 97 + 90 101 100 + 93 102 101 + 92 102 100 + 103 104 105 + 103 107 106 + 104 108 107 + 105 108 106 + 94 109 110 + 94 112 111 + 109 113 112 + 110 113 111 + 103 114 115 + 103 117 116 + 114 118 117 + 115 118 116 + 90 119 120 + 90 122 121 + 119 123 122 + 120 123 121 + 124 125 33 + 124 126 34 + 125 35 126 + 127 87 61 + 127 128 62 + 87 63 128 + 129 103 4 + 129 105 29 + 4 104 29 + 80 2 125 + 80 78 130 + 125 58 130 + 131 22 94 + 131 15 109 + 22 110 15 + 20 74 127 + 74 61 86 + 132 52 133 + 132 54 126 + 133 53 126 + 56 134 81 + 55 134 82 + 28 0 19 + 130 59 126 + 76 78 135 + 76 43 136 + 78 137 43 + 135 137 136 + 137 45 79 + 138 139 140 + 138 142 141 + 139 143 142 + 140 143 141 + 144 145 146 + 144 148 147 + 145 149 148 + 146 149 147 + 150 132 70 + 150 133 71 + 151 152 90 + 151 65 119 + 152 120 65 + 86 76 153 + 76 154 23 + 153 154 88 + 154 77 136 + 98 155 111 + 97 155 112 + 107 149 116 + 106 149 117 + 122 143 100 + 121 143 101 + 48 156 61 + 156 153 51 + 61 153 49 + 152 48 157 + 152 61 158 + 157 156 158 + 159 94 21 + 159 96 128 + 21 95 128 + 160 5 103 + 160 36 114 + 5 115 36 + 133 161 69 + 133 162 130 + 161 135 162 + 69 135 130 + 68 133 163 + 163 161 51 + 164 89 151 + 164 91 152 + 165 54 166 + 165 55 167 + 166 57 167 + 31 163 71 + 163 73 39 + 18 159 131 + 91 157 30 + 157 32 93 + 168 169 170 + 168 172 171 + 169 134 172 + 170 134 171 + 173 174 145 + 173 175 148 + 174 149 175 + 176 140 177 + 176 141 178 + 177 143 178 + 1 129 160 + 179 105 180 + 179 106 181 + 180 108 181 + 182 96 183 + 182 97 184 + 183 99 184 + 185 92 186 + 185 100 187 + 186 102 187 + 188 189 109 + 188 190 112 + 189 113 190 + 191 192 114 + 191 193 117 + 192 118 193 + 194 195 72 + 194 196 82 + 195 83 196 + 197 198 174 + 197 199 175 + 198 149 199 + 200 177 201 + 200 178 202 + 201 143 202 + 203 204 205 + 203 207 206 + 204 155 207 + 205 155 206 + 45 104 41 + 208 70 194 + 208 24 195 + 209 185 89 + 209 186 10 + 77 42 110 + 210 179 129 + 210 180 29 + 211 131 188 + 211 15 189 + 158 64 120 + 88 95 63 + 212 165 132 + 212 166 126 + 162 59 53 + 79 115 60 + 213 214 119 + 213 215 122 + 214 123 215 + 216 217 218 + 216 220 219 + 217 155 220 + 218 155 219 + 221 218 98 + 221 219 111 + 222 223 56 + 222 224 81 + 223 134 224 + 225 226 223 + 225 227 224 + 226 134 227 + 228 121 139 + 228 101 142 + 229 146 107 + 229 147 116 + 230 182 159 + 230 183 128 + 138 231 232 + 231 233 141 + 232 233 142 + 234 235 144 + 234 236 145 + 235 146 236 + 237 160 191 + 237 36 192 + 238 55 169 + 238 82 172 + 239 106 198 + 239 117 199 + 240 97 204 + 240 112 207 + 241 201 122 + 241 202 100 + 242 151 213 + 242 65 214 + 243 59 244 + 243 60 245 + 244 38 245 + 246 63 247 + 246 64 248 + 247 66 248 + 249 32 250 + 249 39 251 + 250 26 251 + 252 41 253 + 252 42 254 + 253 17 254 + 255 210 27 + 255 180 13 + 256 205 217 + 256 206 220 + 257 170 226 + 257 171 227 + 258 259 242 + 258 260 214 + 259 65 260 + 261 212 124 + 261 166 34 + 262 191 239 + 262 193 199 + 263 188 240 + 263 190 207 + 264 239 179 + 264 198 181 + 265 240 182 + 265 204 184 + 266 241 185 + 266 202 187 + 267 209 6 + 267 186 9 + 221 268 95 + 268 99 218 + 269 270 53 + 269 271 56 + 270 57 271 + 272 110 273 + 272 111 274 + 273 113 274 + 222 73 275 + 275 83 224 + 276 277 93 + 276 233 101 + 277 102 233 + 278 279 104 + 278 236 107 + 279 108 236 + 228 120 280 + 280 123 139 + 229 115 281 + 281 118 147 + 282 8 208 + 282 25 195 + 283 194 238 + 283 196 172 + 284 12 211 + 284 16 189 + 216 221 285 + 285 111 220 + 286 285 272 + 286 220 274 + 287 269 288 + 287 271 226 + 288 56 226 + 225 288 222 + 232 276 228 + 235 278 229 + 289 230 290 + 289 183 291 + 290 128 291 + 292 33 237 + 292 37 192 + 293 238 165 + 293 169 167 + 294 213 241 + 294 215 201 + 295 296 276 + 295 250 277 + 296 93 250 + 297 298 278 + 297 253 279 + 298 104 253 + 299 243 300 + 299 244 270 + 300 59 270 + 301 246 302 + 301 247 268 + 302 63 268 + 272 77 303 + 303 42 273 + 304 222 163 + 304 275 39 + 252 45 298 + 269 300 162 + 302 88 221 + 296 249 157 + 305 304 249 + 305 275 251 + 306 303 252 + 306 273 254 + 307 158 246 + 307 120 248 + 243 308 79 + 308 115 245 + 309 228 307 + 309 280 248 + 310 229 308 + 310 281 245 + 259 291 62 + 291 66 260 + 290 127 259 + 164 30 48 + 164 46 84 + 150 85 67 + 150 31 68 + 311 315 314 + 315 318 317 + 320 324 323 + 324 327 326 + 329 317 331 + 331 333 332 + 335 326 337 + 337 339 338 + 341 332 343 + 343 345 344 + 347 338 349 + 349 351 350 + 353 344 355 + 356 355 358 + 359 350 361 + 362 361 364 + 365 367 318 + 367 369 368 + 371 373 327 + 373 375 374 + 333 368 377 + 377 379 378 + 339 374 381 + 381 383 382 + 345 378 385 + 385 387 386 + 351 382 389 + 389 391 390 + 358 386 393 + 394 393 396 + 364 390 397 + 398 397 400 + 401 403 369 + 403 405 404 + 407 409 375 + 409 411 410 + 379 404 413 + 413 415 414 + 383 410 417 + 417 419 418 + 387 414 421 + 421 423 422 + 391 418 425 + 425 427 426 + 396 422 429 + 430 429 432 + 400 426 433 + 434 433 436 + 437 439 405 + 440 442 439 + 443 445 411 + 446 448 445 + 442 449 415 + 450 452 449 + 448 453 419 + 454 456 453 + 452 457 423 + 458 460 457 + 456 461 427 + 462 464 461 + 465 432 460 + 466 468 465 + 469 436 464 + 470 472 469 + 473 474 475 + 473 477 476 + 474 478 477 + 475 478 476 + 479 480 481 + 479 483 482 + 480 484 483 + 481 484 482 + 485 486 487 + 485 489 488 + 486 490 489 + 487 490 488 + 491 492 493 + 491 495 494 + 492 496 495 + 493 496 494 + 497 498 499 + 497 501 500 + 498 502 501 + 499 502 500 + 480 504 503 + 484 505 504 + 483 505 503 + 506 491 507 + 506 493 508 + 507 492 508 + 509 481 510 + 509 482 511 + 510 484 511 + 512 513 514 + 512 516 515 + 513 517 516 + 514 517 515 + 510 518 504 + 511 505 518 + 519 520 492 + 519 521 495 + 520 496 521 + 427 522 473 + 427 523 474 + 522 475 523 + 351 487 510 + 351 488 524 + 510 490 524 + 522 507 519 + 522 508 520 + 382 525 526 + 382 524 527 + 525 528 524 + 526 528 527 + 529 530 531 + 529 533 532 + 530 534 533 + 531 534 532 + 535 536 513 + 535 537 516 + 536 517 537 + 538 539 540 + 538 542 541 + 539 543 542 + 540 543 541 + 487 544 529 + 487 504 545 + 544 546 504 + 529 546 545 + 547 526 548 + 547 527 549 + 548 528 549 + 419 499 519 + 419 500 523 + 519 502 523 + 426 550 474 + 426 535 477 + 550 478 535 + 551 547 552 + 551 527 489 + 552 549 489 + 529 554 553 + 545 555 554 + 546 555 553 + 509 524 556 + 481 490 556 + 410 557 497 + 410 558 500 + 557 501 558 + 559 560 561 + 559 562 482 + 560 563 562 + 561 563 482 + 564 565 566 + 564 568 567 + 565 569 568 + 566 569 567 + 560 571 570 + 563 572 571 + 562 572 570 + 573 574 575 + 573 577 576 + 574 578 577 + 575 578 576 + 564 579 580 + 564 582 581 + 579 583 582 + 580 583 581 + 560 584 585 + 560 587 586 + 584 588 587 + 585 588 586 + 573 589 590 + 573 592 591 + 589 593 592 + 590 593 591 + 594 512 595 + 594 514 596 + 595 513 596 + 557 597 538 + 557 598 539 + 597 540 598 + 473 573 599 + 473 575 508 + 599 574 508 + 600 552 550 + 600 595 535 + 552 478 595 + 499 601 564 + 499 495 579 + 601 580 495 + 501 548 597 + 558 548 538 + 485 529 602 + 485 531 596 + 602 530 596 + 533 603 553 + 532 603 554 + 507 502 475 + 600 596 536 + 391 547 550 + 418 523 547 + 425 523 550 + 472 604 605 + 472 607 606 + 604 608 607 + 605 608 606 + 320 609 610 + 320 612 611 + 609 613 612 + 610 613 611 + 486 544 602 + 614 615 560 + 614 541 584 + 615 585 541 + 383 558 547 + 417 500 547 + 568 616 581 + 567 616 582 + 587 613 570 + 586 613 571 + 577 608 591 + 576 608 592 + 381 538 525 + 383 526 538 + 339 614 525 + 374 538 614 + 497 564 617 + 497 566 598 + 617 565 598 + 477 618 573 + 477 516 589 + 618 590 516 + 389 551 485 + 391 600 551 + 390 600 485 + 559 614 619 + 619 615 561 + 620 621 530 + 620 622 533 + 621 534 622 + 351 518 545 + 498 601 617 + 339 559 509 + 339 562 511 + 623 624 609 + 623 625 612 + 624 613 625 + 626 605 627 + 626 606 628 + 627 608 628 + 629 630 631 + 629 633 632 + 630 603 633 + 631 603 632 + 476 618 599 + 634 635 574 + 634 636 577 + 635 578 636 + 637 638 563 + 637 639 571 + 638 572 639 + 640 641 565 + 640 642 568 + 641 569 642 + 643 590 644 + 643 591 645 + 644 593 645 + 646 580 647 + 646 581 648 + 647 583 648 + 649 546 650 + 649 553 651 + 650 555 651 + 652 653 654 + 652 656 655 + 653 616 656 + 654 616 655 + 657 658 624 + 657 659 625 + 658 613 659 + 660 627 661 + 660 628 662 + 661 608 662 + 427 575 520 + 663 649 544 + 663 650 504 + 664 561 637 + 664 482 638 + 419 521 579 + 665 599 634 + 665 508 635 + 666 646 601 + 666 647 495 + 374 542 584 + 410 566 539 + 667 602 620 + 667 596 621 + 390 531 536 + 426 537 589 + 668 585 669 + 668 586 670 + 669 588 670 + 350 532 671 + 350 554 672 + 671 603 672 + 448 567 673 + 448 582 674 + 673 616 674 + 359 671 675 + 359 672 676 + 675 603 676 + 446 673 677 + 446 674 678 + 677 616 678 + 436 576 604 + 436 592 607 + 327 610 587 + 327 611 570 + 679 617 640 + 679 598 641 + 324 680 611 + 323 680 612 + 470 605 681 + 469 604 681 + 682 643 618 + 682 644 516 + 683 631 533 + 683 632 553 + 684 586 658 + 684 571 659 + 685 661 577 + 685 662 591 + 686 654 568 + 686 655 581 + 687 668 615 + 687 669 541 + 375 688 539 + 375 689 542 + 688 543 689 + 400 690 536 + 400 691 537 + 690 517 691 + 338 692 511 + 338 693 518 + 692 505 693 + 456 694 520 + 456 695 521 + 694 496 695 + 696 506 665 + 696 493 635 + 697 675 630 + 697 676 633 + 698 677 653 + 698 678 656 + 699 687 700 + 699 669 701 + 700 541 701 + 702 594 667 + 702 514 621 + 703 686 646 + 703 655 648 + 704 685 643 + 704 662 645 + 705 634 685 + 705 636 661 + 706 637 684 + 706 639 659 + 707 640 686 + 707 642 654 + 708 479 664 + 708 483 638 + 411 566 709 + 411 567 710 + 709 569 710 + 364 531 711 + 364 532 712 + 711 534 712 + 448 713 579 + 713 583 674 + 350 714 545 + 714 555 672 + 326 562 715 + 326 570 680 + 715 572 680 + 464 575 716 + 464 576 681 + 716 578 681 + 436 717 589 + 717 593 607 + 327 718 584 + 718 588 610 + 719 663 480 + 719 650 503 + 720 683 649 + 720 632 651 + 721 666 491 + 721 647 494 + 443 677 710 + 445 567 677 + 362 675 712 + 361 532 675 + 722 723 679 + 722 724 641 + 723 598 724 + 725 682 512 + 725 644 515 + 726 620 683 + 726 622 631 + 727 684 668 + 727 658 670 + 462 716 694 + 461 575 694 + 335 715 692 + 337 562 692 + 407 709 688 + 409 539 709 + 398 711 690 + 397 536 711 + 453 521 713 + 349 518 714 + 347 693 714 + 454 695 713 + 373 689 584 + 433 589 691 + 434 691 717 + 371 689 718 + 723 700 597 + 700 540 724 + 724 543 701 + 509 619 525 + 619 528 556 + 389 527 488 + 311 312 138 313 + 312 232 316 315 + 313 231 316 314 + 312 228 319 318 + 316 276 319 317 + 320 321 311 322 + 321 315 325 324 + 322 314 325 323 + 321 318 328 327 + 325 317 328 326 + 329 316 295 330 + 330 296 319 331 + 319 157 334 333 + 330 249 334 332 + 335 325 329 336 + 336 331 328 337 + 328 333 340 339 + 336 332 340 338 + 341 330 305 342 + 342 304 334 343 + 334 163 346 345 + 342 222 346 344 + 347 336 341 348 + 348 343 340 349 + 340 345 352 351 + 348 344 352 350 + 353 342 225 354 + 354 288 346 355 + 356 354 287 357 + 357 269 346 358 + 359 348 353 360 + 360 355 352 361 + 362 360 356 363 + 363 358 352 364 + 365 366 309 312 + 366 307 319 367 + 366 246 370 369 + 319 158 370 368 + 371 372 365 321 + 372 367 328 373 + 372 369 376 375 + 328 368 376 374 + 334 156 370 377 + 370 153 380 379 + 334 51 380 378 + 340 377 376 381 + 376 379 384 383 + 340 378 384 382 + 346 161 380 385 + 380 135 388 387 + 346 162 388 386 + 352 385 384 389 + 384 387 392 391 + 352 386 392 390 + 357 300 388 393 + 394 357 299 395 + 395 243 388 396 + 363 393 392 397 + 398 363 394 399 + 399 396 392 400 + 401 402 301 366 + 402 302 370 403 + 402 221 406 405 + 370 88 406 404 + 407 408 401 372 + 408 403 376 409 + 408 405 412 411 + 376 404 412 410 + 380 154 406 413 + 406 77 416 415 + 380 136 416 414 + 384 413 412 417 + 412 415 420 419 + 384 414 420 418 + 388 137 416 421 + 416 45 424 423 + 388 79 424 422 + 392 421 420 425 + 420 423 428 427 + 392 422 428 426 + 395 308 424 429 + 430 395 310 431 + 431 229 424 432 + 399 429 428 433 + 434 399 430 435 + 435 432 428 436 + 437 438 216 402 + 438 285 406 439 + 440 441 286 438 + 441 272 406 442 + 443 444 437 408 + 444 439 412 445 + 446 447 440 444 + 447 442 412 448 + 441 303 416 449 + 450 451 306 441 + 451 252 416 452 + 447 449 420 453 + 454 455 450 447 + 455 452 420 456 + 451 298 424 457 + 458 459 297 451 + 459 278 424 460 + 455 457 428 461 + 462 463 458 455 + 463 460 428 464 + 465 431 235 459 + 466 467 234 459 + 467 144 431 468 + 469 435 465 463 + 470 471 466 463 + 471 468 435 472 + + + 401 402 403 264 404 + 403 405 406 351 407 + 408 409 410 402 411 + 410 412 413 405 414 + 415 416 407 362 417 + 417 418 419 381 420 + 421 422 414 416 423 + 423 424 425 418 426 + 427 428 420 382 429 + 429 430 431 376 432 + 433 434 426 428 435 + 435 436 437 430 438 + 439 440 432 350 441 + 442 443 441 347 444 + 445 446 438 440 447 + 448 449 447 443 450 + 451 452 453 390 406 + 453 454 455 386 456 + 457 458 459 452 413 + 459 460 461 454 462 + 419 463 456 165 464 + 464 465 466 161 467 + 425 468 462 463 469 + 469 470 471 465 472 + 431 473 467 177 474 + 474 475 476 174 477 + 437 478 472 473 479 + 479 480 481 475 482 + 444 483 477 379 484 + 485 486 484 368 487 + 450 488 482 483 489 + 490 491 489 486 492 + 493 494 495 371 455 + 495 496 497 380 498 + 499 500 501 494 461 + 501 502 503 496 504 + 466 505 498 152 506 + 506 507 508 153 509 + 471 510 504 505 511 + 511 512 513 507 514 + 476 515 509 135 516 + 516 517 518 136 519 + 481 520 514 515 521 + 521 522 523 517 524 + 487 525 519 388 526 + 527 528 526 392 529 + 492 530 524 525 531 + 532 533 531 528 534 + 535 536 537 343 497 + 538 539 540 345 537 + 541 542 543 536 503 + 544 545 546 539 543 + 540 547 548 374 508 + 549 550 551 384 548 + 546 552 553 547 513 + 554 555 556 550 553 + 551 557 558 378 518 + 559 560 561 365 558 + 556 562 563 557 523 + 564 565 566 560 563 + 567 568 529 352 561 + 569 570 571 267 567 + 572 573 534 568 566 + 574 575 576 570 572 + 0 1 2 3 + 4 5 6 7 + 8 9 10 11 + 12 13 14 15 + 7 16 17 18 + 19 20 21 8 + 22 6 23 24 + 25 26 27 28 + 24 29 18 30 + 31 32 11 33 + 34 1 35 36 + 37 21 38 31 + 39 40 41 42 + 43 44 45 46 + 47 48 28 49 + 50 51 52 53 + 54 55 56 41 + 57 58 59 60 + 61 62 63 40 + 14 64 65 66 + 67 3 68 69 + 70 71 55 63 + 60 72 73 74 + 75 22 76 77 + 78 15 79 80 + 81 82 83 84 + 85 86 87 88 + 83 89 90 91 + 92 93 94 95 + 96 97 98 99 + 100 101 102 103 + 104 105 106 107 + 108 109 110 25 + 111 112 113 50 + 114 115 92 116 + 117 118 67 119 + 120 121 122 96 + 123 78 124 111 + 125 126 43 127 + 44 72 128 129 + 130 37 34 64 + 119 110 131 47 + 132 133 134 135 + 134 69 136 35 + 137 138 139 140 + 141 142 143 144 + 145 146 125 57 + 147 148 149 104 + 150 79 151 152 + 151 133 153 65 + 86 97 154 155 + 93 101 156 157 + 105 89 158 159 + 160 42 161 162 + 163 164 160 165 + 166 167 85 168 + 169 170 171 100 + 172 173 174 175 + 176 56 172 177 + 178 179 81 147 + 124 62 162 150 + 71 118 175 132 + 180 181 46 182 + 183 29 184 59 + 185 13 166 120 + 186 84 187 23 + 188 189 190 191 + 192 193 194 143 + 195 196 140 197 + 198 2 114 169 + 199 200 95 201 + 202 203 88 204 + 205 206 91 207 + 208 209 210 98 + 211 212 213 102 + 214 215 216 73 + 217 218 219 194 + 220 221 197 222 + 223 224 225 226 + 36 38 116 227 + 228 229 58 214 + 230 231 205 82 + 66 122 32 232 + 233 234 199 115 + 235 236 121 208 + 164 149 51 237 + 80 113 168 238 + 239 240 180 126 + 173 127 241 131 + 68 48 242 171 + 243 244 245 106 + 246 247 248 249 + 250 251 249 154 + 252 253 254 128 + 255 256 257 254 + 258 259 159 139 + 260 261 144 156 + 262 263 202 167 + 264 138 265 266 + 267 268 269 141 + 270 271 170 211 + 272 273 129 190 + 274 275 157 219 + 276 277 155 225 + 278 279 222 158 + 280 281 148 243 + 282 283 49 284 + 285 286 53 287 + 288 289 30 290 + 291 292 33 293 + 294 295 234 20 + 296 297 226 248 + 298 299 191 257 + 300 301 302 281 + 303 304 240 109 + 305 306 212 275 + 307 308 209 277 + 309 310 274 200 + 311 312 276 203 + 313 314 279 206 + 315 316 231 5 + 317 250 318 87 + 319 320 321 45 + 322 323 99 324 + 325 253 74 326 + 327 328 329 90 + 330 331 332 94 + 333 258 107 334 + 335 261 103 336 + 337 338 16 229 + 339 340 215 273 + 341 342 9 236 + 343 247 251 344 + 345 346 344 323 + 347 348 320 349 + 350 255 349 252 + 351 266 328 259 + 352 269 331 260 + 353 354 263 355 + 356 357 26 271 + 358 359 272 181 + 360 361 244 278 + 362 363 364 327 + 365 366 367 330 + 368 369 282 370 + 371 372 285 373 + 374 322 232 375 + 376 377 325 184 + 378 291 227 367 + 379 319 370 241 + 380 373 238 317 + 381 364 288 187 + 382 383 377 289 + 384 385 375 292 + 386 387 237 286 + 388 283 389 242 + 390 391 333 387 + 392 393 335 389 + 394 302 395 52 + 396 355 112 394 + 179 397 186 163 + 398 397 39 76 + 399 400 77 54 + 400 146 183 176 + 577 578 579 580 + 581 582 583 584 + 585 586 587 588 + 589 590 591 592 + 593 594 595 596 + 583 597 598 599 + 600 601 589 602 + 603 604 584 605 + 606 607 608 609 + 605 610 611 598 + 612 613 614 591 + 615 616 617 577 + 618 619 588 620 + 621 622 602 612 + 623 624 625 626 + 627 628 629 630 + 631 632 633 608 + 634 635 636 637 + 638 639 640 641 + 642 643 626 644 + 645 646 596 647 + 648 649 650 579 + 651 652 643 653 + 641 654 655 656 + 603 657 620 658 + 659 660 661 594 + 662 663 664 665 + 666 667 668 669 + 664 670 671 672 + 673 674 675 676 + 677 678 679 680 + 681 682 683 684 + 685 686 687 688 + 689 690 606 691 + 692 693 694 634 + 695 696 673 697 + 698 699 700 650 + 701 702 703 677 + 661 692 704 705 + 706 707 627 708 + 628 654 709 710 + 621 617 647 711 + 699 712 691 631 + 520 713 714 715 + 522 715 616 648 + 716 717 718 719 + 720 721 722 723 + 585 706 638 724 + 725 726 727 681 + 510 728 660 729 + 512 729 646 714 + 667 678 730 731 + 682 670 732 733 + 674 686 734 735 + 470 736 737 623 + 468 738 739 736 + 740 741 666 742 + 743 744 745 685 + 480 746 747 748 + 749 662 725 750 + 728 737 705 642 + 747 713 698 651 + 751 752 753 629 + 618 754 610 639 + 593 740 701 755 + 756 757 663 604 + 758 759 760 722 + 761 762 719 763 + 764 765 766 767 + 578 695 743 768 + 769 770 771 675 + 772 773 774 671 + 775 776 777 668 + 778 779 688 780 + 781 782 680 783 + 784 785 656 786 + 787 788 789 790 + 791 792 793 760 + 794 795 763 796 + 615 797 696 622 + 798 799 784 640 + 800 801 665 772 + 645 802 613 702 + 803 804 697 769 + 805 806 781 703 + 739 807 635 726 + 659 808 741 693 + 809 810 708 751 + 748 811 707 712 + 649 812 632 744 + 813 814 684 815 + 816 817 710 818 + 819 820 731 821 + 822 823 818 824 + 825 826 821 827 + 828 829 735 718 + 830 831 723 732 + 832 833 742 775 + 409 721 834 835 + 575 836 716 837 + 838 839 778 745 + 840 841 767 709 + 842 843 733 793 + 844 845 796 734 + 846 847 790 730 + 848 849 813 727 + 850 851 852 636 + 853 854 855 633 + 856 857 858 611 + 859 860 861 614 + 862 863 601 804 + 864 865 824 766 + 866 867 827 789 + 868 869 849 870 + 871 872 690 810 + 873 874 847 782 + 875 876 845 779 + 877 878 770 844 + 879 880 773 843 + 881 882 776 846 + 883 884 582 801 + 885 886 669 887 + 888 889 630 890 + 891 820 892 679 + 893 817 894 655 + 895 896 672 897 + 898 899 676 900 + 901 829 902 687 + 903 830 904 683 + 905 906 799 597 + 907 908 841 785 + 909 910 806 590 + 542 911 912 886 + 449 913 914 889 + 545 825 819 912 + 446 822 816 914 + 573 837 828 899 + 412 834 831 896 + 915 916 917 833 + 918 919 839 607 + 920 921 752 840 + 922 923 842 814 + 565 924 898 925 + 422 926 895 927 + 500 928 929 850 + 491 930 931 853 + 552 891 932 802 + 436 933 754 893 + 562 859 925 797 + 502 929 885 808 + 488 888 811 931 + 424 927 757 856 + 434 934 857 933 + 555 935 860 932 + 460 936 851 807 + 530 854 812 937 + 533 938 937 901 + 458 939 936 903 + 940 917 941 694 + 941 870 637 942 + 943 657 944 625 + 756 738 943 749 + 746 945 652 586 + 478 619 624 945 + + + + A[0-163,228-391] + F[361,220,312,223,137,195,246,296,301,245,354,395,204,391,334,372,287,318] + F[306,218,359,188,142,193,348,298,357,213,304,27,182,393,336,369,284,321] + F[308,224,310,217,346,297,268,192,342,210,295,10,201,385,324,366,293,332] + F[314,221,340,189,265,196,256,299,338,216,316,17,207,383,326,363,290,329] + F[401,408,451,457,493,499,535,541] + F[571,576,527,532,485,490,442,448] + F[538,544,549,554,559,564,569,574] + F[439,445,427,433,415,421,404,411] + F[720,758,911,866,923,791,882,787,939,904,928,852,887,869,815,916,942,777] + F[717,762,913,864,876,795,921,764,938,902,930,855,890,919,780,872,609,753] + F[826,867,836,761,874,788,878,794,935,892,924,861,900,910,783,863,592,771] + F[835,759,823,865,880,792,908,765,934,894,926,858,897,906,786,884,599,774] + F[360,313,311,307,309,305,358,339,300,280,353,396,262,341,235,294,19,233,356,270,303,108,239,337,228,315,4,230,12,185,123,130,0,198,117,70,399,145,398,178,75,61] + F[922,879,881,873,877,875,920,907,868,848,915,940,832,909,805,862,600,803,918,838,871,689,809,905,798,883,581,800,595,755,704,711,580,768,700,653,587,724,944,750,658,644] + R[164-227] + + + C[1,114] + + + + + + 2e0fb86da236e7e5a3590fcf5e0f608bd8490945 + L0211-XU + 5.3.0 + 21-Jul-2023 15:38:49 + + -v prism_tet_cube_0.5_perturbed.msh prism_tet_cube_0.5_perturbed.tmp.xml:xml:uncompress + + diff --git a/test/unit/nektar_interface/test_basis_evaluation.cpp b/test/unit/nektar_interface/test_basis_evaluation.cpp index d0195526..e59f3d10 100644 --- a/test/unit/nektar_interface/test_basis_evaluation.cpp +++ b/test/unit/nektar_interface/test_basis_evaluation.cpp @@ -23,6 +23,7 @@ using namespace Nektar::SolverUtils; using namespace Nektar::SpatialDomains; using namespace Nektar::MultiRegions; using namespace NESO::Particles; +using namespace NESO::BasisReference; static inline void copy_to_cstring(std::string input, char **output) { *output = new char[input.length() + 1]; @@ -35,13 +36,26 @@ TEST(JacobiCoeffModBasis, Coeff) { ASSERT_NEAR(jacobi(13, 0.3, 7, 11), -9.5066868221006, 1.0e-10); ASSERT_NEAR(jacobi(13, -0.5, 7, 11), -47.09590101242081, 1.0e-10); - JacobiCoeffModBasis jacobi_coeff(13, 7); + const int max_n = 13; + const int max_alpha = 7; + JacobiCoeffModBasis jacobi_coeff(max_n, max_alpha); ASSERT_NEAR(jacobi(13, -0.5, 7, 1), jacobi_coeff.host_evaluate(13, 7, -0.5), 1.0e-10); ASSERT_NEAR(jacobi(0, -0.5, 7, 1), 1.0, 1.0e-10); ASSERT_NEAR(jacobi(7, 0.5, 3, 1), jacobi_coeff.host_evaluate(7, 3, 0.5), 1.0e-10); + + const double z = -0.4234; + for (int n = 0; n <= max_n; n++) { + for (int alpha = 1; alpha <= max_alpha; alpha++) { + const double correct = jacobi(n, z, alpha, 1); + const double to_test = jacobi_coeff.host_evaluate(n, alpha, z); + const double err_rel = relative_error(correct, to_test); + const double err_abs = std::abs(correct - to_test); + ASSERT_TRUE(err_rel < 1.0e-14 | err_abs < 1.0e-14); + } + } } TEST(ParticleFunctionBasisEvaluation, DisContFieldScalar) { @@ -309,3 +323,458 @@ TEST(ParticleFunctionBasisEvaluation, ContFieldScalar) { delete[] argv[1]; delete[] argv[2]; } + +TEST(ParticleFunctionBasisEvaluation, Basis2D) { + + int argc = 3; + char *argv[3]; + + std::filesystem::path source_file = __FILE__; + std::filesystem::path source_dir = source_file.parent_path(); + std::filesystem::path test_resources_dir = + source_dir / "../../test_resources"; + std::filesystem::path mesh_file = + test_resources_dir / "square_triangles_quads_nummodes_6.xml"; + std::filesystem::path conditions_file = test_resources_dir / "conditions.xml"; + + copy_to_cstring(std::string("test_particle_function_evaluation"), &argv[0]); + copy_to_cstring(std::string(mesh_file), &argv[1]); + copy_to_cstring(std::string(conditions_file), &argv[2]); + + LibUtilities::SessionReaderSharedPtr session; + SpatialDomains::MeshGraphSharedPtr graph; + // Create session reader. + session = LibUtilities::SessionReader::CreateInstance(argc, argv); + graph = SpatialDomains::MeshGraph::Read(session); + auto dis_cont_field = std::make_shared(session, graph, "u"); + + std::mt19937 rng{182348}; + + INT errs_count = 0; + REAL errs_total = 0.0; + + for (int ei = 0; ei < dis_cont_field->GetNumElmts(); ei++) { + auto ex = dis_cont_field->GetExp(ei); + auto shape = ex->DetShapeType(); + auto geom = ex->GetGeom(); + auto bounding_box = geom->GetBoundingBox(); + + std::uniform_real_distribution uniform_dist0(bounding_box[0], + bounding_box[0] + 3); + std::uniform_real_distribution uniform_dist1(bounding_box[1], + bounding_box[1] + 3); + + Array local_collapsed(3); + Array local_coord(3); + Array global_coord(3); + + global_coord[2] = 0.0; + + for (int testx = 0; testx < 2; testx++) { + bool is_contained = false; + while (!is_contained) { + global_coord[0] = uniform_dist0(rng); + global_coord[1] = uniform_dist1(rng); + is_contained = + ex->GetGeom()->ContainsPoint(global_coord, local_coord, 1.0e-8); + } + ex->LocCoordToLocCollapsed(local_coord, local_collapsed); + + const int P = ex->GetBasis(0)->GetNumModes(); + const int Q = ex->GetBasis(1)->GetNumModes(); + ASSERT_EQ(P, Q); + + const int num_coeffs = ex->GetNcoeffs(); + const int to_test_num_coeffs = get_total_num_modes(shape, P); + EXPECT_EQ(num_coeffs, to_test_num_coeffs); + + std::vector mode_evals(num_coeffs); + std::vector mode_evals_basis(num_coeffs); + std::vector mode_correct(num_coeffs); + + eval_modes(shape, P, local_collapsed[0], local_collapsed[1], + local_collapsed[2], mode_evals_basis); + + auto lambda_err = [](const double correct, const double to_test) { + const double abs_err = std::abs(correct - to_test); + const double scaling = std::abs(correct); + const double rel_err = scaling > 0 ? abs_err / scaling : abs_err; + return std::min(abs_err, rel_err); + }; + + for (int modex = 0; modex < num_coeffs; modex++) { + const double correct = ex->PhysEvaluateBasis(local_coord, modex); + const double to_test = mode_evals_basis[modex]; + const double err = relative_error(correct, to_test); + errs_total += err; + errs_count++; + // near the collapsed singularity? + if (std::abs(local_collapsed[1]) > 0.85) { + EXPECT_TRUE((err < 1.0e-8) || std::abs(correct) < 1.0e-7); + } else { + EXPECT_TRUE((err < 1.0e-10) || std::abs(correct) < 1.0e-7); + } + } + } + } + + REAL errs_avg = errs_total / errs_count; + ASSERT_TRUE(errs_avg < 1.0e-10); + + delete[] argv[0]; + delete[] argv[1]; + delete[] argv[2]; +} + +TEST(ParticleFunctionBasisEvaluation, Basis3D) { + + std::tuple param = { + "reference_all_types_cube/conditions.xml", + "reference_all_types_cube/mixed_ref_cube_0.5_perturbed.xml", 2.0e-4}; + + const int N_total = 2000; + const double tol = std::get<2>(param); + + std::filesystem::path source_file = __FILE__; + std::filesystem::path source_dir = source_file.parent_path(); + std::filesystem::path test_resources_dir = + source_dir / "../../test_resources"; + + std::filesystem::path condtions_file_basename = + static_cast(std::get<0>(param)); + std::filesystem::path mesh_file_basename = + static_cast(std::get<1>(param)); + std::filesystem::path conditions_file = + test_resources_dir / condtions_file_basename; + std::filesystem::path mesh_file = test_resources_dir / mesh_file_basename; + + int argc = 3; + char *argv[3]; + copy_to_cstring(std::string("test_particle_geometry_interface"), &argv[0]); + copy_to_cstring(std::string(conditions_file), &argv[1]); + copy_to_cstring(std::string(mesh_file), &argv[2]); + + LibUtilities::SessionReaderSharedPtr session; + SpatialDomains::MeshGraphSharedPtr graph; + // Create session reader. + session = LibUtilities::SessionReader::CreateInstance(argc, argv); + graph = SpatialDomains::MeshGraph::Read(session); + + auto mesh = std::make_shared(graph); + auto sycl_target = std::make_shared(0, mesh->get_comm()); + + std::mt19937 rng{182348}; + + auto cont_field = std::make_shared(session, graph, "u"); + + auto lambda_func = [](const double x, const double y, const double z) { + // return sin(x) * cos(y) + exp(z); + return (x + 1) * (x - 1) * (y + 1) * (y - 1) * (z + 1) * (z - 1); + }; + + interpolate_onto_nektar_field_3d(lambda_func, cont_field); + + auto lambda_err = [](const double correct, const double to_test) { + const double abs_err = std::abs(correct - to_test); + const double scaling = std::abs(correct); + const double rel_err = scaling > 0 ? abs_err / scaling : abs_err; + return std::min(abs_err, rel_err); + }; + + double avg_err_dof = 0.0; + double avg_err_eval = 0.0; + INT avg_count_dof = 0; + INT avg_count_eval = 0; + + for (int ei = 0; ei < cont_field->GetNumElmts(); ei++) { + auto ex = cont_field->GetExp(ei); + + // test the basis evaluation in each direction + for (int dimx = 0; dimx < 3; dimx++) { + auto b0 = ex->GetBasis(dimx); + + auto bb0 = b0->GetBdata(); + auto zb0 = b0->GetZ(); + auto nb0 = b0->GetNumModes(); + auto mb0 = b0->GetNumPoints(); + + auto total_num_modes = b0->GetTotNumModes(); + + std::vector to_test(total_num_modes); + + // test that the NESO implementation of the basis matches the Nektar++ + // implementation of the basis at the quadrature points. + for (int m = 0; m < mb0; m++) { + const double zz = zb0[m]; + eval_basis(b0->GetBasisType(), nb0, zz, to_test); + for (int mx = 0; mx < total_num_modes; mx++) { + ASSERT_TRUE((mx * mb0 + m) < bb0.size()); + const double zc = bb0[mx * mb0 + m]; + const double tt = to_test.at(mx); + ASSERT_NEAR(zc, tt, 1.0e-10); + } + } + } + // test the computation of each overall mode + auto shape = ex->DetShapeType(); + auto geom = ex->GetGeom(); + auto bounding_box = geom->GetBoundingBox(); + std::uniform_real_distribution uniform_dist0(bounding_box[0], + bounding_box[0] + 3); + std::uniform_real_distribution uniform_dist1(bounding_box[1], + bounding_box[1] + 3); + std::uniform_real_distribution uniform_dist2(bounding_box[2], + bounding_box[2] + 3); + + Array local_collapsed(3); + Array local_coord(3); + Array global_coord(3); + + for (int testx = 0; testx < 2; testx++) { + + bool is_contained = false; + while (!is_contained) { + + global_coord[0] = uniform_dist0(rng); + global_coord[1] = uniform_dist1(rng); + global_coord[2] = uniform_dist2(rng); + is_contained = + ex->GetGeom()->ContainsPoint(global_coord, local_coord, 1.0e-8); + } + ex->LocCoordToLocCollapsed(local_coord, local_collapsed); + + const int num_coeffs = ex->GetNcoeffs(); + std::vector mode_evals(num_coeffs); + std::vector mode_evals_basis(num_coeffs); + std::vector mode_correct(num_coeffs); + + const int P = ex->GetBasis(0)->GetNumModes(); + const int Q = ex->GetBasis(1)->GetNumModes(); + const int R = ex->GetBasis(2)->GetNumModes(); + ASSERT_EQ(P, Q); + ASSERT_EQ(P, R); + + const int to_test_num_coeffs = get_total_num_modes(shape, P); + EXPECT_EQ(to_test_num_coeffs, num_coeffs); + + eval_modes(shape, P, local_collapsed[0], local_collapsed[1], + local_collapsed[2], mode_evals_basis); + + auto coeffs_global = cont_field->GetCoeffs(); + auto phys_global = cont_field->GetPhys(); + + auto coeffs = coeffs_global + cont_field->GetCoeff_Offset(ei); + auto phys = phys_global + cont_field->GetPhys_Offset(ei); + + // get each basis function evaluated at the point + const int global_num_coeffs = coeffs_global.size(); + const int global_num_phys = phys_global.size(); + + Array basis_coeffs(global_num_coeffs); + Array basis_phys(global_num_phys); + + auto lambda_zero = [&]() { + for (int cx = 0; cx < global_num_coeffs; cx++) { + basis_coeffs[cx] = 0.0; + } + for (int cx = 0; cx < global_num_phys; cx++) { + basis_phys[cx] = 0.0; + } + }; + + const int offset_coeff = cont_field->GetCoeff_Offset(ei); + const int offset_phys = cont_field->GetPhys_Offset(ei); + Array mode_via_coeffs(mode_evals.size()); + + for (int modex = 0; modex < num_coeffs; modex++) { + // set the dof for the mode to 1 and the rest to 0 + lambda_zero(); + basis_coeffs[offset_coeff + modex] = 1.0; + // convert to quadrature point values + cont_field->BwdTrans(basis_coeffs, basis_phys); + mode_via_coeffs[modex] = + ex->StdPhysEvaluate(local_coord, basis_phys + offset_phys); + } + + // get field evaluation at point + const double eval_bary = + ex->StdPhysEvaluate(local_coord, phys_global + offset_phys); + + // test the basis functions computed through StdPhysEvaluate vs the modes + // we computed + for (int modex = 0; modex < num_coeffs; modex++) { + const double dof_err = + lambda_err(mode_via_coeffs[modex], mode_evals_basis[modex]); + const double dof_abs_err = + std::abs(mode_via_coeffs[modex] - mode_evals_basis[modex]); + avg_count_dof++; + avg_err_dof += dof_err; + EXPECT_TRUE(dof_err < 2.0e-5 || dof_abs_err < 1.0e-4); + } + // evaluation using modes we computed + double eval_modes = 0.0; + double eval_modes_nektar = 0.0; + for (int modex = 0; modex < num_coeffs; modex++) { + const double mode_tmp = mode_evals_basis[modex]; + const double mode_tmp_nektar = mode_via_coeffs[modex]; + const double dof_tmp = coeffs[modex]; + eval_modes += dof_tmp * mode_tmp; + eval_modes_nektar += dof_tmp * mode_tmp_nektar; + } + + const double eval_err = lambda_err(eval_bary, eval_modes); + const double eval_err_nektar = lambda_err(eval_bary, eval_modes_nektar); + const double eval_abs_err = std::abs(eval_bary - eval_modes); + + avg_count_eval++; + avg_err_eval += eval_err; + EXPECT_TRUE(eval_err < 2.0e-5 || eval_abs_err < 1.0e-4); + } + } + + double avg_err_tmp[2] = {avg_err_eval, avg_err_dof}; + double avg_err[2] = {0, 0}; + + int64_t avg_count_tmp[2] = {avg_count_eval, avg_count_dof}; + int64_t avg_count[2] = {0, 0}; + + MPICHK(MPI_Allreduce(avg_err_tmp, avg_err, 2, MPI_DOUBLE, MPI_SUM, + sycl_target->comm_pair.comm_parent)); + MPICHK(MPI_Allreduce(avg_count_tmp, avg_count, 2, MPI_INT64_T, MPI_SUM, + sycl_target->comm_pair.comm_parent)); + + avg_err[0] /= ((double)avg_count[0]); + avg_err[1] /= ((double)avg_count[1]); + + EXPECT_TRUE(avg_err[0] < 1.0e-8); + EXPECT_TRUE(avg_err[1] < 1.0e-8); + + mesh->free(); + delete[] argv[0]; + delete[] argv[1]; + delete[] argv[2]; +} + +TEST(KernelBasis, EvalA) { + + const int P = 9; + int max_alpha, max_n, total_num_modes; + + total_num_modes = P; + BasisReference::get_total_num_modes(eQuadrilateral, P, &max_n, &max_alpha); + JacobiCoeffModBasis jacobi_coeff(max_n, max_alpha); + + std::vector to_test(total_num_modes); + std::vector correct(total_num_modes); + + const double z = -0.124124; + + eval_modA(P, z, correct); + + BasisJacobi::mod_A(P, z, jacobi_coeff.stride_n, + jacobi_coeff.coeffs_pnm10.data(), + jacobi_coeff.coeffs_pnm11.data(), + jacobi_coeff.coeffs_pnm2.data(), to_test.data()); + + for (int p = 0; p < P; p++) { + const double err = std::abs(correct[p] - to_test[p]); + EXPECT_TRUE(err < 1.0e-14); + } +} + +TEST(KernelBasis, EvalB) { + + const int P = 9; + int max_alpha, max_n, total_num_modes; + + total_num_modes = + BasisReference::get_total_num_modes(eTriangle, P, &max_n, &max_alpha); + JacobiCoeffModBasis jacobi_coeff(max_n, max_alpha); + + std::vector to_test(total_num_modes); + std::vector correct(total_num_modes); + + const double z = -0.124124; + + eval_modB(P, z, correct); + + BasisJacobi::mod_B(P, z, jacobi_coeff.stride_n, + jacobi_coeff.coeffs_pnm10.data(), + jacobi_coeff.coeffs_pnm11.data(), + jacobi_coeff.coeffs_pnm2.data(), to_test.data()); + + int mode = 0; + for (int p = 0; p < P; p++) { + for (int q = 0; q < (P - p); q++) { + const double err = std::abs(correct[mode] - to_test[mode]); + EXPECT_TRUE(err < 1.0e-14); + mode++; + } + } +} + +TEST(KernelBasis, EvalC) { + + const int P = 9; + int max_alpha, max_n, total_num_modes; + + total_num_modes = + BasisReference::get_total_num_modes(eTetrahedron, P, &max_n, &max_alpha); + JacobiCoeffModBasis jacobi_coeff(max_n, max_alpha); + + std::vector to_test(total_num_modes); + std::vector correct(total_num_modes); + + const double z = -0.124124; + + eval_modC(P, z, correct); + + BasisJacobi::mod_C(P, z, jacobi_coeff.stride_n, + jacobi_coeff.coeffs_pnm10.data(), + jacobi_coeff.coeffs_pnm11.data(), + jacobi_coeff.coeffs_pnm2.data(), to_test.data()); + + int mode = 0; + for (int p = 0; p < P; p++) { + for (int q = 0; q < (P - p); q++) { + for (int r = 0; r < (P - p - q); r++) { + const double err = std::abs(correct[mode] - to_test[mode]); + EXPECT_TRUE(err < 1.0e-14); + mode++; + } + } + } +} + +TEST(KernelBasis, EvalPyrC) { + + const int P = 9; + int max_alpha, max_n, total_num_modes; + + total_num_modes = get_total_num_modes(eModifiedPyr_C, P); + BasisReference::get_total_num_modes(ePyramid, P, &max_n, &max_alpha); + JacobiCoeffModBasis jacobi_coeff(max_n, max_alpha); + + std::vector to_test(total_num_modes); + std::vector correct(total_num_modes); + + const double z = -0.124124; + + eval_modPyrC(P, z, correct); + + BasisJacobi::mod_PyrC(P, z, jacobi_coeff.stride_n, + jacobi_coeff.coeffs_pnm10.data(), + jacobi_coeff.coeffs_pnm11.data(), + jacobi_coeff.coeffs_pnm2.data(), to_test.data()); + + int mode = 0; + for (int p = 0; p < P; p++) { + for (int q = 0; q < P; q++) { + for (int r = 0; r < P - std::max(p, q); r++) { + const double err = std::abs(correct[mode] - to_test[mode]); + EXPECT_TRUE(err < 1.0e-14); + mode++; + } + } + } +} diff --git a/test/unit/nektar_interface/test_kernel_basis_evaluation.cpp b/test/unit/nektar_interface/test_kernel_basis_evaluation.cpp new file mode 100644 index 00000000..f2fcd7c6 --- /dev/null +++ b/test/unit/nektar_interface/test_kernel_basis_evaluation.cpp @@ -0,0 +1,215 @@ +#include "nektar_interface/expansion_looping/expansion_looping.hpp" +#include "nektar_interface/function_evaluation.hpp" +#include "nektar_interface/particle_interface.hpp" +#include "nektar_interface/utilities.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace Nektar; +using namespace Nektar::SolverUtils; +using namespace Nektar::SpatialDomains; +using namespace Nektar::MultiRegions; +using namespace NESO::Particles; +using namespace NESO::BasisReference; + +static inline REAL local_reduce(const std::vector &a, + const std::vector &b) { + REAL e = 0.0; + const int N = a.size(); + for (int ix = 0; ix < N; ix++) { + e += a[ix] * b[ix]; + } + return e; +} + +static inline void local_scale(const REAL value, std::vector &a) { + const int N = a.size(); + for (int ix = 0; ix < N; ix++) { + a[ix] *= value; + } +} + +static inline REAL *zero_device_buffer_host(BufferDeviceHost &b) { + for (int ix = 0; ix < b.size; ix++) { + b.h_buffer.ptr[ix] = 0.0; + } + b.host_to_device(); + return b.d_buffer.ptr; +} + +static inline REAL *copy_to_device_buffer_host(std::vector &v, + BufferDeviceHost &b) { + for (int ix = 0; ix < b.size; ix++) { + b.h_buffer.ptr[ix] = v[ix]; + } + b.host_to_device(); + return b.d_buffer.ptr; +} + +template +inline void kernel_basis_wrapper(const int P) { + + int max_alpha, max_n, total_num_modes; + + total_num_modes = + BasisReference::get_total_num_modes(SHAPE_TYPE, P, &max_n, &max_alpha); + JacobiCoeffModBasis jacobi_coeff(max_n, max_alpha); + + std::map map0; + map0[eTriangle] = eModified_A; + map0[eQuadrilateral] = eModified_A; + map0[eHexahedron] = eModified_A; + map0[ePyramid] = eModified_A; + map0[ePrism] = eModified_A; + map0[eTetrahedron] = eModified_A; + + std::map map1; + map1[eTriangle] = eModified_B; + map1[eQuadrilateral] = eModified_A; + map1[eHexahedron] = eModified_A; + map1[ePyramid] = eModified_A; + map1[ePrism] = eModified_A; + map1[eTetrahedron] = eModified_B; + + std::map map2; + map2[eHexahedron] = eModified_A; + map2[ePyramid] = eModifiedPyr_C; + map2[ePrism] = eModified_B; + map2[eTetrahedron] = eModified_C; + + const int total_num_modes_0 = + BasisReference::get_total_num_modes(map0.at(SHAPE_TYPE), P); + const int total_num_modes_1 = + BasisReference::get_total_num_modes(map1.at(SHAPE_TYPE), P); + const int total_num_modes_2 = + (NDIM > 2) ? BasisReference::get_total_num_modes(map2.at(SHAPE_TYPE), P) + : 1; + + std::vector dir0(total_num_modes_0); + std::vector dir1(total_num_modes_1); + std::vector dir2(total_num_modes_2); + std::vector to_test(total_num_modes); + std::vector correct(total_num_modes); + std::vector coeffs(total_num_modes); + std::fill(coeffs.begin(), coeffs.end(), 1.0); + + const REAL xi0 = -0.235235; + const REAL xi1 = -0.565235; + const REAL xi2 = -0.234; + + REAL eta0, eta1, eta2; + + BASIS_TYPE geom{}; + + geom.loc_coord_to_loc_collapsed(xi0, xi1, xi2, &eta0, &eta1, &eta2); + + geom.evaluate_basis_0(P, eta0, jacobi_coeff.stride_n, + jacobi_coeff.coeffs_pnm10.data(), + jacobi_coeff.coeffs_pnm11.data(), + jacobi_coeff.coeffs_pnm2.data(), dir0.data()); + geom.evaluate_basis_1(P, eta1, jacobi_coeff.stride_n, + jacobi_coeff.coeffs_pnm10.data(), + jacobi_coeff.coeffs_pnm11.data(), + jacobi_coeff.coeffs_pnm2.data(), dir1.data()); + + if (NDIM > 2) { + geom.evaluate_basis_2(P, eta2, jacobi_coeff.stride_n, + jacobi_coeff.coeffs_pnm10.data(), + jacobi_coeff.coeffs_pnm11.data(), + jacobi_coeff.coeffs_pnm2.data(), dir2.data()); + } + + REAL to_test_evaluate; + geom.loop_evaluate(P, coeffs.data(), dir0.data(), dir1.data(), dir2.data(), + &to_test_evaluate); + + eval_modes(SHAPE_TYPE, P, eta0, eta1, eta2, correct); + const REAL correct_evaluate = local_reduce(correct, coeffs); + + const REAL err = relative_error(correct_evaluate, to_test_evaluate); + EXPECT_TRUE(err < 1.0e-14); + + const REAL value = 7.12235; + local_scale(value, correct); + + auto sycl_target = std::make_shared(0, MPI_COMM_WORLD); + + geom.loop_project(P, value, dir0.data(), dir1.data(), dir2.data(), + to_test.data()); + + BufferDeviceHost dh_to_test_dofs(sycl_target, total_num_modes); + REAL *k_dofs = zero_device_buffer_host(dh_to_test_dofs); + + BufferDeviceHost dh_dir0(sycl_target, dir0.size()); + BufferDeviceHost dh_dir1(sycl_target, dir1.size()); + BufferDeviceHost dh_dir2(sycl_target, dir2.size()); + REAL *k_dir0 = copy_to_device_buffer_host(dir0, dh_dir0); + REAL *k_dir1 = copy_to_device_buffer_host(dir1, dh_dir1); + REAL *k_dir2 = copy_to_device_buffer_host(dir2, dh_dir2); + + sycl_target->queue + .submit([&](sycl::handler &cgh) { + cgh.single_task<>([=]() { + BASIS_TYPE k_geom{}; + k_geom.loop_project(P, value, k_dir0, k_dir1, k_dir2, k_dofs); + }); + }) + .wait_and_throw(); + + dh_to_test_dofs.device_to_host(); + + for (int modex = 0; modex < total_num_modes; modex++) { + const REAL err = + relative_error(correct[modex], dh_to_test_dofs.h_buffer.ptr[modex]); + EXPECT_TRUE(err < 1.0e-12); + } +} + +TEST(KernelBasis, Triangle) { + for (int P = 2; P < 11; P++) { + kernel_basis_wrapper<2, ShapeType::eTriangle, ExpansionLooping::Triangle>( + P); + } +} +TEST(KernelBasis, Quadrilateral) { + for (int P = 2; P < 11; P++) { + kernel_basis_wrapper<2, ShapeType::eQuadrilateral, + ExpansionLooping::Quadrilateral>(P); + } +} +TEST(KernelBasis, Hexahedron) { + for (int P = 2; P < 11; P++) { + kernel_basis_wrapper<3, ShapeType::eHexahedron, + ExpansionLooping::Hexahedron>(P); + } +} +TEST(KernelBasis, Prism) { + for (int P = 2; P < 11; P++) { + kernel_basis_wrapper<3, ShapeType::ePrism, ExpansionLooping::Prism>(P); + } +} +TEST(KernelBasis, Pyramid) { + for (int P = 2; P < 11; P++) { + kernel_basis_wrapper<3, ShapeType::ePyramid, ExpansionLooping::Pyramid>(P); + } +} +TEST(KernelBasis, Tetrahedron) { + for (int P = 2; P < 11; P++) { + kernel_basis_wrapper<3, ShapeType::eTetrahedron, + ExpansionLooping::Tetrahedron>(P); + } +} diff --git a/test/unit/nektar_interface/test_particle_function_evaluation_3d.cpp b/test/unit/nektar_interface/test_particle_function_evaluation_3d.cpp new file mode 100644 index 00000000..31969b6c --- /dev/null +++ b/test/unit/nektar_interface/test_particle_function_evaluation_3d.cpp @@ -0,0 +1,180 @@ +#include "nektar_interface/function_evaluation.hpp" +#include "nektar_interface/particle_interface.hpp" +#include "nektar_interface/utilities.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace Nektar; +using namespace Nektar::SolverUtils; +using namespace Nektar::SpatialDomains; +using namespace NESO::Particles; + +static inline void copy_to_cstring(std::string input, char **output) { + *output = new char[input.length() + 1]; + std::strcpy(*output, input.c_str()); +} + +template +static inline void evaluation_wrapper_3d(std::string condtions_file_s, + std::string mesh_file_s, + const double tol) { + + const int N_total = 16000; + + std::filesystem::path source_file = __FILE__; + std::filesystem::path source_dir = source_file.parent_path(); + std::filesystem::path test_resources_dir = + source_dir / "../../test_resources"; + + std::filesystem::path condtions_file_basename{condtions_file_s}; + std::filesystem::path mesh_file_basename{mesh_file_s}; + std::filesystem::path conditions_file = + test_resources_dir / condtions_file_basename; + std::filesystem::path mesh_file = test_resources_dir / mesh_file_basename; + + int argc = 3; + char *argv[3]; + copy_to_cstring(std::string("test_particle_geometry_interface"), &argv[0]); + copy_to_cstring(std::string(conditions_file), &argv[1]); + copy_to_cstring(std::string(mesh_file), &argv[2]); + + LibUtilities::SessionReaderSharedPtr session; + SpatialDomains::MeshGraphSharedPtr graph; + // Create session reader. + session = LibUtilities::SessionReader::CreateInstance(argc, argv); + graph = SpatialDomains::MeshGraph::Read(session); + + auto mesh = std::make_shared(graph); + auto sycl_target = std::make_shared(0, mesh->get_comm()); + + std::mt19937 rng{182348}; + + auto nektar_graph_local_mapper = + std::make_shared(sycl_target, mesh); + auto domain = std::make_shared(mesh, nektar_graph_local_mapper); + + const int ndim = 3; + ParticleSpec particle_spec{ParticleProp(Sym("P"), ndim, true), + ParticleProp(Sym("CELL_ID"), 1, true), + ParticleProp(Sym("E"), 1), + ParticleProp(Sym("ID"), 1)}; + + auto A = std::make_shared(domain, particle_spec, sycl_target); + + NektarCartesianPeriodic pbc(sycl_target, graph, A->position_dat); + auto cell_id_translation = + std::make_shared(sycl_target, A->cell_id_dat, mesh); + const int rank = sycl_target->comm_pair.rank_parent; + const int size = sycl_target->comm_pair.size_parent; + + std::mt19937 rng_pos(52234234 + rank); + int rstart, rend; + get_decomp_1d(size, N_total, rank, &rstart, &rend); + const int N = rend - rstart; + int N_check = -1; + MPICHK(MPI_Allreduce(&N, &N_check, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD)); + NESOASSERT(N_check == N_total, "Error creating particles"); + const int cell_count = domain->mesh->get_cell_count(); + if (N > 0) { + auto positions = + uniform_within_extents(N, ndim, pbc.global_extent, rng_pos); + + ParticleSet initial_distribution(N, A->get_particle_spec()); + for (int px = 0; px < N; px++) { + for (int dimx = 0; dimx < ndim; dimx++) { + const double pos_orig = positions[dimx][px] + pbc.global_origin[dimx]; + initial_distribution[Sym("P")][px][dimx] = pos_orig; + } + + initial_distribution[Sym("CELL_ID")][px][0] = 0; + initial_distribution[Sym("ID")][px][0] = px; + } + A->add_particles_local(initial_distribution); + } + reset_mpi_ranks((*A)[Sym("NESO_MPI_RANK")]); + + pbc.execute(); + A->hybrid_move(); + cell_id_translation->execute(); + A->cell_move(); + + auto field = std::make_shared(session, graph, "u"); + + auto lambda_f = [&](const NekDouble x, const NekDouble y, const NekDouble z) { + return std::pow((x + 1.0) * (x - 1.0) * (y + 1.0) * (y - 1.0) * (z + 1.0) * + (z - 1.0), + 4); + }; + interpolate_onto_nektar_field_3d(lambda_f, field); + NESOCellsToNektarExp map_cells_to_exp(field, cell_id_translation); + + auto field_evaluate = std::make_shared>( + field, A, cell_id_translation); + field_evaluate->evaluate(Sym("E")); + + Array local_coord(3); + + for (int cellx = 0; cellx < cell_count; cellx++) { + + auto cell_ids = A->cell_id_dat->cell_dat.get_cell(cellx); + auto reference_positions = + (*A)[Sym("NESO_REFERENCE_POSITIONS")]->cell_dat.get_cell(cellx); + auto E = (*A)[Sym("E")]->cell_dat.get_cell(cellx); + + const int exp_id = map_cells_to_exp.get_exp_id(cellx); + const auto exp = map_cells_to_exp.get_exp(cellx); + const auto exp_phys = field->GetPhys() + field->GetPhys_Offset(exp_id); + + for (int rowx = 0; rowx < cell_ids->nrow; rowx++) { + + local_coord[0] = (*reference_positions)[0][rowx]; + local_coord[1] = (*reference_positions)[1][rowx]; + local_coord[2] = (*reference_positions)[2][rowx]; + + const REAL to_test = (*E)[0][rowx]; + const REAL correct = exp->StdPhysEvaluate(local_coord, exp_phys); + + const double err = relative_error(correct, to_test); + const double err_abs = std::abs(correct - to_test); + EXPECT_TRUE(err < tol || err_abs < tol); + } + } + + A->free(); + mesh->free(); + + delete[] argv[0]; + delete[] argv[1]; + delete[] argv[2]; +} + +TEST(ParticleFunctionEvaluation3D, ContField) { + evaluation_wrapper_3d( + "reference_all_types_cube/conditions_cg.xml", + "reference_all_types_cube/mixed_ref_cube_0.5_perturbed.xml", 1.0e-7); +} +TEST(ParticleFunctionEvaluation3D, DisContFieldHex) { + evaluation_wrapper_3d( + "reference_hex_cube/conditions.xml", + "reference_hex_cube/hex_cube_0.3_perturbed.xml", 1.0e-7); +} +TEST(ParticleFunctionEvaluation3D, DisContFieldPrismTet) { + evaluation_wrapper_3d( + "reference_prism_tet_cube/conditions.xml", + "reference_prism_tet_cube/prism_tet_cube_0.5_perturbed.xml", 1.0e-7); +} diff --git a/test/unit/nektar_interface/test_particle_function_projection.cpp b/test/unit/nektar_interface/test_particle_function_projection.cpp index a59c4f1e..92c0a544 100644 --- a/test/unit/nektar_interface/test_particle_function_projection.cpp +++ b/test/unit/nektar_interface/test_particle_function_projection.cpp @@ -30,657 +30,6 @@ static inline void copy_to_cstring(std::string input, char **output) { std::strcpy(*output, input.c_str()); } -TEST(ParticleFunctionProjection, DisContScalarExpQuantity) { - - auto project_run = [&](const int N_total, const int samplex) { - const double tol = 1.0e-10; - int argc = 3; - char *argv[3]; - - std::filesystem::path source_file = __FILE__; - std::filesystem::path source_dir = source_file.parent_path(); - std::filesystem::path test_resources_dir = - source_dir / "../../test_resources"; - std::filesystem::path mesh_file = - test_resources_dir / "square_triangles_quads_nummodes_2.xml"; - std::filesystem::path conditions_file = - test_resources_dir / "conditions.xml"; - - copy_to_cstring(std::string("test_particle_function_projection"), &argv[0]); - copy_to_cstring(std::string(mesh_file), &argv[1]); - copy_to_cstring(std::string(conditions_file), &argv[2]); - - LibUtilities::SessionReaderSharedPtr session; - SpatialDomains::MeshGraphSharedPtr graph; - // Create session reader. - session = LibUtilities::SessionReader::CreateInstance(argc, argv); - graph = SpatialDomains::MeshGraph::Read(session); - - auto dis_cont_field = std::make_shared(session, graph, "u"); - - auto mesh = std::make_shared(graph); - auto sycl_target = std::make_shared(0, mesh->get_comm()); - - auto nektar_graph_local_mapper = - std::make_shared(sycl_target, mesh); - - auto domain = std::make_shared(mesh, nektar_graph_local_mapper); - - const int ndim = 2; - ParticleSpec particle_spec{ParticleProp(Sym("P"), ndim, true), - ParticleProp(Sym("CELL_ID"), 1, true), - ParticleProp(Sym("Q"), 1)}; - - auto A = - std::make_shared(domain, particle_spec, sycl_target); - - NektarCartesianPeriodic pbc(sycl_target, graph, A->position_dat); - auto cell_id_translation = - std::make_shared(sycl_target, A->cell_id_dat, mesh); - - const int rank = sycl_target->comm_pair.rank_parent; - const int size = sycl_target->comm_pair.size_parent; - - std::mt19937 rng_pos(52234234 + samplex); - - int rstart, rend; - get_decomp_1d(size, N_total, rank, &rstart, &rend); - const int N = rend - rstart; - - int N_check = -1; - MPICHK(MPI_Allreduce(&N, &N_check, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD)); - NESOASSERT(N_check == N_total, "Error creating particles"); - - const int cell_count = domain->mesh->get_cell_count(); - - if (N > 0) { - auto positions = - uniform_within_extents(N_total, ndim, pbc.global_extent, rng_pos); - - std::uniform_int_distribution uniform_dist( - 0, sycl_target->comm_pair.size_parent - 1); - ParticleSet initial_distribution(N, A->get_particle_spec()); - for (int px = 0; px < N; px++) { - for (int dimx = 0; dimx < ndim; dimx++) { - const double pos_orig = - positions[dimx][rstart + px] + pbc.global_origin[dimx]; - initial_distribution[Sym("P")][px][dimx] = pos_orig; - } - initial_distribution[Sym("CELL_ID")][px][0] = px % cell_count; - } - A->add_particles_local(initial_distribution); - } - reset_mpi_ranks((*A)[Sym("NESO_MPI_RANK")]); - - MeshHierarchyGlobalMap mesh_hierarchy_global_map( - sycl_target, domain->mesh, A->position_dat, A->cell_id_dat, - A->mpi_rank_dat); - - pbc.execute(); - mesh_hierarchy_global_map.execute(); - A->hybrid_move(); - cell_id_translation->execute(); - A->cell_move(); - - const auto k_P = (*A)[Sym("P")]->cell_dat.device_ptr(); - auto k_Q = (*A)[Sym("Q")]->cell_dat.device_ptr(); - - const auto pl_iter_range = A->mpi_rank_dat->get_particle_loop_iter_range(); - const auto pl_stride = A->mpi_rank_dat->get_particle_loop_cell_stride(); - const auto pl_npart_cell = A->mpi_rank_dat->get_particle_loop_npart_cell(); - const REAL two_over_sqrt_pi = 1.1283791670955126; - const REAL reweight = - pbc.global_extent[0] * pbc.global_extent[1] / ((REAL)N_total); - - sycl_target->queue - .submit([&](sycl::handler &cgh) { - cgh.parallel_for<>( - sycl::range<1>(pl_iter_range), [=](sycl::id<1> idx) { - NESO_PARTICLES_KERNEL_START - const INT cellx = NESO_PARTICLES_KERNEL_CELL; - const INT layerx = NESO_PARTICLES_KERNEL_LAYER; - - const REAL x = k_P[cellx][0][layerx]; - const REAL y = k_P[cellx][1][layerx]; - const REAL exp_eval = - two_over_sqrt_pi * exp(-(4.0 * ((x) * (x) + (y) * (y)))); - k_Q[cellx][0][layerx] = exp_eval * reweight; - - NESO_PARTICLES_KERNEL_END - }); - }) - .wait_and_throw(); - - // create projection object - auto field_project = std::make_shared>( - dis_cont_field, A, cell_id_translation); - - // evaluate field at particle locations - field_project->project(Sym("Q")); - - const int tot_quad_points = dis_cont_field->GetTotPoints(); - Array phys_projected(tot_quad_points); - Array phys_correct(tot_quad_points); - - phys_projected = dis_cont_field->GetPhys(); - - // H5Part h5part("exp.h5part", A, Sym("P"), Sym("NESO_MPI_RANK"), - // Sym("NESO_REFERENCE_POSITIONS"), Sym("Q")); - // h5part.write(); - // h5part.close(); - - // write_vtu(dis_cont_field, "func_projected.vtu", "u"); - - auto lambda_f = [&](const NekDouble x, const NekDouble y) { - const REAL two_over_sqrt_pi = 1.1283791670955126; - const REAL exp_eval = - two_over_sqrt_pi * exp(-(4.0 * ((x) * (x) + (y) * (y)))); - return exp_eval; - }; - interpolate_onto_nektar_field_2d(lambda_f, dis_cont_field); - phys_correct = dis_cont_field->GetPhys(); - - // write_vtu(dis_cont_field, "func_correct.vtu", "u"); - - const double err = dis_cont_field->L2(phys_projected, phys_correct); - - mesh->free(); - - delete[] argv[0]; - delete[] argv[1]; - delete[] argv[2]; - - return err; - }; - - const int Nsample = 4; - - std::vector Nparticles; - Nparticles.push_back(200000); - Nparticles.push_back(800000); - - std::map> R_errors; - - for (auto N_total : Nparticles) { - for (int samplex = 0; samplex < Nsample; samplex++) { - const double err = project_run(N_total, samplex); - // nprint(N_total, err); - R_errors[N_total].push_back(err); - } - } - - auto lambda_average = [=](std::vector errors) { - double avg = 0.0; - for (auto &err : errors) { - avg += err; - } - avg /= errors.size(); - return avg; - }; - - const double err_0 = lambda_average(R_errors[Nparticles[0]]); - const double err_1 = lambda_average(R_errors[Nparticles[1]]); - - // nprint(err_0, err_1); - // nprint(err_0 / err_1); - - ASSERT_NEAR(ABS(err_0 / err_1), 2.0, 0.075); -} - -TEST(ParticleFunctionProjection, ContScalarExpQuantity) { - - auto project_run = [&](const int N_total, const int samplex) { - const double tol = 1.0e-10; - int argc = 3; - char *argv[3]; - - std::filesystem::path source_file = __FILE__; - std::filesystem::path source_dir = source_file.parent_path(); - std::filesystem::path test_resources_dir = - source_dir / "../../test_resources"; - std::filesystem::path mesh_file = - test_resources_dir / "square_triangles_quads_nummodes_2.xml"; - std::filesystem::path conditions_file = - test_resources_dir / "conditions_cg.xml"; - - copy_to_cstring(std::string("test_particle_function_projection"), &argv[0]); - copy_to_cstring(std::string(mesh_file), &argv[1]); - copy_to_cstring(std::string(conditions_file), &argv[2]); - - LibUtilities::SessionReaderSharedPtr session; - SpatialDomains::MeshGraphSharedPtr graph; - // Create session reader. - session = LibUtilities::SessionReader::CreateInstance(argc, argv); - graph = SpatialDomains::MeshGraph::Read(session); - - auto cont_field = std::make_shared(session, graph, "u"); - - auto mesh = std::make_shared(graph); - auto sycl_target = std::make_shared(0, mesh->get_comm()); - - auto nektar_graph_local_mapper = - std::make_shared(sycl_target, mesh); - - auto domain = std::make_shared(mesh, nektar_graph_local_mapper); - - const int ndim = 2; - ParticleSpec particle_spec{ParticleProp(Sym("P"), ndim, true), - ParticleProp(Sym("CELL_ID"), 1, true), - ParticleProp(Sym("Q"), 1)}; - - auto A = - std::make_shared(domain, particle_spec, sycl_target); - - NektarCartesianPeriodic pbc(sycl_target, graph, A->position_dat); - auto cell_id_translation = - std::make_shared(sycl_target, A->cell_id_dat, mesh); - - const int rank = sycl_target->comm_pair.rank_parent; - const int size = sycl_target->comm_pair.size_parent; - - std::mt19937 rng_pos(52234234 + samplex); - - int rstart, rend; - get_decomp_1d(size, N_total, rank, &rstart, &rend); - int N = rend - rstart; - - int N_check = -1; - MPICHK(MPI_Allreduce(&N, &N_check, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD)); - NESOASSERT(N_check == N_total, "Error creating particles"); - - const int cell_count = domain->mesh->get_cell_count(); - - if (N > 0) { - auto positions = - uniform_within_extents(N_total, ndim, pbc.global_extent, rng_pos); - - std::uniform_int_distribution uniform_dist( - 0, sycl_target->comm_pair.size_parent - 1); - ParticleSet initial_distribution(N, A->get_particle_spec()); - for (int px = 0; px < N; px++) { - for (int dimx = 0; dimx < ndim; dimx++) { - const double pos_orig = - positions[dimx][rstart + px] + pbc.global_origin[dimx]; - initial_distribution[Sym("P")][px][dimx] = pos_orig; - } - initial_distribution[Sym("CELL_ID")][px][0] = px % cell_count; - } - A->add_particles_local(initial_distribution); - } - reset_mpi_ranks((*A)[Sym("NESO_MPI_RANK")]); - - MeshHierarchyGlobalMap mesh_hierarchy_global_map( - sycl_target, domain->mesh, A->position_dat, A->cell_id_dat, - A->mpi_rank_dat); - - pbc.execute(); - mesh_hierarchy_global_map.execute(); - A->hybrid_move(); - cell_id_translation->execute(); - A->cell_move(); - - const auto k_P = (*A)[Sym("P")]->cell_dat.device_ptr(); - auto k_Q = (*A)[Sym("Q")]->cell_dat.device_ptr(); - - const auto pl_iter_range = A->mpi_rank_dat->get_particle_loop_iter_range(); - const auto pl_stride = A->mpi_rank_dat->get_particle_loop_cell_stride(); - const auto pl_npart_cell = A->mpi_rank_dat->get_particle_loop_npart_cell(); - const REAL two_over_sqrt_pi = 1.1283791670955126; - const REAL reweight = - pbc.global_extent[0] * pbc.global_extent[1] / ((REAL)N_total); - - sycl_target->queue - .submit([&](sycl::handler &cgh) { - cgh.parallel_for<>( - sycl::range<1>(pl_iter_range), [=](sycl::id<1> idx) { - NESO_PARTICLES_KERNEL_START - const INT cellx = NESO_PARTICLES_KERNEL_CELL; - const INT layerx = NESO_PARTICLES_KERNEL_LAYER; - - const REAL x = k_P[cellx][0][layerx]; - const REAL y = k_P[cellx][1][layerx]; - const REAL exp_eval = - two_over_sqrt_pi * exp(-(4.0 * ((x) * (x) + (y) * (y)))); - k_Q[cellx][0][layerx] = exp_eval * reweight; - - NESO_PARTICLES_KERNEL_END - }); - }) - .wait_and_throw(); - - // create projection object - auto field_project = std::make_shared>( - cont_field, A, cell_id_translation); - - // evaluate field at particle locations - field_project->project(Sym("Q")); - - const int tot_quad_points = cont_field->GetTotPoints(); - Array phys_projected(tot_quad_points); - Array phys_correct(tot_quad_points); - - phys_projected = cont_field->GetPhys(); - - // H5Part h5part("exp.h5part", A, Sym("P"), Sym("NESO_MPI_RANK"), - // Sym("NESO_REFERENCE_POSITIONS"), Sym("Q")); - // h5part.write(); - // h5part.close(); - - // write_vtu(cont_field, "func_projected_" + std::to_string(rank) + - // "_0.vtu", "u"); nprint("project integral:", cont_field->Integral(), - // 0.876184); - - auto lambda_f = [&](const NekDouble x, const NekDouble y) { - const REAL two_over_sqrt_pi = 1.1283791670955126; - const REAL exp_eval = - two_over_sqrt_pi * exp(-(4.0 * ((x) * (x) + (y) * (y)))); - return exp_eval; - }; - interpolate_onto_nektar_field_2d(lambda_f, cont_field); - phys_correct = cont_field->GetPhys(); - - // write_vtu(cont_field, "func_correct_" + std::to_string(rank) + "_0.vtu", - // "u"); nprint("correct integral:", cont_field->Integral(), 0.876184); - - const double err = cont_field->L2(phys_projected, phys_correct); - - mesh->free(); - - delete[] argv[0]; - delete[] argv[1]; - delete[] argv[2]; - - return err; - }; - - const int Nsample = 4; - - std::vector Nparticles; - Nparticles.push_back(200000); - Nparticles.push_back(800000); - - std::map> R_errors; - - for (auto N_total : Nparticles) { - for (int samplex = 0; samplex < Nsample; samplex++) { - const double err = project_run(N_total, samplex); - // nprint(N_total, err); - R_errors[N_total].push_back(err); - } - } - - auto lambda_average = [=](std::vector errors) { - double avg = 0.0; - for (auto &err : errors) { - avg += err; - } - avg /= errors.size(); - return avg; - }; - - const double err_0 = lambda_average(R_errors[Nparticles[0]]); - const double err_1 = lambda_average(R_errors[Nparticles[1]]); - - // nprint(err_0, err_1); - // nprint(err_0 / err_1); - - ASSERT_NEAR(ABS(err_0 / err_1), 2.0, 0.075); -} - -TEST(ParticleFunctionProjection, ContScalarExpQuantityMultiple) { - - auto project_run = [&](const int N_total, const int samplex, double *err) { - const double tol = 1.0e-10; - int argc = 3; - char *argv[3]; - - std::filesystem::path source_file = __FILE__; - std::filesystem::path source_dir = source_file.parent_path(); - std::filesystem::path test_resources_dir = - source_dir / "../../test_resources"; - std::filesystem::path mesh_file = - test_resources_dir / "square_triangles_quads_nummodes_2.xml"; - std::filesystem::path conditions_file = - test_resources_dir / "conditions_cg.xml"; - - copy_to_cstring(std::string("test_particle_function_projection"), &argv[0]); - copy_to_cstring(std::string(mesh_file), &argv[1]); - copy_to_cstring(std::string(conditions_file), &argv[2]); - - LibUtilities::SessionReaderSharedPtr session; - SpatialDomains::MeshGraphSharedPtr graph; - // Create session reader. - session = LibUtilities::SessionReader::CreateInstance(argc, argv); - graph = SpatialDomains::MeshGraph::Read(session); - - auto mesh = std::make_shared(graph); - auto sycl_target = std::make_shared(0, mesh->get_comm()); - - auto nektar_graph_local_mapper = - std::make_shared(sycl_target, mesh); - - auto domain = std::make_shared(mesh, nektar_graph_local_mapper); - - const int ndim = 2; - ParticleSpec particle_spec{ParticleProp(Sym("P"), ndim, true), - ParticleProp(Sym("CELL_ID"), 1, true), - ParticleProp(Sym("Q"), 2), - ParticleProp(Sym("Q2"), 3)}; - - auto A = - std::make_shared(domain, particle_spec, sycl_target); - - NektarCartesianPeriodic pbc(sycl_target, graph, A->position_dat); - auto cell_id_translation = - std::make_shared(sycl_target, A->cell_id_dat, mesh); - - const int rank = sycl_target->comm_pair.rank_parent; - const int size = sycl_target->comm_pair.size_parent; - - std::mt19937 rng_pos(52234234 + samplex); - - int rstart, rend; - get_decomp_1d(size, N_total, rank, &rstart, &rend); - int N = rend - rstart; - - int N_check = -1; - MPICHK(MPI_Allreduce(&N, &N_check, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD)); - NESOASSERT(N_check == N_total, "Error creating particles"); - - const int cell_count = domain->mesh->get_cell_count(); - - if (N > 0) { - auto positions = - uniform_within_extents(N_total, ndim, pbc.global_extent, rng_pos); - - std::uniform_int_distribution uniform_dist( - 0, sycl_target->comm_pair.size_parent - 1); - ParticleSet initial_distribution(N, A->get_particle_spec()); - for (int px = 0; px < N; px++) { - for (int dimx = 0; dimx < ndim; dimx++) { - const double pos_orig = - positions[dimx][rstart + px] + pbc.global_origin[dimx]; - initial_distribution[Sym("P")][px][dimx] = pos_orig; - } - initial_distribution[Sym("CELL_ID")][px][0] = px % cell_count; - } - A->add_particles_local(initial_distribution); - } - reset_mpi_ranks((*A)[Sym("NESO_MPI_RANK")]); - - MeshHierarchyGlobalMap mesh_hierarchy_global_map( - sycl_target, domain->mesh, A->position_dat, A->cell_id_dat, - A->mpi_rank_dat); - - pbc.execute(); - mesh_hierarchy_global_map.execute(); - A->hybrid_move(); - cell_id_translation->execute(); - A->cell_move(); - - const auto k_P = (*A)[Sym("P")]->cell_dat.device_ptr(); - auto k_Q = (*A)[Sym("Q")]->cell_dat.device_ptr(); - auto k_Q2 = (*A)[Sym("Q2")]->cell_dat.device_ptr(); - - const auto pl_iter_range = A->mpi_rank_dat->get_particle_loop_iter_range(); - const auto pl_stride = A->mpi_rank_dat->get_particle_loop_cell_stride(); - const auto pl_npart_cell = A->mpi_rank_dat->get_particle_loop_npart_cell(); - const REAL two_over_sqrt_pi = 1.1283791670955126; - const REAL reweight = - pbc.global_extent[0] * pbc.global_extent[1] / ((REAL)N_total); - - sycl_target->queue - .submit([&](sycl::handler &cgh) { - cgh.parallel_for<>( - sycl::range<1>(pl_iter_range), [=](sycl::id<1> idx) { - NESO_PARTICLES_KERNEL_START - const INT cellx = NESO_PARTICLES_KERNEL_CELL; - const INT layerx = NESO_PARTICLES_KERNEL_LAYER; - - const REAL x = k_P[cellx][0][layerx]; - const REAL y = k_P[cellx][1][layerx]; - const REAL exp_eval = - two_over_sqrt_pi * exp(-(4.0 * ((x) * (x) + (y) * (y)))); - k_Q[cellx][0][layerx] = exp_eval * reweight; - k_Q[cellx][1][layerx] = -1.0 * exp_eval * reweight; - k_Q2[cellx][1][layerx] = reweight; - - NESO_PARTICLES_KERNEL_END - }); - }) - .wait_and_throw(); - - auto cont_field_u = std::make_shared(session, graph, "u"); - auto cont_field_v = std::make_shared(session, graph, "v"); - auto cont_field_n = std::make_shared(session, graph, "n"); - - std::vector> cont_fields = { - cont_field_u, cont_field_v, cont_field_n}; - // create projection object - auto field_project = std::make_shared>( - cont_fields, A, cell_id_translation); - - // const double err = 1.0; - - // project field at particle locations - std::vector> project_syms = {Sym("Q"), Sym("Q"), - Sym("Q2")}; - std::vector project_components = {0, 1, 1}; - - if (samplex == 0) { - field_project->testing_enable(); - } - field_project->project(project_syms, project_components); - if (samplex == 0) { - // Checks that the SYCL version matches the original version computed - // using nektar - field_project->project_host(project_syms, project_components); - double *rhs_host, *rhs_device; - field_project->testing_get_rhs(&rhs_host, &rhs_device); - const int ncoeffs = cont_field_u->GetNcoeffs(); - for (int cx = 0; cx < ncoeffs; cx++) { - EXPECT_NEAR(rhs_host[cx], rhs_device[cx], 1.0e-5); - EXPECT_NEAR(rhs_host[cx + ncoeffs], rhs_device[cx + ncoeffs], 1.0e-5); - EXPECT_NEAR(rhs_host[cx + 2 * ncoeffs], rhs_device[cx + 2 * ncoeffs], - 1.0e-5); - } - } - - const int tot_quad_points = cont_field_u->GetTotPoints(); - Array phys_correct(tot_quad_points); - Array phys_projected_u(tot_quad_points); - Array phys_projected_v(tot_quad_points); - Array phys_projected_n(tot_quad_points); - - phys_projected_u = cont_field_u->GetPhys(); - phys_projected_v = cont_field_v->GetPhys(); - phys_projected_n = cont_field_n->GetPhys(); - - // write_vtu(cont_field_u, - // "func_projected_u_" + std::to_string(rank) + "_0.vtu", "u"); - // write_vtu(cont_field_v, - // "func_projected_v_" + std::to_string(rank) + "_0.vtu", "v"); - // write_vtu(cont_field_n, - // "func_projected_n_" + std::to_string(rank) + "_0.vtu", "n"); - - for (int cx = 0; cx < tot_quad_points; cx++) { - ASSERT_NEAR(phys_projected_u[cx], phys_projected_v[cx] * -1.0, 1.0e-2); - - // This bound is huge so there is also an L2 error norm test below. - ASSERT_NEAR(phys_projected_n[cx], 1.0, 0.4); - } - - // H5Part h5part("exp.h5part", A, Sym("P"), Sym("NESO_MPI_RANK"), - // Sym("NESO_REFERENCE_POSITIONS"), Sym("Q")); - // h5part.write(); - // h5part.close(); - // nprint("project integral:", cont_field_u->Integral(), - // 0.876184); - - auto lambda_f = [&](const NekDouble x, const NekDouble y) { - const REAL two_over_sqrt_pi = 1.1283791670955126; - const REAL exp_eval = - two_over_sqrt_pi * exp(-(4.0 * ((x) * (x) + (y) * (y)))); - return exp_eval; - }; - interpolate_onto_nektar_field_2d(lambda_f, cont_field_u); - phys_correct = cont_field_u->GetPhys(); - - // write_vtu(cont_field_u, "func_correct_" + std::to_string(rank) + - // "_0.vtu", "u"); nprint("correct integral:", cont_field_u->Integral(), - // 0.876184); - - *err = cont_field_u->L2(phys_projected_u, phys_correct); - - for (int cx = 0; cx < tot_quad_points; cx++) { - phys_correct[cx] = 1.0; - } - - const double err2 = cont_field_n->L2(phys_projected_n, phys_correct); - ASSERT_TRUE(err2 < 0.15); - - mesh->free(); - - delete[] argv[0]; - delete[] argv[1]; - delete[] argv[2]; - }; - - const int Nsample = 4; - - std::vector Nparticles; - Nparticles.push_back(200000); - Nparticles.push_back(800000); - - std::map> R_errors; - - for (auto N_total : Nparticles) { - for (int samplex = 0; samplex < Nsample; samplex++) { - double err = -1; - project_run(N_total, samplex, &err); - // nprint(N_total, err); - R_errors[N_total].push_back(err); - } - } - - auto lambda_average = [=](std::vector errors) { - double avg = 0.0; - for (auto &err : errors) { - avg += err; - } - avg /= errors.size(); - return avg; - }; - - const double err_0 = lambda_average(R_errors[Nparticles[0]]); - const double err_1 = lambda_average(R_errors[Nparticles[1]]); - - // nprint(err_0, err_1); - // nprint(err_0 / err_1); - - ASSERT_NEAR(ABS(err_0 / err_1), 2.0, 0.075); -} - TEST(ParticleFunctionProjection, BasisEvalCorrectnessCG) { const int N_total = 1000; diff --git a/test/unit/nektar_interface/test_particle_function_projection_3d.cpp b/test/unit/nektar_interface/test_particle_function_projection_3d.cpp new file mode 100644 index 00000000..adb0f7c8 --- /dev/null +++ b/test/unit/nektar_interface/test_particle_function_projection_3d.cpp @@ -0,0 +1,166 @@ +#include "nektar_interface/function_projection.hpp" +#include "nektar_interface/particle_interface.hpp" +#include "nektar_interface/utilities.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace Nektar; +using namespace Nektar::SolverUtils; +using namespace Nektar::SpatialDomains; +using namespace NESO::Particles; + +static inline void copy_to_cstring(std::string input, char **output) { + *output = new char[input.length() + 1]; + std::strcpy(*output, input.c_str()); +} + +template +static inline void projection_wrapper_3d(std::string condtions_file_s, + std::string mesh_file_s, + const double tol) { + + const int N_total = 32000; + + std::filesystem::path source_file = __FILE__; + std::filesystem::path source_dir = source_file.parent_path(); + std::filesystem::path test_resources_dir = + source_dir / "../../test_resources"; + + std::filesystem::path condtions_file_basename{condtions_file_s}; + std::filesystem::path mesh_file_basename{mesh_file_s}; + std::filesystem::path conditions_file = + test_resources_dir / condtions_file_basename; + std::filesystem::path mesh_file = test_resources_dir / mesh_file_basename; + + int argc = 3; + char *argv[3]; + copy_to_cstring(std::string("test_particle_geometry_interface"), &argv[0]); + copy_to_cstring(std::string(conditions_file), &argv[1]); + copy_to_cstring(std::string(mesh_file), &argv[2]); + + LibUtilities::SessionReaderSharedPtr session; + SpatialDomains::MeshGraphSharedPtr graph; + // Create session reader. + session = LibUtilities::SessionReader::CreateInstance(argc, argv); + graph = SpatialDomains::MeshGraph::Read(session); + + auto mesh = std::make_shared(graph); + auto sycl_target = std::make_shared(0, mesh->get_comm()); + + std::mt19937 rng{182348}; + + auto nektar_graph_local_mapper = + std::make_shared(sycl_target, mesh); + auto domain = std::make_shared(mesh, nektar_graph_local_mapper); + + const int ndim = 3; + ParticleSpec particle_spec{ParticleProp(Sym("P"), ndim, true), + ParticleProp(Sym("CELL_ID"), 1, true), + ParticleProp(Sym("E"), 1), + ParticleProp(Sym("ID"), 1)}; + + auto A = std::make_shared(domain, particle_spec, sycl_target); + + NektarCartesianPeriodic pbc(sycl_target, graph, A->position_dat); + auto cell_id_translation = + std::make_shared(sycl_target, A->cell_id_dat, mesh); + const int rank = sycl_target->comm_pair.rank_parent; + const int size = sycl_target->comm_pair.size_parent; + + std::mt19937 rng_pos(52234234 + rank); + int rstart, rend; + get_decomp_1d(size, N_total, rank, &rstart, &rend); + const int N = rend - rstart; + int N_check = -1; + MPICHK(MPI_Allreduce(&N, &N_check, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD)); + NESOASSERT(N_check == N_total, "Error creating particles"); + const int cell_count = domain->mesh->get_cell_count(); + if (N > 0) { + auto positions = + uniform_within_extents(N, ndim, pbc.global_extent, rng_pos); + + ParticleSet initial_distribution(N, A->get_particle_spec()); + for (int px = 0; px < N; px++) { + for (int dimx = 0; dimx < ndim; dimx++) { + const double pos_orig = positions[dimx][px] + pbc.global_origin[dimx]; + initial_distribution[Sym("P")][px][dimx] = pos_orig; + } + + initial_distribution[Sym("CELL_ID")][px][0] = 0; + initial_distribution[Sym("ID")][px][0] = px; + initial_distribution[Sym("E")][px][0] = 2.12412; + } + A->add_particles_local(initial_distribution); + } + reset_mpi_ranks((*A)[Sym("NESO_MPI_RANK")]); + + pbc.execute(); + A->hybrid_move(); + cell_id_translation->execute(); + A->cell_move(); + + auto field = std::make_shared(session, graph, "u"); + + std::vector> fields = {field}; + auto field_project = std::make_shared>( + fields, A, cell_id_translation); + + field_project->testing_enable(); + + std::vector> project_syms = {Sym("E")}; + std::vector project_components = {0}; + + field_project->project(project_syms, project_components); + + // Checks that the SYCL version matches the original version computed + // using nektar + field_project->project_host(project_syms, project_components); + + double *rhs_host, *rhs_device; + field_project->testing_get_rhs(&rhs_host, &rhs_device); + + const int ncoeffs = field->GetNcoeffs(); + for (int cx = 0; cx < ncoeffs; cx++) { + const double err_rel = relative_error(rhs_host[cx], rhs_device[cx]); + const double err_abs = std::abs(rhs_host[cx] - rhs_device[cx]); + ASSERT_TRUE(err_rel < tol || err_abs < tol); + } + + A->free(); + mesh->free(); + + delete[] argv[0]; + delete[] argv[1]; + delete[] argv[2]; +} + +TEST(ParticleFunctionProjection3DBasisEval, ContField) { + projection_wrapper_3d( + "reference_all_types_cube/conditions_cg.xml", + "reference_all_types_cube/mixed_ref_cube_0.5_perturbed.xml", 1.0e-7); +} +TEST(ParticleFunctionProjection3D, DisContFieldHex) { + projection_wrapper_3d( + "reference_hex_cube/conditions.xml", + "reference_hex_cube/hex_cube_0.3_perturbed.xml", 1.0e-7); +} +TEST(ParticleFunctionProjection3D, DisContFieldPrismTet) { + projection_wrapper_3d( + "reference_prism_tet_cube/conditions.xml", + "reference_prism_tet_cube/prism_tet_cube_0.5_perturbed.xml", 1.0e-7); +}